PluginProbe
Friends / 4.2.1
Friends v4.2.1
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-feed.php

class-user-feed.php in Friends 4.2.1, at includes/class-user-feed.php

837 lines 21.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Friends Feed URL
4 *
5 * This contains the functions for managing feed URLs.
6 *
7 * @package Friends
8 */
9
10 namespace Friends;
11
12 use WP_Error;
13
14 /**
15 * This is the class for the feed URLs part of the Friends Plugin.
16 *
17 * @since 1.0
18 *
19 * @package Friends
20 * @author Alex Kirk
21 */
22 class User_Feed {
23 const TAXONOMY = 'friend-user-feed';
24 const INTERVAL_BACKTRACK = 600;
25
26 /**
27 * Contains a reference to the \WP_Term for the feed.
28 *
29 * @var \WP_Term
30 */
31 private $term;
32
33 /**
34 * Contains a reference to the associated User.
35 *
36 * @var User
37 */
38 private $friend_user;
39
40 /**
41 * Constructor
42 *
43 * @param \WP_Term $term The WordPress term of the feed taxonomy.
44 * @param User|null $friend_user Optionally the associated User, if available.
45 */
46 public function __construct( \WP_Term $term, ?User $friend_user = null ) {
47 $this->term = $term;
48 $this->friend_user = $friend_user;
49 }
50
51 /**
52 * The string representation of the term = The URL.
53 *
54 * @return string Term name = URL.
55 */
56 public function __toString() {
57 return $this->term->name;
58 }
59
60 /**
61 * Gets the identifier.
62 *
63 * @return int The identifier.
64 */
65 public function get_id() {
66 return $this->term->term_id;
67 }
68
69 /**
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.
73 *
74 * @return string The feed URL.
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 }
81 return $this->term->name;
82 }
83
84
85 /**
86 * Get the local feed URL. Dysfunctional at the moment.
87 *
88 * @return string The local feed URL.
89 */
90 public function get_local_url() {
91 $friend_user = $this->get_friend_user();
92 return home_url() . '/friends/' . $friend_user->user_login . '/feed/';
93 }
94
95 /**
96 * Get the local HTML URL. This would be the URL where to see the friends post on the friends page.
97 * Dysfunctional at the moment.
98 *
99 * @return string The local HTML URL.
100 */
101 public function get_local_html_url() {
102 $friend_user = $this->get_friend_user();
103 return $friend_user->get_local_friends_page_url();
104 }
105
106 /**
107 * Gets the friend user associated wit the term.
108 *
109 * @return User|null The associated user.
110 */
111 public function get_friend_user() {
112 if ( empty( $this->friend_user ) ) {
113 $users = $this->get_all_friend_users();
114 $this->friend_user = reset( $users );
115 }
116
117 return $this->friend_user;
118 }
119
120 /**
121 * Gets the friend users associated wit the term.
122 *
123 * @return array(User) The associated users.
124 */
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 return array();
134 }
135
136 /**
137 * Whether the feed is active (=subscribed).
138 *
139 * @return bool Feed is active if true.
140 */
141 public function get_active() {
142 return self::validate_active( get_metadata( 'term', $this->term->term_id, 'active', true ) );
143 }
144
145 /**
146 * The post format to which the feed items should be imported.
147 *
148 * @return string The post format.
149 */
150 public function get_post_format() {
151 return self::validate_post_format( get_metadata( 'term', $this->term->term_id, 'post-format', true ) );
152 }
153
154 /**
155 * The parser to be used to fetch and parse the feed.
156 *
157 * @return string The parser slug.
158 */
159 public function get_parser() {
160 return self::validate_parser( get_metadata( 'term', $this->term->term_id, 'parser', true ) );
161 }
162
163 /**
164 * The (expected) mime-type of the feed.
165 *
166 * @return string The mime-type.
167 */
168 public function get_mime_type() {
169 return self::validate_mime_type( get_metadata( 'term', $this->term->term_id, 'mime-type', true ) );
170 }
171
172 /**
173 * Gets the linked ActivityPub actor post ID.
174 *
175 * @return int|null The ap_actor post ID, or null if not linked.
176 */
177 public function get_ap_actor_id() {
178 if ( ! class_exists( '\Activitypub\Collection\Remote_Actors' ) ) {
179 return null;
180 }
181
182 $object_ids = get_objects_in_term( $this->term->term_id, self::TAXONOMY );
183 if ( is_wp_error( $object_ids ) || empty( $object_ids ) ) {
184 return null;
185 }
186
187 foreach ( $object_ids as $object_id ) {
188 if ( 'ap_actor' === get_post_type( $object_id ) ) {
189 return (int) $object_id;
190 }
191 }
192
193 return null;
194 }
195
196 /**
197 * Checks if this feed is linked to an ActivityPub actor.
198 *
199 * @return bool True if this is an ActivityPub feed with a linked actor.
200 */
201 public function is_activitypub_feed() {
202 return 'activitypub' === $this->get_parser() && null !== $this->get_ap_actor_id();
203 }
204
205 /**
206 * Gets the URL from the linked ActivityPub actor post.
207 *
208 * @return string|null The actor URL from the ap_actor post, or null if not available.
209 */
210 public function get_ap_actor_url() {
211 $ap_actor_id = $this->get_ap_actor_id();
212 if ( ! $ap_actor_id ) {
213 return null;
214 }
215
216 $ap_actor = get_post( $ap_actor_id );
217 if ( ! $ap_actor ) {
218 return null;
219 }
220
221 return $ap_actor->guid;
222 }
223
224 /**
225 * Sets the linked ActivityPub actor post ID.
226 *
227 * @param int $ap_actor_id The ap_actor post ID.
228 * @return array|false|\WP_Error Array of term taxonomy IDs on success, false or WP_Error on failure.
229 */
230 public function set_ap_actor_id( $ap_actor_id ) {
231 // Ensure taxonomy is registered for ap_actor post type (may not be during cron).
232 if ( class_exists( '\Activitypub\Collection\Remote_Actors' ) ) {
233 register_taxonomy_for_object_type( self::TAXONOMY, \Activitypub\Collection\Remote_Actors::POST_TYPE );
234 }
235
236 $object_ids = get_objects_in_term( $this->term->term_id, self::TAXONOMY );
237 if ( ! is_wp_error( $object_ids ) ) {
238 foreach ( $object_ids as $object_id ) {
239 if ( absint( $ap_actor_id ) !== absint( $object_id ) && 'ap_actor' === get_post_type( $object_id ) ) {
240 wp_remove_object_terms( $object_id, $this->term->term_id, self::TAXONOMY );
241 }
242 }
243 }
244
245 return wp_set_object_terms( absint( $ap_actor_id ), $this->term->term_id, self::TAXONOMY );
246 }
247
248 /**
249 * The title of the feed.
250 *
251 * @return string The feed title.
252 */
253 public function get_title() {
254 return self::validate_title( get_metadata( 'term', $this->term->term_id, 'title', true ) );
255 }
256
257 /**
258 * A log entry of the last retrieval of the feed.
259 *
260 * @return string The log line.
261 */
262 public function get_last_log() {
263 return self::validate_last_log( get_metadata( 'term', $this->term->term_id, 'last-log', true ) );
264 }
265
266 /**
267 * Whether the feed is active.
268 *
269 * @return boolean Is the feed to be fetched?
270 */
271 public function is_active() {
272 return self::validate_active( get_metadata( 'term', $this->term->term_id, 'active', true ) );
273 }
274
275 /**
276 * The interval for calculating the next poll date.
277 *
278 * @return int The interval.
279 */
280 public function get_interval() {
281 return self::validate_interval( get_metadata( 'term', $this->term->term_id, 'interval', true ) );
282 }
283
284 /**
285 * The modifiert for the next interval.
286 *
287 * Examples:
288 * - interval 3600, modifier: 100. intervals: 3600, 3600, 3600, 3600
289 * - interval 3600, modifier: 110. intervals: 3600, 3960, 4356, 4792
290 * - interval 3600, modifier: 200. intervals: 3600, 7200, 14400, 28800
291 *
292 * @return int The modifier.
293 */
294 public function get_modifier() {
295 return self::validate_modifier( get_metadata( 'term', $this->term->term_id, 'modifier', true ) );
296 }
297
298 /**
299 * The datetime when the feed should be polled next.
300 *
301 * @return string The next poll date.
302 */
303 public function get_next_poll() {
304 return self::validate_poll_date( get_metadata( 'term', $this->term->term_id, 'next-poll', true ) );
305 }
306
307 /**
308 * Whether the feed poll has been started within the last 60 seconds.
309 *
310 * @return bool Is the feed currently being polled?
311 */
312 public function can_be_polled_now() {
313 $polling_now = get_metadata( 'term', $this->term->term_id, 'last-poll', true ) > gmdate( 'Y-m-d H:i:s', time() - 60 );
314 // Explicitly use time() to allow mocking it inside the namespace.
315 $due = gmdate( 'Y-m-d H:i:s', time() ) >= $this->get_next_poll();
316 return $due && ! $polling_now;
317 }
318
319 public function set_polling_now() {
320 // Explicitly use time() to allow mocking it inside the namespace.
321 $this->update_metadata( 'last-poll', gmdate( 'Y-m-d H:i:s', time() ) );
322 }
323
324 public function was_polled() {
325 $interval = $this->get_interval();
326 $percent = $this->get_modifier();
327
328 $updated_interval = $interval * $percent / 100;
329 if ( $interval !== $updated_interval ) {
330 $this->update_metadata( 'interval', $updated_interval );
331 }
332 // Set a time 5 minutes earlier so that an hourly job that takes longer gets a chance to refresh it.
333 return $this->update_metadata( 'next-poll', gmdate( 'Y-m-d H:i:s', time() + $interval - self::INTERVAL_BACKTRACK ) );
334 }
335
336
337 /**
338 * Validates the post format against defined post formats.
339 *
340 * @param string $post_format The post format to be validated.
341 * @return string A validated post format.
342 */
343 public static function validate_post_format( $post_format ) {
344 $post_formats = get_post_format_strings();
345 $post_formats['autodetect'] = true;
346 if ( isset( $post_formats[ $post_format ] ) ) {
347 return $post_format;
348 }
349 $keys = array_keys( $post_formats );
350 return reset( $keys );
351 }
352
353 /**
354 * Validates the parser against defined parsers.
355 *
356 * @param string $parser The parser to be validated.
357 * @return string A validated parser.
358 */
359 public static function validate_parser( $parser ) {
360 $friends = Friends::get_instance();
361 $parsers = $friends->feed->get_registered_parsers();
362 if ( isset( $parsers[ $parser ] ) ) {
363 return $parser;
364 }
365 // We're lax with parsers to allow deactivating parser plugins without deleting this information.
366 return $parser;
367 }
368
369 /**
370 * Validates the mime-type.
371 *
372 * @param string $mime_type The mime-type to be validated.
373 * @return string A validated mime-type.
374 */
375 public static function validate_mime_type( $mime_type ) {
376 return substr( preg_replace( '/[^a-z0-9\/_+-]/', '', $mime_type ), 0, 100 );
377 }
378
379 /**
380 * Validates the title.
381 *
382 * @param string $title The title to be validated.
383 * @return string A validated title.
384 */
385 public static function validate_title( $title ) {
386 return substr( $title, 0, 100 );
387 }
388
389 /**
390 * Validates the last log line.
391 *
392 * @param string $last_log The log line to be validated.
393 * @return string A validated log line.
394 */
395 public static function validate_last_log( $last_log ) {
396 return substr( $last_log, 0, 1000 );
397 }
398
399 /**
400 * Validates the polling interval.
401 *
402 * @param string $interval The interval to be validated.
403 * @return int A validated interval.
404 */
405 public static function validate_interval( $interval ) {
406 if ( $interval > WEEK_IN_SECONDS ) {
407 return WEEK_IN_SECONDS;
408 }
409 if ( $interval < HOUR_IN_SECONDS ) {
410 return HOUR_IN_SECONDS;
411 }
412 return intval( $interval );
413 }
414
415 /**
416 * Validates the interval percentage modifier.
417 *
418 * @param string $percentage The percentage modifier.
419 * @return int A validated percentage modifier.
420 */
421 public static function validate_modifier( $percentage ) {
422 if ( $percentage > 500 ) {
423 return 500;
424 }
425 if ( $percentage < 100 ) {
426 return 100;
427 }
428 return intval( $percentage );
429 }
430
431 /**
432 * Validates a poll date.
433 *
434 * @param string $poll_date The poll date to be validated.
435 * @return string A validated poll date.
436 */
437 public static function validate_poll_date( $poll_date ) {
438 if ( ! preg_match( '/^2\d{3}-[01]\d-[0123]\d [012]\d:[0-5]\d:[0-5]\d$/', $poll_date ) ) {
439 // Explicitly use time() to allow mocking it inside the namespace.
440 return gmdate( 'Y-m-d H:i:s', time() );
441 }
442 return $poll_date;
443 }
444
445 /**
446 * Validates the active attribute.
447 *
448 * @param string $active The active value to be validated.
449 * @return string A validated active value.
450 */
451 public static function validate_active( $active ) {
452 return (bool) $active;
453 }
454
455 /**
456 * Registers the taxonomy
457 */
458 public static function register_taxonomy() {
459 $args = array(
460 'labels' => array(
461 'name' => _x( 'Feed URL', 'taxonomy general name', 'friends' ),
462 'singular_name' => _x( 'Feed URL', 'taxonomy singular name', 'friends' ),
463 'menu_name' => __( 'Feed URL', 'friends' ),
464 ),
465 'hierarchical' => true,
466 'show_ui' => false,
467 'show_admin_column' => false,
468 'query_var' => true,
469 'rewrite' => false,
470 'public' => false,
471 'update_count_callback' => '_update_generic_term_count',
472 );
473 register_taxonomy( self::TAXONOMY, array(), $args );
474
475 // Register for ap_actor post type if ActivityPub plugin is active.
476 if ( class_exists( '\Activitypub\Collection\Remote_Actors' ) ) {
477 register_taxonomy_for_object_type( self::TAXONOMY, \Activitypub\Collection\Remote_Actors::POST_TYPE );
478 }
479
480 register_term_meta(
481 self::TAXONOMY,
482 'post-format',
483 array(
484 'type' => 'string',
485 'single' => true,
486 'sanitize_callback' => array( __CLASS__, 'validate_post_format' ),
487 )
488 );
489
490 register_term_meta(
491 self::TAXONOMY,
492 'mime-type',
493 array(
494 'type' => 'string',
495 'single' => true,
496 'sanitize_callback' => array( __CLASS__, 'validate_mime_type' ),
497 )
498 );
499
500 register_term_meta(
501 self::TAXONOMY,
502 'title',
503 array(
504 'type' => 'string',
505 'single' => true,
506 'sanitize_callback' => array( __CLASS__, 'validate_title' ),
507 )
508 );
509
510 register_term_meta(
511 self::TAXONOMY,
512 'last-log',
513 array(
514 'type' => 'string',
515 'single' => true,
516 'sanitize_callback' => array( __CLASS__, 'validate_last_log' ),
517 )
518 );
519
520 register_term_meta(
521 self::TAXONOMY,
522 'active',
523 array(
524 'type' => 'boolean',
525 'single' => true,
526 'sanitize_callback' => array( __CLASS__, 'validate_active' ),
527 )
528 );
529
530 register_term_meta(
531 self::TAXONOMY,
532 'parser',
533 array(
534 'type' => 'string',
535 'single' => true,
536 'sanitize_callback' => array( __CLASS__, 'validate_parser' ),
537 )
538 );
539
540 register_term_meta(
541 self::TAXONOMY,
542 'interval',
543 array(
544 'type' => 'integer',
545 'single' => true,
546 'sanitize_callback' => array( __CLASS__, 'validate_interval' ),
547 )
548 );
549
550 register_term_meta(
551 self::TAXONOMY,
552 'modifier',
553 array(
554 'type' => 'integer',
555 'single' => true,
556 'sanitize_callback' => array( __CLASS__, 'validate_modifier' ),
557 )
558 );
559
560 register_term_meta(
561 self::TAXONOMY,
562 'next-poll',
563 array(
564 'type' => 'string',
565 'single' => true,
566 'sanitize_callback' => array( __CLASS__, 'validate_poll_date' ),
567 )
568 );
569
570 register_term_meta(
571 self::TAXONOMY,
572 'last-poll',
573 array(
574 'type' => 'string',
575 'single' => true,
576 'sanitize_callback' => array( __CLASS__, 'validate_poll_date' ),
577 )
578 );
579
580 do_action( 'friends_after_register_feed_taxonomy' );
581 }
582
583 public function activate() {
584 if ( metadata_exists( 'term', $this->term->term_id, 'active' ) ) {
585 update_metadata( 'term', $this->term->term_id, 'active', true );
586 } else {
587 add_metadata( 'term', $this->term->term_id, 'active', true, true );
588 }
589 do_action( 'friends_user_feed_activated', $this );
590 }
591
592 public function deactivate() {
593 delete_metadata( 'term', $this->term->term_id, 'active' );
594 do_action( 'friends_user_feed_deactivated', $this );
595 }
596
597 /**
598 * Delete this feed.
599 */
600 public function delete() {
601 wp_delete_term( $this->term->term_id, self::TAXONOMY );
602 }
603
604 /**
605 * Saves a new feed as a term for the user.
606 *
607 * @param User $friend_user The user to be associated.
608 * @param string $url The feed URL.
609 * @param array $args Further parameters. Possibly array keys: active, parser, post_format, mime_type, title.
610 * @return User_Feed A newly created User_Feed.
611 */
612 public static function save( User $friend_user, $url, $args = array() ) {
613 $all_urls = $friend_user->save_feeds(
614 array(
615 $url => $args,
616 )
617 );
618
619 if ( is_wp_error( $all_urls ) ) {
620 return $all_urls;
621 }
622
623 if ( ! is_array( $all_urls ) || ! isset( $all_urls[ $url ] ) ) {
624 return new \WP_Error( 'cound-not-save-feed' );
625 }
626
627 $term_id = $all_urls[ $url ];
628
629 $term = get_term( $term_id );
630 if ( is_wp_error( $term ) ) {
631 return $term;
632 }
633
634 return new self( $term, $friend_user );
635 }
636
637 /**
638 * Generic function for getting User_Feed metadata.
639 *
640 * @param string $key The key.
641 */
642 public function get_metadata( $key ) {
643 if ( metadata_exists( 'term', $this->term->term_id, $key ) ) {
644 return get_metadata( 'term', $this->term->term_id, $key, true );
645 }
646 return null;
647 }
648
649 /**
650 * Generic function for updating User_Feed metadata.
651 *
652 * @param string $key The key.
653 * @param string $value The value.
654 */
655 public function update_metadata( $key, $value ) {
656 if ( metadata_exists( 'term', $this->term->term_id, $key ) ) {
657 return update_metadata( 'term', $this->term->term_id, $key, $value );
658 }
659 return add_metadata( 'term', $this->term->term_id, $key, $value, true );
660 }
661
662 /**
663 * Generic function for deleting User_Feed metadata.
664 *
665 * @param string $key The key.
666 */
667 public function delete_metadata( $key ) {
668 if ( metadata_exists( 'term', $this->term->term_id, $key ) ) {
669 return delete_metadata( 'term', $this->term->term_id, $key );
670 }
671 }
672
673 /**
674 * Update the last-log metadata.
675 *
676 * @param string $value The value to log.
677 *
678 * @return int|false The inserted term id.
679 */
680 public function update_last_log( $value ) {
681 // Explicitly use time() to allow mocking it inside the namespace.
682 return $this->update_metadata( 'last-log', gmdate( 'Y-m-d H:i:s', time() ) . ': ' . $value );
683 }
684
685 /**
686 * Get the feed with the specific id.
687 *
688 * @param int $id The feed id.
689 *
690 * @return object|\WP_Error A User_Feed object.
691 */
692 public static function get_by_id( $id ) {
693 $term = get_term( $id, self::TAXONOMY );
694 if ( is_wp_error( $term ) ) {
695 return $term;
696 }
697
698 return new self( $term );
699 }
700
701 /**
702 * Get the feed with a specific URL.
703 *
704 * @param string $url The feed URL.
705 *
706 * @return object|\WP_Error A User_Feed object.
707 */
708 public static function get_by_url( $url ) {
709 $term_query = new \WP_Term_Query(
710 array(
711 'taxonomy' => self::TAXONOMY,
712 'slug' => $url,
713 'hide_empty' => false,
714 )
715 );
716 foreach ( $term_query->get_terms() as $term ) {
717 return new self( $term );
718 }
719
720 return new \WP_Error( 'term_not_found' );
721 }
722
723 /**
724 * Get the feed linked to a specific ap_actor post ID.
725 *
726 * @param int $ap_actor_id The ap_actor post ID.
727 * @return User_Feed|\WP_Error A User_Feed object or WP_Error if not found.
728 */
729 public static function get_by_ap_actor_id( $ap_actor_id ) {
730 $terms = get_the_terms( $ap_actor_id, self::TAXONOMY );
731 if ( $terms && ! is_wp_error( $terms ) ) {
732 return new self( $terms[0] );
733 }
734
735 return new \WP_Error( 'term_not_found' );
736 }
737
738 public static function get_all_users() {
739 $term_query = new \WP_Term_Query(
740 array(
741 'taxonomy' => self::TAXONOMY,
742 'hide_empty' => false,
743 )
744 );
745 $users = array();
746 foreach ( $term_query->get_terms() as $term ) {
747 $feed = new self( $term );
748 $friend_user = $feed->get_friend_user();
749 if ( $friend_user ) {
750 $users[ $friend_user->ID ] = $friend_user;
751 }
752 }
753
754 return $users;
755 }
756
757 /**
758 * Get all feeds due.
759 *
760 * @param bool $ignore_due_date Whether to get also undue feeds.
761 *
762 * @return array An array of User_Feed objects.
763 */
764 public static function get_all_due( $ignore_due_date = false ) {
765 $term_query = new \WP_Term_Query(
766 array(
767 'taxonomy' => self::TAXONOMY,
768 'meta_key' => 'active', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
769 'meta_value' => true, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
770 'hide_empty' => false,
771 )
772 );
773
774 $due_feeds = array();
775 foreach ( $term_query->get_terms() as $term ) {
776 $feed = new self( $term );
777 // Explicitly use time() to allow mocking it inside the namespace.
778 if ( $ignore_due_date || gmdate( 'Y-m-d H:i:s', time() ) >= $feed->get_next_poll() ) {
779 $due_feeds[] = $feed;
780 }
781 }
782
783 // Let's poll the oldest feeds first.
784 usort(
785 $due_feeds,
786 function ( $a, $b ) {
787 return strcmp( $a->get_next_poll(), $b->get_next_poll() );
788 }
789 );
790
791 return $due_feeds;
792 }
793
794 /**
795 * Get all feeds for a parser
796 *
797 * @param string $parser The feed parser.
798 *
799 * @return array A list of user feeds.
800 */
801 public static function get_by_parser( $parser ) {
802 $term_query = new \WP_Term_Query(
803 array(
804 'taxonomy' => self::TAXONOMY,
805 'meta_key' => 'parser', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
806 'meta_value' => $parser, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
807 'hide_empty' => false,
808 )
809 );
810 $feeds = array();
811 foreach ( $term_query->get_terms() as $term ) {
812 $feeds[] = new self( $term );
813 }
814
815 return $feeds;
816 }
817
818 /**
819 * Get the parser for a post
820 *
821 * @param int $post_id The post id.
822 *
823 * @return string The feed parser.
824 */
825 public static function get_parser_for_post_id( $post_id ) {
826 $user_feeds = wp_get_object_terms( $post_id, User_Feed::TAXONOMY );
827 if ( empty( $user_feeds ) ) {
828 // We used to save the parser as post meta.
829 return get_post_meta( $post_id, 'parser', true );
830 }
831
832 $user_feed = reset( $user_feeds );
833 $user_feed = new self( $user_feed );
834 return $user_feed->get_parser();
835 }
836 }
837