PluginProbe
Friends / 2.7.8
Friends v2.7.8
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 2.7.8, at includes/class-user-feed.php

771 lines 19.5 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 POST_TAXONOMY = 'friend-post-feed';
25 const INTERVAL_BACKTRACK = 600;
26
27 /**
28 * Contains a reference to the \WP_Term for the feed.
29 *
30 * @var \WP_Term
31 */
32 private $term;
33
34 /**
35 * Contains a reference to the associated User.
36 *
37 * @var User
38 */
39 private $friend_user;
40
41 /**
42 * Constructor
43 *
44 * @param \WP_Term $term The WordPress term of the feed taxonomy.
45 * @param User|null $friend_user Optionally the associated User, if available.
46 */
47 public function __construct( \WP_Term $term, User $friend_user = null ) {
48 $this->term = $term;
49 $this->friend_user = $friend_user;
50 }
51
52 /**
53 * The string representation of the term = The URL.
54 *
55 * @return string Term name = URL.
56 */
57 public function __toString() {
58 return $this->term->name;
59 }
60
61 /**
62 * Gets the identifier.
63 *
64 * @return int The identifier.
65 */
66 public function get_id() {
67 return $this->term->term_id;
68 }
69
70 /**
71 * Gets the URL (= the term name).
72 *
73 * @return string The URL (= the term name).
74 */
75 public function get_url() {
76 return $this->term->name;
77 }
78
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
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 /**
99 * Get the local feed URL. Dysfunctional at the moment.
100 *
101 * @return string The local feed URL.
102 */
103 public function get_local_url() {
104 $friend_user = $this->get_friend_user();
105 return home_url() . '/friends/' . $friend_user->user_login . '/feed/';
106 }
107
108 /**
109 * Get the local HTML URL. This would be the URL where to see the friends post on the friends page.
110 * Dysfunctional at the moment.
111 *
112 * @return string The local HTML URL.
113 */
114 public function get_local_html_url() {
115 $friend_user = $this->get_friend_user();
116 return $friend_user->get_local_friends_page_url();
117 }
118
119 /**
120 * Gets the friend user associated wit the term.
121 *
122 * @return User|null The associated user.
123 */
124 public function get_friend_user() {
125 if ( empty( $this->friend_user ) ) {
126 $users = $this->get_all_friend_users();
127 $this->friend_user = reset( $users );
128 }
129
130 return $this->friend_user;
131 }
132
133 /**
134 * Gets the friend users associated wit the term.
135 *
136 * @return array(User) The associated users.
137 */
138 public function get_all_friend_users() {
139 $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 }
150 }
151 }
152
153 return $users;
154 }
155
156 /**
157 * Whether the feed is active (=subscribed).
158 *
159 * @return bool Feed is active if true.
160 */
161 public function get_active() {
162 return self::validate_active( get_metadata( 'term', $this->term->term_id, 'active', true ) );
163 }
164
165 /**
166 * The post format to which the feed items should be imported.
167 *
168 * @return string The post format.
169 */
170 public function get_post_format() {
171 return self::validate_post_format( get_metadata( 'term', $this->term->term_id, 'post-format', true ) );
172 }
173
174 /**
175 * The parser to be used to fetch and parse the feed.
176 *
177 * @return string The parser slug.
178 */
179 public function get_parser() {
180 return self::validate_parser( get_metadata( 'term', $this->term->term_id, 'parser', true ) );
181 }
182
183 /**
184 * The (expected) mime-type of the feed.
185 *
186 * @return string The mime-type.
187 */
188 public function get_mime_type() {
189 return self::validate_mime_type( get_metadata( 'term', $this->term->term_id, 'mime-type', true ) );
190 }
191
192 /**
193 * The title of the feed.
194 *
195 * @return string The feed title.
196 */
197 public function get_title() {
198 return self::validate_title( get_metadata( 'term', $this->term->term_id, 'title', true ) );
199 }
200
201 /**
202 * A log entry of the last retrieval of the feed.
203 *
204 * @return string The log line.
205 */
206 public function get_last_log() {
207 return self::validate_last_log( get_metadata( 'term', $this->term->term_id, 'last-log', true ) );
208 }
209
210 /**
211 * Whether the feed is active.
212 *
213 * @return boolean Is the feed to be fetched?
214 */
215 public function is_active() {
216 return self::validate_active( get_metadata( 'term', $this->term->term_id, 'active', true ) );
217 }
218
219 /**
220 * The interval for calculating the next poll date.
221 *
222 * @return int The interval.
223 */
224 public function get_interval() {
225 return self::validate_interval( get_metadata( 'term', $this->term->term_id, 'interval', true ) );
226 }
227
228 /**
229 * The modifiert for the next interval.
230 *
231 * Examples:
232 * - interval 3600, modifier: 100. intervals: 3600, 3600, 3600, 3600
233 * - interval 3600, modifier: 110. intervals: 3600, 3960, 4356, 4792
234 * - interval 3600, modifier: 200. intervals: 3600, 7200, 14400, 28800
235 *
236 * @return int The modifier.
237 */
238 public function get_modifier() {
239 return self::validate_modifier( get_metadata( 'term', $this->term->term_id, 'modifier', true ) );
240 }
241
242 /**
243 * The datetime when the feed should be polled next.
244 *
245 * @return string The next poll date.
246 */
247 public function get_next_poll() {
248 return self::validate_next_poll( get_metadata( 'term', $this->term->term_id, 'next-poll', true ) );
249 }
250
251 public function was_polled() {
252 $interval = $this->get_interval();
253 $percent = $this->get_modifier();
254
255 $updated_interval = $interval * $percent / 100;
256 if ( $interval !== $updated_interval ) {
257 $this->update_metadata( 'interval', $updated_interval );
258 }
259 // Set a time 5 minutes earlier so that an hourly job that takes longer gets a chance to refresh it.
260 return $this->update_metadata( 'next-poll', gmdate( 'Y-m-d H:i:s', time() + $interval - self::INTERVAL_BACKTRACK ) );
261 }
262
263
264 /**
265 * Validates the post format against defined post formats.
266 *
267 * @param string $post_format The post format to be validated.
268 * @return string A validated post format.
269 */
270 public static function validate_post_format( $post_format ) {
271 $post_formats = get_post_format_strings();
272 $post_formats['autodetect'] = true;
273 if ( isset( $post_formats[ $post_format ] ) ) {
274 return $post_format;
275 }
276 $keys = array_keys( $post_formats );
277 return reset( $keys );
278 }
279
280 /**
281 * Validates the parser against defined parsers.
282 *
283 * @param string $parser The parser to be validated.
284 * @return string A validated parser.
285 */
286 public static function validate_parser( $parser ) {
287 $friends = Friends::get_instance();
288 $parsers = $friends->feed->get_registered_parsers();
289 if ( isset( $parsers[ $parser ] ) ) {
290 return $parser;
291 }
292 // We're lax with parsers to allow deactivating parser plugins without deleting this information.
293 return $parser;
294 }
295
296 /**
297 * Validates the mime-type.
298 *
299 * @param string $mime_type The mime-type to be validated.
300 * @return string A validated mime-type.
301 */
302 public static function validate_mime_type( $mime_type ) {
303 return substr( preg_replace( '/[^a-z0-9\/_+-]/', '', $mime_type ), 0, 100 );
304 }
305
306 /**
307 * Validates the title.
308 *
309 * @param string $title The title to be validated.
310 * @return string A validated title.
311 */
312 public static function validate_title( $title ) {
313 return substr( $title, 0, 100 );
314 }
315
316 /**
317 * Validates the last log line.
318 *
319 * @param string $last_log The log line to be validated.
320 * @return string A validated log line.
321 */
322 public static function validate_last_log( $last_log ) {
323 return substr( $last_log, 0, 1000 );
324 }
325
326 /**
327 * Validates the polling interval.
328 *
329 * @param string $interval The interval to be validated.
330 * @return int A validated interval.
331 */
332 public static function validate_interval( $interval ) {
333 if ( $interval > WEEK_IN_SECONDS ) {
334 return WEEK_IN_SECONDS;
335 }
336 if ( $interval < HOUR_IN_SECONDS ) {
337 return HOUR_IN_SECONDS;
338 }
339 return intval( $interval );
340 }
341
342 /**
343 * Validates the interval percentage modifier.
344 *
345 * @param string $percentage The percentage modifier.
346 * @return int A validated percentage modifier.
347 */
348 public static function validate_modifier( $percentage ) {
349 if ( $percentage > 500 ) {
350 return 500;
351 }
352 if ( $percentage < 100 ) {
353 return 100;
354 }
355 return intval( $percentage );
356 }
357
358 /**
359 * Validates the next poll date.
360 *
361 * @param string $next_poll The poll date to be validated.
362 * @return string A validated poll date.
363 */
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 ) ) {
366 // Explicitly use time() to allow mocking it inside the namespace.
367 return gmdate( 'Y-m-d H:i:s', time() );
368 }
369 return $next_poll;
370 }
371
372 /**
373 * Validates the active attribute.
374 *
375 * @param string $active The active value to be validated.
376 * @return string A validated active value.
377 */
378 public static function validate_active( $active ) {
379 return (bool) $active;
380 }
381
382 /**
383 * Registers the taxonomy
384 */
385 public static function register_taxonomy() {
386 $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(
404 'name' => _x( 'Feed URL', 'taxonomy general name', 'friends' ),
405 'singular_name' => _x( 'Feed URL', 'taxonomy singular name', 'friends' ),
406 'menu_name' => __( 'Feed URL', 'friends' ),
407 ),
408 'hierarchical' => false,
409 'show_ui' => true,
410 'show_admin_column' => true,
411 'query_var' => true,
412 'rewrite' => false,
413 'public' => false,
414 );
415 register_taxonomy( self::TAXONOMY, 'user', $args );
416 register_taxonomy_for_object_type( self::TAXONOMY, 'user' );
417
418 register_term_meta(
419 self::TAXONOMY,
420 'post-format',
421 array(
422 'type' => 'string',
423 'single' => true,
424 'sanitize_callback' => array( __CLASS__, 'validate_post_format' ),
425 )
426 );
427
428 register_term_meta(
429 self::TAXONOMY,
430 'mime-type',
431 array(
432 'type' => 'string',
433 'single' => true,
434 'sanitize_callback' => array( __CLASS__, 'validate_mime_type' ),
435 )
436 );
437
438 register_term_meta(
439 self::TAXONOMY,
440 'title',
441 array(
442 'type' => 'string',
443 'single' => true,
444 'sanitize_callback' => array( __CLASS__, 'validate_title' ),
445 )
446 );
447
448 register_term_meta(
449 self::TAXONOMY,
450 'last-log',
451 array(
452 'type' => 'string',
453 'single' => true,
454 'sanitize_callback' => array( __CLASS__, 'validate_last_log' ),
455 )
456 );
457
458 register_term_meta(
459 self::TAXONOMY,
460 'active',
461 array(
462 'type' => 'boolean',
463 'single' => true,
464 'sanitize_callback' => array( __CLASS__, 'validate_active' ),
465 )
466 );
467
468 register_term_meta(
469 self::TAXONOMY,
470 'parser',
471 array(
472 'type' => 'string',
473 'single' => true,
474 'sanitize_callback' => array( __CLASS__, 'validate_parser' ),
475 )
476 );
477
478 register_term_meta(
479 self::TAXONOMY,
480 'interval',
481 array(
482 'type' => 'integer',
483 'single' => true,
484 'sanitize_callback' => array( __CLASS__, 'validate_interval' ),
485 )
486 );
487
488 register_term_meta(
489 self::TAXONOMY,
490 'modifier',
491 array(
492 'type' => 'integer',
493 'single' => true,
494 'sanitize_callback' => array( __CLASS__, 'validate_modifier' ),
495 )
496 );
497
498 register_term_meta(
499 self::TAXONOMY,
500 'next-poll',
501 array(
502 'type' => 'string',
503 'single' => true,
504 'sanitize_callback' => array( __CLASS__, 'validate_next_poll' ),
505 )
506 );
507
508 do_action( 'friends_after_register_feed_taxonomy' );
509 }
510
511 public function activate() {
512 if ( metadata_exists( 'term', $this->term->term_id, 'active' ) ) {
513 update_metadata( 'term', $this->term->term_id, 'active', true );
514 } else {
515 add_metadata( 'term', $this->term->term_id, 'active', true, true );
516 }
517 do_action( 'friends_user_feed_activated', $this );
518 }
519
520 public function deactivate() {
521 delete_metadata( 'term', $this->term->term_id, 'active' );
522 do_action( 'friends_user_feed_deactivated', $this );
523 }
524
525 /**
526 * Delete this feed.
527 */
528 public function delete() {
529 wp_delete_term( $this->term->term_id, self::TAXONOMY );
530 }
531
532 /**
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 * Saves a new feed as a term for the user.
577 *
578 * @param User $friend_user The user to be associated.
579 * @param string $url The feed URL.
580 * @param array $args Further parameters. Possibly array keys: active, parser, post_format, mime_type, title.
581 * @return User_Feed A newly created User_Feed.
582 */
583 public static function save( User $friend_user, $url, $args = array() ) {
584 $all_urls = $friend_user->save_feeds(
585 array(
586 $url => $args,
587 )
588 );
589
590 if ( is_wp_error( $all_urls ) ) {
591 return $all_urls;
592 }
593
594 if ( ! is_array( $all_urls ) || ! isset( $all_urls[ $url ] ) ) {
595 return new \WP_Error( 'cound-not-save-feed' );
596 }
597
598 $term_id = $all_urls[ $url ];
599
600 $term = get_term( $term_id );
601 if ( is_wp_error( $term ) ) {
602 return $term;
603 }
604
605 return new self( $term );
606 }
607
608 /**
609 * Generic function for getting User_Feed metadata.
610 *
611 * @param string $key The key.
612 */
613 public function get_metadata( $key ) {
614 if ( metadata_exists( 'term', $this->term->term_id, $key ) ) {
615 return get_metadata( 'term', $this->term->term_id, $key, true );
616 }
617 return null;
618 }
619
620 /**
621 * Generic function for updating User_Feed metadata.
622 *
623 * @param string $key The key.
624 * @param string $value The value.
625 */
626 public function update_metadata( $key, $value ) {
627 if ( metadata_exists( 'term', $this->term->term_id, $key ) ) {
628 return update_metadata( 'term', $this->term->term_id, $key, $value );
629 }
630 return add_metadata( 'term', $this->term->term_id, $key, $value, true );
631 }
632
633 /**
634 * Generic function for deleting User_Feed metadata.
635 *
636 * @param string $key The key.
637 */
638 public function delete_metadata( $key ) {
639 if ( metadata_exists( 'term', $this->term->term_id, $key ) ) {
640 return delete_metadata( 'term', $this->term->term_id, $key );
641 }
642 }
643
644 /**
645 * Update the last-log metadata.
646 *
647 * @param string $value The value to log.
648 *
649 * @return int|false The inserted term id.
650 */
651 public function update_last_log( $value ) {
652 // Explicitly use time() to allow mocking it inside the namespace.
653 return $this->update_metadata( 'last-log', gmdate( 'Y-m-d H:i:s', time() ) . ': ' . $value );
654 }
655
656 /**
657 * Get the feed with the specific id.
658 *
659 * @param int $id The feed id.
660 *
661 * @return object|\WP_Error A User_Feed object.
662 */
663 public static function get_by_id( $id ) {
664 $term = get_term( $id, self::TAXONOMY );
665 if ( is_wp_error( $term ) ) {
666 return $term;
667 }
668
669 return new self( $term );
670 }
671
672 /**
673 * Get the feed with a specific URL.
674 *
675 * @param string $url The feed URL.
676 *
677 * @return object|\WP_Error A User_Feed object.
678 */
679 public static function get_by_url( $url ) {
680 $term_query = new \WP_Term_Query(
681 array(
682 'taxonomy' => self::TAXONOMY,
683 'slug' => $url,
684 )
685 );
686 foreach ( $term_query->get_terms() as $term ) {
687 return new self( $term );
688 }
689
690 return new \WP_Error( 'term_not_found' );
691 }
692
693 /**
694 * Get the feed with a specific URL.
695 *
696 * @param bool $also_undue The feed URL.
697 *
698 * @return object|\WP_Error A User_Feed object.
699 */
700 public static function get_all_due( $also_undue = false ) {
701 $term_query = new \WP_Term_Query(
702 array(
703 'taxonomy' => self::TAXONOMY,
704 'meta_key' => 'active',
705 'meta_value' => true,
706 )
707 );
708
709 $due_feeds = array();
710 foreach ( $term_query->get_terms() as $term ) {
711 $feed = new self( $term );
712 // 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() ) {
714 $due_feeds[] = $feed;
715 }
716 }
717
718 // Let's poll the oldest feeds first.
719 usort(
720 $due_feeds,
721 function ( $a, $b ) {
722 return strcmp( $a->get_next_poll(), $b->get_next_poll() );
723 }
724 );
725
726 return $due_feeds;
727 }
728
729 /**
730 * Get all feeds for a parser
731 *
732 * @param string $parser The feed parser.
733 *
734 * @return array A list of user feeds.
735 */
736 public static function get_by_parser( $parser ) {
737 $term_query = new \WP_Term_Query(
738 array(
739 'taxonomy' => self::TAXONOMY,
740 'meta_key' => 'parser',
741 'meta_value' => $parser,
742 )
743 );
744 $feeds = array();
745 foreach ( $term_query->get_terms() as $term ) {
746 $feeds[] = new self( $term );
747 }
748
749 return $feeds;
750 }
751
752 /**
753 * Get the parser for a post
754 *
755 * @param int $post_id The post id.
756 *
757 * @return string The feed parser.
758 */
759 public static function get_parser_for_post_id( $post_id ) {
760 $user_feeds = wp_get_object_terms( $post_id, User_Feed::TAXONOMY );
761 if ( empty( $user_feeds ) ) {
762 // We used to save the parser as post meta.
763 return get_post_meta( $post_id, 'parser', true );
764 }
765
766 $user_feed = reset( $user_feeds );
767 $user_feed = new self( $user_feed );
768 return $user_feed->get_parser();
769 }
770 }
771