PluginProbe
Friends / 2.9.1
Friends v2.9.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-friends.php

class-friends.php in Friends 2.9.1, at includes/class-friends.php

1,299 lines 37.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Friends
4 *
5 * A plugin to connect WordPresses and communicate privately with your friends.
6 *
7 * @package Friends
8 */
9
10 namespace Friends;
11
12 use stdClass;
13
14 /**
15 * This is the class for the Friends Plugin.
16 *
17 * @package Friends
18 * @author Alex Kirk
19 */
20 class Friends {
21 const VERSION = FRIENDS_VERSION;
22 const CPT = 'friend_post_cache';
23 const FEED_URL = 'friends-feed-url';
24 const PLUGIN_URL = 'https://wordpress.org/plugins/friends/';
25 const REQUIRED_ROLE = 'edit_private_posts';
26
27 /**
28 * Initialize the plugin
29 */
30 public static function init() {
31 static::get_instance();
32 }
33
34 /**
35 * A reference to the Admin object.
36 *
37 * @var Admin
38 */
39 public $admin;
40
41 /**
42 * A reference to the Access_Control object.
43 *
44 * @var Access_Control
45 */
46 public $access_control;
47
48 /**
49 * A reference to the Feed object.
50 *
51 * @var Feed
52 */
53 public $feed;
54
55 /**
56 * A reference to the Messages object.
57 *
58 * @var Messages
59 */
60 public $messages;
61
62 /**
63 * A reference to the Notifications object.
64 *
65 * @var Notifications
66 */
67 public $notifications;
68
69 /**
70 * A reference to the Frontend object.
71 *
72 * @var Frontend
73 */
74 public $frontend;
75
76 /**
77 * A reference to the REST object.
78 *
79 * @var REST
80 */
81 public $rest;
82
83 /**
84 * A reference to the Reactions object.
85 *
86 * @var Reactions
87 */
88 public $reactions;
89
90 /**
91 * Get the class singleton
92 *
93 * @return Friends A class instance.
94 */
95 public static function get_instance() {
96 static $instance;
97 if ( ! isset( $instance ) ) {
98 $self = get_called_class();
99 $instance = new $self();
100 }
101 return $instance;
102 }
103
104 /**
105 * Get the Template_Loader singleton
106 *
107 * @return Template_Loader A class instance.
108 */
109 public static function template_loader() {
110 static $template_loader;
111 if ( ! isset( $template_loader ) ) {
112 $template_loader = new Template_Loader();
113 }
114 return $template_loader;
115 }
116
117 /**
118 * Constructor
119 */
120 public function __construct() {
121 $this->access_control = new Access_Control( $this );
122 $this->admin = new Admin( $this );
123 $this->feed = new Feed( $this );
124 $this->messages = new Messages( $this );
125 $this->notifications = new Notifications( $this );
126 $this->frontend = new Frontend( $this );
127 $this->reactions = new Reactions( $this );
128 $this->rest = new REST( $this );
129
130 new Third_Parties( $this );
131 new Blocks( $this );
132 new Logging( $this );
133 new Shortcodes( $this );
134 new Automatic_Status( $this );
135 $this->register_hooks();
136 load_plugin_textdomain( 'friends', false, FRIENDS_PLUGIN_FILE . '/languages/' );
137 do_action( 'friends_loaded', $this );
138 do_action( 'friends_load_parsers', $this->feed );
139 }
140
141 /**
142 * Register the WordPress hooks
143 */
144 private function register_hooks() {
145 add_action( 'init', array( $this, 'register_custom_post_type' ) );
146 add_action( 'init', array( 'Friends\Subscription', 'register_taxonomy' ) );
147 add_action( 'init', array( 'Friends\User_Feed', 'register_taxonomy' ) );
148 add_filter( 'get_avatar_data', array( $this, 'get_avatar_data' ), 10, 2 );
149
150 add_action( 'template_redirect', array( $this, 'http_header' ), 5 );
151 add_filter( 'wp_head', array( $this, 'html_rel_links' ) );
152 add_filter( 'login_head', array( $this, 'html_rel_links' ) );
153
154 add_filter( 'after_setup_theme', array( $this, 'enable_post_formats' ) );
155 add_filter( 'pre_get_posts', array( $this, 'pre_get_posts_filter_by_post_format' ), 20 );
156 add_filter( 'template_redirect', array( $this, 'disable_friends_author_page' ) );
157
158 add_action( 'comment_form_defaults', array( $this, 'comment_form_defaults' ) );
159 add_filter( 'friends_frontend_post_types', array( $this, 'add_frontend_post_types' ) );
160
161 add_filter( 'request', array( $this, 'limit_post_format_request' ), 20 );
162
163 User::register_wrapper_hooks();
164 }
165
166 /**
167 * Registers the custom post type
168 */
169 public function register_custom_post_type() {
170 $labels = array(
171 'name' => __( 'Friend Posts', 'friends' ),
172 'singular_name' => __( 'Friend Post', 'friends' ),
173 'add_new' => _x( 'Add New', 'cached friend post', 'friends' ),
174 'add_new_item' => __( 'Add New Friend Post', 'friends' ),
175 'edit_item' => __( 'Edit Friend Post', 'friends' ),
176 'new_item' => __( 'New Friend Post', 'friends' ),
177 'all_items' => __( 'All Friend Posts', 'friends' ),
178 'view_item' => __( 'View Friend Post', 'friends' ),
179 'search_items' => __( 'Search Friend Posts', 'friends' ),
180 'not_found' => __( 'No Friend Posts found', 'friends' ),
181 'not_found_in_trash' => __( 'No Friend Posts found in the Trash', 'friends' ),
182 'parent_item_colon' => '',
183 'menu_name' => __( 'Cached Friend Posts', 'friends' ),
184 );
185
186 $args = array(
187 'labels' => $labels,
188 'description' => "A cached friend's post",
189 'publicly_queryable' => self::authenticated_for_posts(),
190 'show_ui' => true,
191 'show_in_menu' => apply_filters( 'friends_show_cached_posts', false ),
192 'show_in_nav_menus' => false,
193 'show_in_admin_bar' => false,
194 'show_in_rest' => is_user_logged_in(),
195 'exclude_from_search' => true,
196 'public' => false,
197 'delete_with_user' => true,
198 'menu_position' => 5,
199 'menu_icon' => 'dashicons-groups',
200 'supports' => array( 'title', 'editor', 'author', 'revisions', 'thumbnail', 'excerpt', 'comments', 'post-formats' ),
201 'taxonomies' => array( 'post_tag', 'post_format', 'friend-reaction-' . get_current_user_id() ),
202 'has_archive' => true,
203 'rewrite' => false,
204 );
205
206 register_post_type( self::CPT, $args );
207
208 register_post_meta(
209 self::CPT,
210 Feed::COMMENTS_FEED_META,
211 array(
212 'show_in_rest' => true,
213 'single' => true,
214 'type' => 'string',
215 )
216 );
217
218 register_post_meta(
219 self::CPT,
220 'remote_post_id',
221 array(
222 'show_in_rest' => true,
223 'single' => true,
224 'type' => 'string',
225 )
226 );
227
228 register_post_meta(
229 self::CPT,
230 'parser',
231 array(
232 'show_in_rest' => true,
233 'single' => true,
234 'type' => 'string',
235 )
236 );
237
238 register_post_meta(
239 self::CPT,
240 'feed_url',
241 array(
242 'show_in_rest' => true,
243 'single' => true,
244 'type' => 'string',
245 )
246 );
247
248 register_post_meta(
249 self::CPT,
250 'reblogged',
251 array(
252 'show_in_rest' => true,
253 'single' => true,
254 'type' => 'string',
255 )
256 );
257
258 register_post_meta(
259 self::CPT,
260 'reblogged_by',
261 array(
262 'show_in_rest' => true,
263 'single' => true,
264 'type' => 'integer',
265 )
266 );
267 }
268
269 public static function get_role_capabilities( $role ) {
270 $capabilities = array();
271
272 $capabilities['friend_request'] = array(
273 'friend_request' => true,
274 );
275
276 $capabilities['pending_friend_request'] = array(
277 'pending_friend_request' => true,
278 );
279
280 $capabilities['subscription'] = array(
281 'subscription' => true,
282 );
283
284 $capabilities['acquaintance'] = array(
285 'read' => true,
286 'friend' => true,
287 );
288
289 // Friend is an Acquaintance who can read private posts.
290 $capabilities['friend'] = $capabilities['acquaintance'];
291 $capabilities['friend']['read_private_posts'] = true;
292
293 // All roles belonging to this plugin have the friends_plugin capability.
294 foreach ( array_keys( $capabilities ) as $type ) {
295 $capabilities[ $type ]['friends_plugin'] = true;
296 }
297
298 if ( ! isset( $capabilities[ $role ] ) ) {
299 return array();
300 }
301
302 return $capabilities[ $role ];
303 }
304
305 /**
306 * Create the Friend user roles
307 */
308 private static function setup_roles() {
309 $default_roles = array(
310 'friend' => _x( 'Friend', 'User role', 'friends' ),
311 'acquaintance' => _x( 'Acquaintance', 'User role', 'friends' ),
312 'friend_request' => _x( 'Friend Request', 'User role', 'friends' ),
313 'pending_friend_request' => _x( 'Pending Friend Request', 'User role', 'friends' ),
314 'subscription' => _x( 'Subscription', 'User role', 'friends' ),
315 );
316
317 $roles = new \WP_Roles();
318
319 foreach ( $default_roles as $type => $name ) {
320 $role = false;
321 foreach ( $roles->roles as $slug => $data ) {
322 if ( isset( $data['capabilities'][ $type ] ) ) {
323 $role = get_role( $slug );
324 break;
325 }
326 }
327 if ( ! $role ) {
328 $role = add_role( $type, $name, self::get_role_capabilities( $type ) );
329 continue;
330 }
331
332 // This might update missing capabilities.
333 foreach ( array_keys( self::get_role_capabilities( $type ) ) as $cap ) {
334 $role->add_cap( $cap );
335 }
336 }
337 }
338
339 /**
340 * Creates a page /friends/ to enable customization via.
341 */
342 public static function create_friends_page() {
343 $query = new \WP_Query(
344 array(
345 'name' => 'friends',
346 'post_type' => 'page',
347 )
348 );
349 if ( $query->have_posts() ) {
350 return;
351 }
352 $content = '<!-- wp:paragraph {"className":"only-friends"} -->' . PHP_EOL . '<p class="only-friends">';
353 $content .= __( 'Hi Friend!', 'friends' );
354 $content .= '<br/><br/>';
355 $content .= __( 'Do you know any of my friends? Maybe you want to become friends with them as well?', 'friends' );
356 $content .= PHP_EOL . '</p>' . PHP_EOL . '<!-- /wp:paragraph -->' . PHP_EOL;
357
358 $content .= '<!-- wp:friends/friends-list {"className":"only-friends","user_types":"friends"} /-->' . PHP_EOL;
359
360 $content .= '<!-- wp:paragraph {"className":"not-friends"} -->' . PHP_EOL . '<p class="not-friends">';
361 $content .= __( 'I have connected with my friends using <strong>WordPress</strong> and the <strong>Friends plugin</strong>. This means I can share private posts with just my friends while keeping my data under control.', 'friends' );
362 $content .= PHP_EOL;
363 // translators: %1$s and %2$s are URLs.
364 $content .= sprintf( __( 'If you also have a WordPress site with the friends plugin, you can send me a friend request. If not, get your own <a href="%1$s">WordPress</a> now, install the <a href="%2$s">Friends plugin</a>, and follow me!', 'friends' ), 'https://wordpress.org/', self::PLUGIN_URL );
365 $content .= PHP_EOL . '</p>' . PHP_EOL . '<!-- /wp:paragraph -->' . PHP_EOL;
366
367 $content .= '<!-- wp:friends/follow-me {"className":"not-friends"} -->' . PHP_EOL . '<div class="wp-block-friends-follow-me not-friends">';
368 $content .= '<form method="post"><!-- wp:paragraph -->' . PHP_EOL . '<p>';
369 $content .= __( 'Enter your blog URL to join my network. <a href="https://wpfriends.at/follow-me">Learn more</a>', 'friends' );
370 $content .= '</p>' . PHP_EOL;
371 $content .= '<!-- /wp:paragraph --><div><input type="text" name="friends_friend_request_url" placeholder="https://example.com/"/> <button>';
372 $content .= __( 'Follow this site', 'friends' );
373 $content .= '</button></div></form></div>' . PHP_EOL;
374 $content .= '</p>' . PHP_EOL . '<!-- /wp:friends/follow-me -->' . PHP_EOL;
375
376 $post_data = array(
377 'post_title' => __( 'Friends', 'friends' ),
378 'post_content' => $content,
379 'post_type' => 'page',
380 'post_name' => 'friends',
381 'post_status' => 'publish',
382 );
383 wp_insert_post( $post_data );
384 }
385
386 /**
387 * Enable translated user roles.
388 * props https://wordpress.stackexchange.com/a/141705/74893
389 *
390 * @param string $translations The potentially translated text.
391 * @param string $text Text to translate.
392 * @param string $context Context information for the translators.
393 * @param string $domain Unique identifier for retrieving translated strings.
394 * @return string Translated text on success, original text on failure.
395 */
396 public static function translate_user_role( $translations, $text, $context, $domain ) {
397 $roles = array(
398 'Friend',
399 'Acquaintance',
400 'Friend Request',
401 'Pending Friend Request',
402 'Subscription',
403 );
404
405 if (
406 'User role' === $context
407 && in_array( $text, $roles, true )
408 && 'friends' !== $domain
409 ) {
410 // @codingStandardsIgnoreLine
411 return translate_with_gettext_context( $text, $context, 'friends' );
412 }
413
414 return $translations;
415 }
416
417 /**
418 * Gets the roles that the plugin uses.
419 *
420 * @return array The roles.
421 */
422 public static function get_friends_plugin_roles() {
423 return apply_filters( 'friends_plugin_roles', array( 'friend', 'pending_friend_request', 'friend_request', 'subscription' ) );
424 }
425
426 /**
427 * If a plugin version upgrade requires changes, they can be done here
428 *
429 * @param \WP_Upgrader $upgrader_object The WP_Upgrader instance.
430 * @param array $options Array of bulk item update data.
431 */
432 public static function upgrade_plugin_trigger( $upgrader_object, $options ) {
433 $upgraded = false;
434 if ( 'update' === $options['action'] && 'plugin' === $options['type'] && isset( $options['plugins'] ) ) {
435 foreach ( $options['plugins'] as $plugin ) {
436 if ( FRIENDS_PLUGIN_BASENAME === $plugin ) {
437 return self::upgrade_plugin();
438 }
439 }
440 }
441 }
442
443 public static function upgrade_plugin() {
444 $previous_version = get_option( 'friends_plugin_version' );
445
446 if ( version_compare( $previous_version, '0.20.1', '<' ) ) {
447 $users = User_Query::all_associated_users();
448 foreach ( $users->get_results() as $user ) {
449 $gravatar = get_user_option( 'friends_gravatar', $user->ID );
450 $user_icon_url = get_user_option( 'friends_user_icon_url', $user->ID );
451 if ( $gravatar ) {
452 if ( ! $user_icon_url ) {
453 update_user_option( $user->ID, 'friends_user_icon_url', $gravatar );
454 }
455 delete_user_option( $user->ID, 'friends_gravatar' );
456 }
457 }
458 }
459
460 if ( version_compare( $previous_version, '2.1.3', '<' ) ) {
461 self::setup_roles();
462 }
463
464 if ( version_compare( $previous_version, '2.6.0', '<' ) ) {
465 $users = User_Query::all_associated_users();
466 foreach ( $users->get_results() as $user ) {
467 if ( get_option( 'friends_feed_rules_' . $user->ID ) ) {
468 $user->update_user_option( 'friends_feed_rules', get_option( 'friends_feed_rules_' . $user->ID ) );
469 }
470 if ( get_option( 'friends_feed_catch_all_' . $user->ID ) ) {
471 $user->update_user_option( 'friends_feed_catch_all', get_option( 'friends_feed_catch_all_' . $user->ID ) );
472 }
473 }
474 }
475
476 if ( version_compare( $previous_version, '2.8.7', '<' ) ) {
477 $users = User_Query::all_associated_users();
478 foreach ( $users->get_results() as $user ) {
479 if ( ! ( $user instanceof Subscription ) ) {
480 // We have a user that is not a virtual user, so the friendship functionality had been used.
481 update_option( 'friends_enable_wp_friendships', 1 );
482 break;
483 }
484 }
485 }
486
487 update_option( 'friends_plugin_version', Friends::VERSION );
488 }
489
490 /**
491 * Actions to take upon plugin activation.
492 *
493 * @param bool $network_activate Whether the plugin has been activated network-wide.
494 */
495 public static function activate_plugin( $network_activate = null ) {
496 if ( function_exists( 'is_multisite' ) && is_multisite() ) {
497 if ( $network_activate ) {
498 // Only Super Admins can use Network Activate.
499 if ( ! is_super_admin() ) {
500 return;
501 }
502
503 // Activate for each site.
504 foreach ( get_sites() as $blog ) {
505 switch_to_blog( $blog->blog_id );
506 self::setup();
507 restore_current_blog();
508 }
509 } elseif ( current_user_can( 'activate_plugins' ) ) {
510 self::setup();
511 }
512 return;
513 }
514
515 self::setup();
516 }
517
518 /**
519 * Actions to take upon plugin activation.
520 *
521 * @param int|WP_Site $blog_id Blog ID.
522 */
523 public static function activate_for_blog( $blog_id ) {
524 if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
525 require_once ABSPATH . 'wp-admin/includes/plugin.php';
526 }
527
528 if ( $blog_id instanceof \WP_Site ) {
529 $blog_id = (int) $blog_id->blog_id;
530 }
531
532 if ( is_plugin_active_for_network( FRIENDS_PLUGIN_BASENAME ) ) {
533 switch_to_blog( $blog_id );
534 self::setup();
535 restore_current_blog();
536 }
537 }
538
539 /**
540 * Actions to take upon plugin activation.
541 */
542 private static function setup() {
543 self::setup_roles();
544 self::create_friends_page();
545
546 self::upgrade_plugin(
547 null,
548 array(
549 'action' => 'update',
550 'plugins' => array( FRIENDS_PLUGIN_BASENAME ),
551 'type' => 'plugin',
552 )
553 );
554
555 if ( false === get_option( 'friends_main_user_id' ) ) {
556 update_option( 'friends_main_user_id', get_current_user_id() );
557 }
558
559 if ( false === get_option( 'friends_private_rss_key' ) ) {
560 update_option( 'friends_private_rss_key', wp_generate_password( 128, false ) );
561 }
562
563 if ( false === get_option( 'friends_default_friend_role' ) ) {
564 update_option( 'friends_default_friend_role', 'friend' );
565 }
566
567 if ( ! wp_next_scheduled( 'cron_friends_refresh_feeds' ) ) {
568 wp_schedule_event( time(), 'hourly', 'cron_friends_refresh_feeds' );
569 }
570
571 self::add_default_sidebars_widgets();
572 flush_rewrite_rules();
573 }
574
575 /**
576 * Add default widgets to the sidebars.
577 */
578 public static function add_default_sidebars_widgets() {
579 $sidebars_widgets = get_option( 'sidebars_widgets' );
580
581 if ( ! is_array( $sidebars_widgets ) ) {
582 $sidebars_widgets = array();
583 }
584 $id = count( $sidebars_widgets );
585
586 foreach ( array(
587 'friends-topbar' => array(
588 'friends-widget-header' => array(
589 'title' => 'Friends',
590 ),
591 'friends-widget-new-private-post' => array(),
592 ),
593 'friends-sidebar' => array(
594 'friends-widget-refresh' => array(),
595 'friends-widget-post-formats' => array(),
596 'friends-widget-friend-list' => array(
597 'title' => 'Friends',
598 ),
599 'friends-widget-friend-request' => array(),
600 ),
601 ) as $sidebar_id => $default_widgets ) {
602 if ( ! empty( $sidebars_widgets[ $sidebar_id ] ) ) {
603 continue;
604 }
605 $sidebars_widgets[ $sidebar_id ] = array();
606 foreach ( $default_widgets as $widget_id => $options ) {
607 $id += 1;
608 $sidebars_widgets[ $sidebar_id ][] = $widget_id . '-' . $id;
609 update_option( 'widget_' . $widget_id, array( $id => $options ) );
610 }
611 }
612
613 update_option( 'sidebars_widgets', $sidebars_widgets );
614 }
615
616 /**
617 * Actions to take upon plugin deactivation.
618 */
619 public static function deactivate_plugin() {
620 $timestamp = wp_next_scheduled( 'cron_friends_refresh_feeds' );
621 wp_unschedule_event( $timestamp, 'cron_friends_refresh_feeds' );
622 }
623
624 public static function required_menu_role() {
625 return 'edit_private_posts';
626 }
627
628 public static function has_required_privileges() {
629 return self::is_main_user() || current_user_can( 'administrator' );
630 }
631
632 public static function is_main_user() {
633 return is_user_logged_in() && get_current_user_id() === self::get_main_friend_user_id();
634 }
635
636 /**
637 * Get the main friend user id.
638 *
639 * @return int The user_id.
640 */
641 public static function get_main_friend_user_id() {
642 $main_user_id = get_option( 'friends_main_user_id' );
643
644 if (
645 false === $main_user_id
646 || ( is_multisite() && ! is_user_member_of_blog( $main_user_id, get_current_blog_id() ) )
647 ) {
648 $users = User_Query::all_admin_users();
649 foreach ( $users->get_results() as $user ) {
650 $main_user_id = $user->ID;
651 break;
652 }
653 if ( $main_user_id ) {
654 update_option( 'friends_main_user_id', $main_user_id );
655 }
656 }
657 return intval( $main_user_id );
658 }
659
660 /**
661 * Override Avatar URL for friends
662 *
663 * @param array $args The avatar details array.
664 * @param mixed $id_or_email Identifies what to retrieve the avatar for.
665 * @return array The avatar (potentially modified) details array.
666 */
667 public function get_avatar_data( $args, $id_or_email ) {
668 if ( is_object( $id_or_email ) ) {
669 if ( $id_or_email instanceof User ) {
670 $id_or_email = $id_or_email->ID;
671 } elseif ( $id_or_email instanceof \WP_Post ) {
672 $id_or_email = $id_or_email->post_author;
673 } elseif ( $id_or_email instanceof \WP_Comment ) {
674 $id_or_email = $id_or_email->user_id;
675 }
676 }
677
678 $user = false;
679 if ( is_numeric( $id_or_email ) && $id_or_email > 0 ) {
680 $user = get_user_by( 'ID', $id_or_email );
681 if ( $user ) {
682 $user = new User( $user );
683 }
684 } elseif ( is_string( $id_or_email ) ) {
685 $user = User::get_by_username( $id_or_email );
686 }
687
688 if ( $user ) {
689 if ( is_wp_error( $user ) ) {
690 return $args;
691 }
692
693 $url = $user->get_avatar_url();
694 if ( $url ) {
695 $args['url'] = $url;
696 $args['found_avatar'] = true;
697 }
698 }
699 return $args;
700 }
701
702 /**
703 * Enables the post formats.
704 */
705 public function enable_post_formats() {
706 if ( get_option( 'friends_force_enable_post_formats' ) ) {
707 add_theme_support( 'post-formats', get_post_format_slugs() );
708 }
709 }
710
711 /**
712 * Determine whether we are on the /friends/ page or a subpage.
713 *
714 * @return boolean Whether we are on a friends page URL.
715 */
716 public static function on_frontend() {
717 global $wp_query;
718
719 if ( ! isset( $wp_query ) || ! isset( $wp_query->query['pagename'] ) ) {
720 // The request has not yet been parsed but we need to know, so this snippet is from wp->parse_request().
721 list( $req_uri ) = explode( '?', $_SERVER['REQUEST_URI'] );
722 $home_path = wp_parse_url( home_url(), PHP_URL_PATH );
723 $home_path_regex = '';
724 if ( is_string( $home_path ) && '' !== $home_path ) {
725 $home_path = trim( $home_path, '/' );
726 $home_path_regex = sprintf( '|^%s|i', preg_quote( $home_path, '|' ) );
727 }
728 $req_uri = trim( $req_uri, '/' );
729
730 if ( ! empty( $home_path_regex ) ) {
731 $req_uri = preg_replace( $home_path_regex, '', $req_uri );
732 $req_uri = trim( $req_uri, '/' );
733 }
734 $pagename = $req_uri;
735 } else {
736 $pagename = $wp_query->query['pagename'];
737 }
738
739 // A nonce is not needed here since this is to show the public page.
740 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
741 if ( isset( $_GET['public'] ) ) {
742 return false;
743 }
744
745 if ( ! current_user_can( self::REQUIRED_ROLE ) || ( is_multisite() && ! is_user_member_of_blog( get_current_user_id(), get_current_blog_id() ) && is_super_admin( get_current_user_id() ) ) ) {
746 return false;
747 }
748
749 $pagename_parts = explode( '/', trim( $pagename, '/' ) );
750 return count( $pagename_parts ) > 0 && 'friends' === $pagename_parts[0];
751 }
752
753 /**
754 * Check whether the request has been authenticated to display (private) posts.
755 *
756 * @return bool Whether the posts can be accessed.
757 */
758 public static function authenticated_for_posts() {
759 return Access_Control::private_rss_is_authenticated() || ( is_admin() && self::is_main_user() && apply_filters( 'friends_show_cached_posts', false ) );
760 }
761
762 /**
763 * Adds the post types to be displayed on the frontend.
764 *
765 * @param string $post_types The incoming post types.
766 *
767 * @return array The frontend post types.
768 */
769 public static function add_frontend_post_types( $post_types ) {
770 return array_merge( array( Friends::CPT ), $post_types );
771 }
772
773 /**
774 * Gets the post count by post format.
775 *
776 * @return array The post count by post format.
777 */
778 public function get_post_count_by_post_format() {
779 $cache_key = 'friends_post_count_by_post_format';
780
781 $counts = get_transient( $cache_key );
782 if ( false === $counts ) {
783 $counts = array();
784 $post_types = apply_filters( 'friends_frontend_post_types', array() );
785
786 global $wpdb;
787
788 $counts = array();
789
790 $post_format_counts = $wpdb->get_results(
791 $wpdb->prepare(
792 "SELECT terms.slug AS post_format, COUNT(terms.slug) AS count
793 FROM {$wpdb->posts} AS posts
794 JOIN {$wpdb->term_relationships} AS relationships
795 JOIN {$wpdb->term_taxonomy} AS taxonomy
796 JOIN {$wpdb->terms} AS terms
797
798 WHERE posts.post_status IN ( 'publish', 'private' )
799 AND posts.post_type IN ( " . implode( ',', array_fill( 0, count( $post_types ), '%s' ) ) . " )
800 AND relationships.object_id = posts.ID
801 AND relationships.term_taxonomy_id = taxonomy.term_taxonomy_id
802 AND taxonomy.taxonomy = 'post_format'
803
804 AND terms.term_id = taxonomy.term_id
805 GROUP BY terms.slug",
806 $post_types
807 )
808 );
809 $post_count = null;
810 foreach ( $post_types as $post_type ) {
811 $count = wp_count_posts( $post_type );
812 if ( is_null( $post_count ) ) {
813 $post_count = $count;
814 } else {
815 foreach ( (array) $count as $post_status => $c ) {
816 $post_count->$post_status = ( isset( $post_count->$post_status ) ? $post_count->$post_status : 0 ) + $c;
817 }
818 }
819 }
820 $counts['standard'] = $post_count->publish + $post_count->private;
821
822 foreach ( $post_format_counts as $row ) {
823 $counts[ str_replace( 'post-format-', '', $row->post_format ) ] = $row->count;
824 $counts['standard'] -= $row->count;
825 }
826
827 $counts = array_filter( $counts );
828
829 set_transient( $cache_key, $counts, HOUR_IN_SECONDS );
830 }
831
832 return $counts;
833 }
834
835 /**
836 * Get the retention by number of posts.
837 *
838 * @return int The retention by number of posts.
839 */
840 public static function get_retention_number() {
841 $number = get_option( 'friends_retention_number' );
842 if ( $number <= 0 ) {
843 return 2000;
844 }
845
846 return $number;
847 }
848
849 /**
850 * Get the retention by days of posts.
851 *
852 * @return int The retention by days of posts.
853 */
854 public static function get_retention_days() {
855 $days = get_option( 'friends_retention_days' );
856 if ( $days <= 0 ) {
857 return 30;
858 }
859
860 return $days;
861 }
862
863 /**
864 * Gets the post stats.
865 *
866 * @return object The post stats.
867 */
868 public static function get_post_stats() {
869 global $wpdb;
870 $post_types = apply_filters( 'friends_frontend_post_types', array() );
871 $post_stats = $wpdb->get_row(
872 $wpdb->prepare(
873 'SELECT SUM(
874 LENGTH( ID ) +
875 LENGTH( post_author ) +
876 LENGTH( post_date ) +
877 LENGTH( post_date_gmt ) +
878 LENGTH( post_content ) +
879 LENGTH( post_title ) +
880 LENGTH( post_excerpt ) +
881 LENGTH( post_status ) +
882 LENGTH( comment_status ) +
883 LENGTH( ping_status ) +
884 LENGTH( post_password ) +
885 LENGTH( post_name ) +
886 LENGTH( to_ping ) +
887 LENGTH( pinged ) +
888 LENGTH( post_modified ) +
889 LENGTH( post_modified_gmt ) +
890 LENGTH( post_content_filtered ) +
891 LENGTH( post_parent ) +
892 LENGTH( guid ) +
893 LENGTH( menu_order ) +
894 LENGTH( post_type ) +
895 LENGTH( post_mime_type ) +
896 LENGTH( comment_count )
897 ) AS total_size,
898 COUNT(*) as post_count
899 FROM ' . $wpdb->posts . ' WHERE post_type IN ( ' . implode( ', ', array_fill( 0, count( $post_types ), '%s' ) ) . ' )',
900 $post_types
901 ),
902 ARRAY_A
903 );
904 $post_stats['earliest_post_date'] = mysql2date(
905 'U',
906 $wpdb->get_var(
907 $wpdb->prepare(
908 "SELECT MIN(post_date) FROM $wpdb->posts WHERE post_status = 'publish' AND post_type IN ( " . implode( ', ', array_fill( 0, count( $post_types ), '%s' ) ) . ' )',
909 $post_types
910 )
911 )
912 );
913
914 return $post_stats;
915 }
916
917 /**
918 * Gets the main header data.
919 *
920 * @return array The main header data.
921 */
922 public function get_main_header_data() {
923 $data = array(
924 'description' => '',
925 'post_count_by_post_format' => $this->get_post_count_by_post_format(),
926 'post_count_by_post_status' => \wp_count_posts( self::CPT ),
927 );
928
929 return $data;
930 }
931
932 /**
933 * Disables the author page for friends users.
934 */
935 public function disable_friends_author_page() {
936 global $wp_query;
937
938 if ( is_author() && ! self::authenticated_for_posts() ) {
939 $author_obj = $wp_query->get_queried_object();
940 if ( $author_obj instanceof \WP_User && User::is_friends_plugin_user( $author_obj ) && ! self::on_frontend() ) {
941 $wp_query->set_404();
942 status_header( 404 );
943 }
944 }
945 }
946
947 /**
948 * Modify the main query to allow limiting the post format on the homepage.
949 *
950 * @param \WP_Query $query The query.
951 */
952 public function pre_get_posts_filter_by_post_format( $query ) {
953 global $wp_query;
954
955 if ( ! ( $query->is_main_query() || $query->is_feed() ) || ! empty( $wp_query->query['post_format'] ) || $query->is_friends_page ) {
956 return;
957 }
958
959 $tax_query = $this->wp_query_get_post_format_tax_query( array(), get_option( 'friends_limit_homepage_post_format', false ) );
960 if ( $tax_query ) {
961 $query->set( 'tax_query', $tax_query );
962 }
963 }
964
965 /**
966 * Fix a bug in core where it outputs cached friend posts.
967 *
968 * @param array $qvs Query variables.
969 * @return array
970 */
971 public function limit_post_format_request( $qvs ) {
972 if ( isset( $qvs['post_type'] ) && ! is_admin() ) {
973 if ( is_array( $qvs['post_type'] ) ) {
974 $qvs['post_type'] = array_filter( $qvs['post_type'], 'is_post_type_viewable' );
975 } elseif ( ! is_post_type_viewable( $qvs['post_type'] ) ) {
976 unset( $qvs['post_type'] );
977 }
978 }
979
980 return $qvs;
981 }
982 /**
983 * Add a post_format filter to a \WP_Query.
984 *
985 * @param array $tax_query The tax query.
986 * @param string|array $filter_by_post_format The filter by post format.
987 *
988 * @return array|null The tax query, if any.
989 */
990 public function wp_query_get_post_format_tax_query( $tax_query, $filter_by_post_format ) {
991 if ( empty( $filter_by_post_format ) ) {
992 return $tax_query;
993 }
994
995 if ( ! is_array( $filter_by_post_format ) ) {
996 $filter_by_post_format = array( $filter_by_post_format );
997 }
998
999 $post_formats = get_post_format_slugs();
1000 $filter_by_post_format = array_filter(
1001 $filter_by_post_format,
1002 function ( $post_format ) use ( $post_formats ) {
1003 return in_array( $post_format, $post_formats, true );
1004 }
1005 );
1006 if ( empty( $filter_by_post_format ) ) {
1007 return $tax_query;
1008 }
1009
1010 if ( ! empty( $tax_query ) ) {
1011 $tax_query['relation'] = 'AND';
1012 }
1013 $post_format_query = array(
1014 'taxonomy' => 'post_format',
1015 'field' => 'slug',
1016 );
1017
1018 if ( in_array( 'standard', $filter_by_post_format, true ) ) {
1019 $post_format_query['operator'] = 'NOT IN';
1020 $post_format_query['terms'] = array_values(
1021 array_map(
1022 function ( $post_format ) {
1023 return 'post-format-' . $post_format;
1024 },
1025 array_diff( $post_formats, $filter_by_post_format )
1026 )
1027 );
1028 } else {
1029 $post_format_query['operator'] = 'IN';
1030 $post_format_query['terms'] = array_map(
1031 function ( $post_format ) {
1032 return 'post-format-' . $post_format;
1033 },
1034 $filter_by_post_format
1035 );
1036 }
1037 $tax_query[] = $post_format_query;
1038
1039 return $tax_query;
1040 }
1041
1042 /**
1043 * Plural texts for post formats.
1044 *
1045 * @param string $format The post format.
1046 * @param int $count The count.
1047 *
1048 * @return string The plural string.
1049 */
1050 public function get_post_format_plural_string( $format, $count ) {
1051 if ( ! in_array( $format, get_post_format_slugs(), true ) ) {
1052 $format = 'standard';
1053 }
1054
1055 $plurals = array(
1056 // translators: %s is a count.
1057 'standard' => _nx_noop( '%s post', '%s posts', 'Post format', 'friends' ), // Special case. Any value that evals to false will be considered standard.
1058 // translators: %s is a count.
1059 'aside' => _nx_noop( '%s aside', '%s asides', 'Post format', 'friends' ),
1060 // translators: %s is a count.
1061 'chat' => _nx_noop( '%s chat', '%s chats', 'Post format', 'friends' ),
1062 // translators: %s is a count.
1063 'gallery' => _nx_noop( '%s gallery', '%s galleries', 'Post format', 'friends' ),
1064 // translators: %s is a count.
1065 'link' => _nx_noop( '%s link', '%s links', 'Post format', 'friends' ),
1066 // translators: %s is a count.
1067 'image' => _nx_noop( '%s image', '%s images', 'Post format', 'friends' ),
1068 // translators: %s is a count.
1069 'quote' => _nx_noop( '%s quote', '%s quotes', 'Post format', 'friends' ),
1070 // translators: %s is a count.
1071 'status' => _nx_noop( '%s status', '%s statuses', 'Post format', 'friends' ),
1072 // translators: %s is a count.
1073 'video' => _nx_noop( '%s video', '%s videos', 'Post format', 'friends' ),
1074 // translators: %s is a count.
1075 'audio' => _nx_noop( '%s audio', '%s audios', 'Post format', 'friends' ),
1076 );
1077
1078 return sprintf( translate_nooped_plural( $plurals[ $format ], $count, 'friends' ), number_format_i18n( $count ) );
1079 }
1080
1081 /**
1082 * Overwrite the must_log_in message.
1083 *
1084 * @param array $defaults The default strings.
1085 *
1086 * @return array The modified defaults.
1087 */
1088 public function comment_form_defaults( $defaults ) {
1089 $comment_registration_message = get_option( 'friends_comment_registration_message', __( 'Only people in my network can comment.', 'friends' ) );
1090 $comment_registration_message = str_replace( __( 'my network', 'friends' ), '<a href="' . esc_attr( home_url() . '/friends/' ) . '">' . __( 'my network', 'friends' ) . '</a>', $comment_registration_message );
1091 $defaults['must_log_in'] = $comment_registration_message;
1092 return $defaults;
1093 }
1094
1095 /**
1096 * Get all of the rel links for the HTML head.
1097 */
1098 public static function get_link_rels() {
1099 $rest_prefix = get_rest_url() . REST::PREFIX;
1100 $links = array(
1101 array(
1102 'rel' => 'friends-base-url',
1103 'href' => $rest_prefix,
1104 ),
1105 );
1106
1107 if ( get_option( 'friends_expose_post_format_feeds' ) && current_theme_supports( 'post-formats' ) ) {
1108 $links = array_merge( $links, self::get_html_link_rel_alternate_post_formats() );
1109 }
1110
1111 return $links;
1112 }
1113
1114 /**
1115 * Strip non-ascii bytes.
1116 *
1117 * @param string $text The text.
1118 *
1119 * @return string The text in ASCII only.
1120 */
1121 private static function strip_non_ascii( $text ) {
1122 $text = html_entity_decode( $text );
1123 $text = str_replace( '»', '>', $text );
1124 $text = strtr( $text, '"', '' );
1125 return filter_var( $text, FILTER_DEFAULT, FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_NO_ENCODE_QUOTES );
1126 }
1127
1128 /**
1129 * Get all of the rel links for the HTTP header.
1130 */
1131 public static function http_header() {
1132 foreach ( self::get_link_rels() as $link ) {
1133 $header = 'Link: <' . esc_url( $link['href'] ) . '>; rel="' . esc_attr( $link['rel'] ) . '"';
1134 if ( isset( $link['type'] ) ) {
1135 $header .= '; type="' . self::strip_non_ascii( $link['type'] ) . '"';
1136 }
1137 if ( isset( $link['title'] ) ) {
1138 $header .= '; title="' . self::strip_non_ascii( $link['title'] ) . '"';
1139 }
1140 header( $header, false );
1141 }
1142 }
1143
1144 /**
1145 * Get all of the rel links for the HTML head.
1146 */
1147 public static function get_html_rel_links() {
1148 return array_map(
1149 function ( $link ) {
1150 return '<link' .
1151 ' rel="' . esc_attr( $link['rel'] ) . '"' .
1152 ' href="' . esc_url( $link['href'] ) . '"' .
1153 ( isset( $link['type'] ) ? ( ' type="' . esc_attr( $link['type'] ) . '"' ) : '' ) .
1154 ( isset( $link['title'] ) ? ( ' title="' . esc_attr( $link['title'] ) . '"' ) : '' ) .
1155 ' />';
1156 },
1157 self::get_link_rels()
1158 );
1159 }
1160
1161 /**
1162 * Output all of the rel links for the HTML head.
1163 */
1164 public static function html_rel_links() {
1165 echo wp_kses(
1166 implode( PHP_EOL, self::get_html_rel_links() ),
1167 array(
1168 'link' => array(
1169 'rel' => array(),
1170 'type' => array(),
1171 'href' => array(),
1172 'title' => array(),
1173 ),
1174 )
1175 ), PHP_EOL;
1176 }
1177
1178 /**
1179 * Generate link tag(s) with the alternate post formats.
1180 */
1181 public static function get_html_link_rel_alternate_post_formats() {
1182 $separator = _x( '&raquo;', 'feed link' ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1183 $blog_title = get_bloginfo( 'name' );
1184
1185 $links = array();
1186 foreach ( get_post_format_strings() as $format => $title ) {
1187 // translators: 1: Blog title, 2: Separator (raquo), 3: Post Format.
1188 $title = sprintf( __( '%1$s %2$s %3$s Feed', 'friends' ), $blog_title, $separator, $title );
1189 $links[] = array(
1190 'rel' => 'alternate',
1191 'type' => 'application/rss+xml',
1192 'href' => esc_url( home_url( '/type/' . $format . '/feed/' ) ),
1193 'title' => $title,
1194 );
1195 }
1196
1197 return $links;
1198 }
1199
1200 /**
1201 * Check whether this is a valid URL
1202 *
1203 * @param string $url The URL to check.
1204 * @return false|string URL or false on failure.
1205 */
1206 public static function check_url( $url ) {
1207 $pre = apply_filters( 'friends_pre_check_url', null );
1208 if ( ! is_null( $pre ) ) {
1209 return $pre;
1210 }
1211 $host = wp_parse_url( $url, PHP_URL_HOST );
1212
1213 $check_url = apply_filters( 'friends_host_is_valid', null, $host );
1214 if ( ! is_null( $check_url ) ) {
1215 return $check_url;
1216 }
1217
1218 return wp_http_validate_url( $url );
1219 }
1220
1221 public static function url_truncate( $url, $max_length = 50 ) {
1222 $p = wp_parse_url( untrailingslashit( $url ) );
1223 $parts = array( $p['host'] );
1224 if ( trim( $p['path'] ) ) {
1225 $parts = array_merge( $parts, explode( '/', $p['path'] ) );
1226 }
1227
1228 $url = join( '/', $parts );
1229 $reduce = 4;
1230 while ( strlen( $url ) > $max_length ) {
1231 $last_part = array_pop( $parts );
1232 $last_part = substr( $last_part, strlen( $last_part ) - $reduce );
1233 foreach ( $parts as $k => $part ) {
1234 $parts[ $k ] = substr( $part, 0, strlen( $part ) - $reduce );
1235 }
1236 $url = join( '../', array_filter( $parts ) ) . '../..' . $last_part;
1237 array_push( $parts, $last_part );
1238 $reduce = 1;
1239
1240 }
1241
1242 return $url;
1243 }
1244
1245 /**
1246 * Delete all the data the plugin has stored in WordPress
1247 */
1248 public static function uninstall_plugin() {
1249 $taxonomies = array(
1250 User_Feed::TAXONOMY,
1251 );
1252
1253 $affected_users = new \WP_User_Query( array( 'role__in' => array( 'friend', 'acquaintance', 'friend_request', 'pending_friend_request', 'subscription' ) ) );
1254 foreach ( $affected_users as $user ) {
1255 $in_token = get_user_option( 'friends_in_token', $user->ID );
1256 delete_option( 'friends_in_token_' . $in_token );
1257 delete_user_option( $user->ID, 'friends_out_token' );
1258 delete_user_option( $user->ID, 'friends_in_token' );
1259 delete_user_option( $user->ID, 'friends_new_friend' );
1260 $taxonomies[] = 'friend-reaction-' . $user->ID;
1261 }
1262
1263 delete_option( 'friends_main_user_id' );
1264 remove_role( 'friend' );
1265 remove_role( 'acquaintance' );
1266 remove_role( 'friend_request' );
1267 remove_role( 'pending_friend_request' );
1268 remove_role( 'subscription' );
1269
1270 $friend_posts = new \WP_Query(
1271 array(
1272 'post_type' => Friends::CPT,
1273 'post_status' => array( 'publish', 'private', 'trash' ),
1274 )
1275 );
1276
1277 while ( $friend_posts->have_posts() ) {
1278 $post = $friend_posts->next_post();
1279 wp_delete_post( $post->ID, true );
1280 }
1281
1282 // We have to resort to using direct database statements since the taxonomy is not registered because the plugin is deactivated.
1283 global $wpdb;
1284 foreach ( $taxonomies as $taxonomy ) {
1285 $terms = $wpdb->get_results( $wpdb->prepare( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy = %s ORDER BY t.name ASC", $taxonomy ) );
1286
1287 if ( $terms ) {
1288 foreach ( $terms as $term ) {
1289 $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $term->term_taxonomy_id ) );
1290 $wpdb->delete( $wpdb->terms, array( 'term_id' => $term->term_id ) );
1291 delete_option( 'prefix_' . $term->slug . '_option_name' );
1292 }
1293 }
1294
1295 $wpdb->delete( $wpdb->term_taxonomy, array( 'taxonomy' => $taxonomy ), array( '%s' ) );
1296 }
1297 }
1298 }
1299