PluginProbe
Friends / 2.8.7
Friends v2.8.7
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.8.7, at includes/class-friends.php

1,297 lines 37.5 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( $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 $upgraded = true;
438 break;
439 }
440 }
441 }
442 if ( ! $upgraded ) {
443 return;
444 }
445 $previous_version = get_option( 'friends_plugin_version' );
446
447 if ( version_compare( $previous_version, '2.9.0', '<' ) ) {
448 $users = User_Query::all_associated_users();
449 foreach ( $users->get_results() as $user ) {
450 if ( ! ( $user instanceof Subscription ) ) {
451 // We have a user that is not a virtual user, so the friendship functionality had been used.
452 update_option( 'friends_enable_wp_friendships', 1 );
453 break;
454 }
455 }
456 }
457 if ( version_compare( $previous_version, '0.20.1', '<' ) ) {
458 $users = User_Query::all_associated_users();
459 foreach ( $users->get_results() as $user ) {
460 $gravatar = get_user_option( 'friends_gravatar', $user->ID );
461 $user_icon_url = get_user_option( 'friends_user_icon_url', $user->ID );
462 if ( $gravatar ) {
463 if ( ! $user_icon_url ) {
464 update_user_option( $user->ID, 'friends_user_icon_url', $gravatar );
465 }
466 delete_user_option( $user->ID, 'friends_gravatar' );
467 }
468 }
469 }
470
471 if ( version_compare( $previous_version, '2.1.3', '<' ) ) {
472 self::setup_roles();
473 }
474
475 if ( version_compare( $previous_version, '2.6.0', '<' ) ) {
476 $users = User_Query::all_associated_users();
477 foreach ( $users->get_results() as $user ) {
478 if ( get_option( 'friends_feed_rules_' . $user->ID ) ) {
479 $user->update_user_option( 'friends_feed_rules', get_option( 'friends_feed_rules_' . $user->ID ) );
480 }
481 if ( get_option( 'friends_feed_catch_all_' . $user->ID ) ) {
482 $user->update_user_option( 'friends_feed_catch_all', get_option( 'friends_feed_catch_all_' . $user->ID ) );
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 = 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 if ( isset( $_GET['public'] ) ) {
740 return false;
741 }
742
743 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() ) ) ) {
744 return false;
745 }
746
747 $pagename_parts = explode( '/', trim( $pagename, '/' ) );
748 return count( $pagename_parts ) > 0 && 'friends' === $pagename_parts[0];
749 }
750
751 /**
752 * Check whether the request has been authenticated to display (private) posts.
753 *
754 * @return bool Whether the posts can be accessed.
755 */
756 public static function authenticated_for_posts() {
757 return Access_Control::private_rss_is_authenticated() || ( is_admin() && self::is_main_user() && apply_filters( 'friends_show_cached_posts', false ) );
758 }
759
760 /**
761 * Adds the post types to be displayed on the frontend.
762 *
763 * @param string $post_types The incoming post types.
764 *
765 * @return array The frontend post types.
766 */
767 public static function add_frontend_post_types( $post_types ) {
768 return array_merge( array( Friends::CPT ), $post_types );
769 }
770
771 /**
772 * Gets the post count by post format.
773 *
774 * @return array The post count by post format.
775 */
776 public function get_post_count_by_post_format() {
777 $cache_key = 'friends_post_count_by_post_format';
778
779 $counts = get_transient( $cache_key );
780 if ( false === $counts ) {
781 $counts = array();
782 $post_types = apply_filters( 'friends_frontend_post_types', array() );
783
784 global $wpdb;
785
786 $counts = array();
787
788 $post_format_counts = $wpdb->get_results(
789 $wpdb->prepare(
790 "SELECT terms.slug AS post_format, COUNT(terms.slug) AS count
791 FROM {$wpdb->posts} AS posts
792 JOIN {$wpdb->term_relationships} AS relationships
793 JOIN {$wpdb->term_taxonomy} AS taxonomy
794 JOIN {$wpdb->terms} AS terms
795
796 WHERE posts.post_status IN ( 'publish', 'private' )
797 AND posts.post_type IN ( " . implode( ',', array_fill( 0, count( $post_types ), '%s' ) ) . " )
798 AND relationships.object_id = posts.ID
799 AND relationships.term_taxonomy_id = taxonomy.term_taxonomy_id
800 AND taxonomy.taxonomy = 'post_format'
801
802 AND terms.term_id = taxonomy.term_id
803 GROUP BY terms.slug",
804 $post_types
805 )
806 );
807 $post_count = null;
808 foreach ( $post_types as $post_type ) {
809 $count = wp_count_posts( $post_type );
810 if ( is_null( $post_count ) ) {
811 $post_count = $count;
812 } else {
813 foreach ( (array) $count as $post_status => $c ) {
814 $post_count->$post_status = ( isset( $post_count->$post_status ) ? $post_count->$post_status : 0 ) + $c;
815 }
816 }
817 }
818 $counts['standard'] = $post_count->publish + $post_count->private;
819
820 foreach ( $post_format_counts as $row ) {
821 $counts[ str_replace( 'post-format-', '', $row->post_format ) ] = $row->count;
822 $counts['standard'] -= $row->count;
823 }
824
825 $counts = array_filter( $counts );
826
827 set_transient( $cache_key, $counts, HOUR_IN_SECONDS );
828 }
829
830 return $counts;
831 }
832
833 /**
834 * Get the retention by number of posts.
835 *
836 * @return int The retention by number of posts.
837 */
838 public static function get_retention_number() {
839 $number = get_option( 'friends_retention_number' );
840 if ( $number <= 0 ) {
841 return 2000;
842 }
843
844 return $number;
845 }
846
847 /**
848 * Get the retention by days of posts.
849 *
850 * @return int The retention by days of posts.
851 */
852 public static function get_retention_days() {
853 $days = get_option( 'friends_retention_days' );
854 if ( $days <= 0 ) {
855 return 30;
856 }
857
858 return $days;
859 }
860
861 /**
862 * Gets the post stats.
863 *
864 * @return object The post stats.
865 */
866 public static function get_post_stats() {
867 global $wpdb;
868 $post_types = apply_filters( 'friends_frontend_post_types', array() );
869 $post_stats = $wpdb->get_row(
870 $wpdb->prepare(
871 'SELECT SUM(
872 LENGTH( ID ) +
873 LENGTH( post_author ) +
874 LENGTH( post_date ) +
875 LENGTH( post_date_gmt ) +
876 LENGTH( post_content ) +
877 LENGTH( post_title ) +
878 LENGTH( post_excerpt ) +
879 LENGTH( post_status ) +
880 LENGTH( comment_status ) +
881 LENGTH( ping_status ) +
882 LENGTH( post_password ) +
883 LENGTH( post_name ) +
884 LENGTH( to_ping ) +
885 LENGTH( pinged ) +
886 LENGTH( post_modified ) +
887 LENGTH( post_modified_gmt ) +
888 LENGTH( post_content_filtered ) +
889 LENGTH( post_parent ) +
890 LENGTH( guid ) +
891 LENGTH( menu_order ) +
892 LENGTH( post_type ) +
893 LENGTH( post_mime_type ) +
894 LENGTH( comment_count )
895 ) AS total_size,
896 COUNT(*) as post_count
897 FROM ' . $wpdb->posts . ' WHERE post_type IN ( ' . implode( ', ', array_fill( 0, count( $post_types ), '%s' ) ) . ' )',
898 $post_types
899 ),
900 ARRAY_A
901 );
902 $post_stats['earliest_post_date'] = mysql2date(
903 'U',
904 $wpdb->get_var(
905 $wpdb->prepare(
906 "SELECT MIN(post_date) FROM $wpdb->posts WHERE post_status = 'publish' AND post_type IN ( " . implode( ', ', array_fill( 0, count( $post_types ), '%s' ) ) . ' )',
907 $post_types
908 )
909 )
910 );
911
912 return $post_stats;
913 }
914
915 /**
916 * Gets the main header data.
917 *
918 * @return array The main header data.
919 */
920 public function get_main_header_data() {
921 $data = array(
922 'description' => '',
923 'post_count_by_post_format' => $this->get_post_count_by_post_format(),
924 'post_count_by_post_status' => \wp_count_posts( self::CPT ),
925 );
926
927 return $data;
928 }
929
930 /**
931 * Disables the author page for friends users.
932 */
933 public function disable_friends_author_page() {
934 global $wp_query;
935
936 if ( is_author() && ! self::authenticated_for_posts() ) {
937 $author_obj = $wp_query->get_queried_object();
938 if ( $author_obj instanceof \WP_User && User::is_friends_plugin_user( $author_obj ) && ! self::on_frontend() ) {
939 $wp_query->set_404();
940 status_header( 404 );
941 }
942 }
943 }
944
945 /**
946 * Modify the main query to allow limiting the post format on the homepage.
947 *
948 * @param \WP_Query $query The query.
949 */
950 public function pre_get_posts_filter_by_post_format( $query ) {
951 global $wp_query;
952
953 if ( ! ( $query->is_main_query() || $query->is_feed() ) || ! empty( $wp_query->query['post_format'] ) || $query->is_friends_page ) {
954 return;
955 }
956
957 $tax_query = $this->wp_query_get_post_format_tax_query( array(), get_option( 'friends_limit_homepage_post_format', false ) );
958 if ( $tax_query ) {
959 $query->set( 'tax_query', $tax_query );
960 }
961 }
962
963 /**
964 * Fix a bug in core where it outputs cached friend posts.
965 *
966 * @param array $qvs Query variables.
967 * @return array
968 */
969 public function limit_post_format_request( $qvs ) {
970 if ( isset( $qvs['post_type'] ) && ! is_admin() ) {
971 if ( is_array( $qvs['post_type'] ) ) {
972 $qvs['post_type'] = array_filter( $qvs['post_type'], 'is_post_type_viewable' );
973 } elseif ( ! is_post_type_viewable( $qvs['post_type'] ) ) {
974 unset( $qvs['post_type'] );
975 }
976 }
977
978 return $qvs;
979 }
980 /**
981 * Add a post_format filter to a \WP_Query.
982 *
983 * @param array $tax_query The tax query.
984 * @param string|array $filter_by_post_format The filter by post format.
985 *
986 * @return array|null The tax query, if any.
987 */
988 public function wp_query_get_post_format_tax_query( $tax_query, $filter_by_post_format ) {
989 if ( empty( $filter_by_post_format ) ) {
990 return $tax_query;
991 }
992
993 if ( ! is_array( $filter_by_post_format ) ) {
994 $filter_by_post_format = array( $filter_by_post_format );
995 }
996
997 $post_formats = get_post_format_slugs();
998 $filter_by_post_format = array_filter(
999 $filter_by_post_format,
1000 function ( $post_format ) use ( $post_formats ) {
1001 return in_array( $post_format, $post_formats, true );
1002 }
1003 );
1004 if ( empty( $filter_by_post_format ) ) {
1005 return $tax_query;
1006 }
1007
1008 if ( ! empty( $tax_query ) ) {
1009 $tax_query['relation'] = 'AND';
1010 }
1011 $post_format_query = array(
1012 'taxonomy' => 'post_format',
1013 'field' => 'slug',
1014 );
1015
1016 if ( in_array( 'standard', $filter_by_post_format, true ) ) {
1017 $post_format_query['operator'] = 'NOT IN';
1018 $post_format_query['terms'] = array_values(
1019 array_map(
1020 function ( $post_format ) {
1021 return 'post-format-' . $post_format;
1022 },
1023 array_diff( $post_formats, $filter_by_post_format )
1024 )
1025 );
1026 } else {
1027 $post_format_query['operator'] = 'IN';
1028 $post_format_query['terms'] = array_map(
1029 function ( $post_format ) {
1030 return 'post-format-' . $post_format;
1031 },
1032 $filter_by_post_format
1033 );
1034 }
1035 $tax_query[] = $post_format_query;
1036
1037 return $tax_query;
1038 }
1039
1040 /**
1041 * Plural texts for post formats.
1042 *
1043 * @param string $format The post format.
1044 * @param int $count The count.
1045 *
1046 * @return string The plural string.
1047 */
1048 public function get_post_format_plural_string( $format, $count ) {
1049 if ( ! in_array( $format, get_post_format_slugs(), true ) ) {
1050 $format = 'standard';
1051 }
1052
1053 $plurals = array(
1054 // translators: %s is a count.
1055 'standard' => _nx_noop( '%s post', '%s posts', 'Post format', 'friends' ), // Special case. Any value that evals to false will be considered standard.
1056 // translators: %s is a count.
1057 'aside' => _nx_noop( '%s aside', '%s asides', 'Post format', 'friends' ),
1058 // translators: %s is a count.
1059 'chat' => _nx_noop( '%s chat', '%s chats', 'Post format', 'friends' ),
1060 // translators: %s is a count.
1061 'gallery' => _nx_noop( '%s gallery', '%s galleries', 'Post format', 'friends' ),
1062 // translators: %s is a count.
1063 'link' => _nx_noop( '%s link', '%s links', 'Post format', 'friends' ),
1064 // translators: %s is a count.
1065 'image' => _nx_noop( '%s image', '%s images', 'Post format', 'friends' ),
1066 // translators: %s is a count.
1067 'quote' => _nx_noop( '%s quote', '%s quotes', 'Post format', 'friends' ),
1068 // translators: %s is a count.
1069 'status' => _nx_noop( '%s status', '%s statuses', 'Post format', 'friends' ),
1070 // translators: %s is a count.
1071 'video' => _nx_noop( '%s video', '%s videos', 'Post format', 'friends' ),
1072 // translators: %s is a count.
1073 'audio' => _nx_noop( '%s audio', '%s audios', 'Post format', 'friends' ),
1074 );
1075
1076 return sprintf( translate_nooped_plural( $plurals[ $format ], $count, 'friends' ), number_format_i18n( $count ) );
1077 }
1078
1079 /**
1080 * Overwrite the must_log_in message.
1081 *
1082 * @param array $defaults The default strings.
1083 *
1084 * @return array The modified defaults.
1085 */
1086 public function comment_form_defaults( $defaults ) {
1087 $comment_registration_message = get_option( 'friends_comment_registration_message', __( 'Only people in my network can comment.', 'friends' ) );
1088 $comment_registration_message = str_replace( __( 'my network', 'friends' ), '<a href="' . esc_attr( home_url() . '/friends/' ) . '">' . __( 'my network', 'friends' ) . '</a>', $comment_registration_message );
1089 $defaults['must_log_in'] = $comment_registration_message;
1090 return $defaults;
1091 }
1092
1093 /**
1094 * Get all of the rel links for the HTML head.
1095 */
1096 public static function get_link_rels() {
1097 $rest_prefix = get_rest_url() . REST::PREFIX;
1098 $links = array(
1099 array(
1100 'rel' => 'friends-base-url',
1101 'href' => $rest_prefix,
1102 ),
1103 );
1104
1105 if ( get_option( 'friends_expose_post_format_feeds' ) && current_theme_supports( 'post-formats' ) ) {
1106 $links = array_merge( $links, self::get_html_link_rel_alternate_post_formats() );
1107 }
1108
1109 return $links;
1110 }
1111
1112 /**
1113 * Strip non-ascii bytes.
1114 *
1115 * @param string $text The text.
1116 *
1117 * @return string The text in ASCII only.
1118 */
1119 private static function strip_non_ascii( $text ) {
1120 $text = html_entity_decode( $text );
1121 $text = str_replace( '»', '>', $text );
1122 $text = strtr( $text, '"', '' );
1123 return filter_var( $text, FILTER_DEFAULT, FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_NO_ENCODE_QUOTES );
1124 }
1125
1126 /**
1127 * Get all of the rel links for the HTTP header.
1128 */
1129 public static function http_header() {
1130 foreach ( self::get_link_rels() as $link ) {
1131 $header = 'Link: <' . esc_url( $link['href'] ) . '>; rel="' . esc_attr( $link['rel'] ) . '"';
1132 if ( isset( $link['type'] ) ) {
1133 $header .= '; type="' . self::strip_non_ascii( $link['type'] ) . '"';
1134 }
1135 if ( isset( $link['title'] ) ) {
1136 $header .= '; title="' . self::strip_non_ascii( $link['title'] ) . '"';
1137 }
1138 header( $header, false );
1139 }
1140 }
1141
1142 /**
1143 * Get all of the rel links for the HTML head.
1144 */
1145 public static function get_html_rel_links() {
1146 return array_map(
1147 function ( $link ) {
1148 return '<link' .
1149 ' rel="' . esc_attr( $link['rel'] ) . '"' .
1150 ' href="' . esc_url( $link['href'] ) . '"' .
1151 ( isset( $link['type'] ) ? ( ' type="' . esc_attr( $link['type'] ) . '"' ) : '' ) .
1152 ( isset( $link['title'] ) ? ( ' title="' . esc_attr( $link['title'] ) . '"' ) : '' ) .
1153 ' />';
1154 },
1155 self::get_link_rels()
1156 );
1157 }
1158
1159 /**
1160 * Output all of the rel links for the HTML head.
1161 */
1162 public static function html_rel_links() {
1163 echo wp_kses(
1164 implode( PHP_EOL, self::get_html_rel_links() ),
1165 array(
1166 'link' => array(
1167 'rel' => array(),
1168 'type' => array(),
1169 'href' => array(),
1170 'title' => array(),
1171 ),
1172 )
1173 ), PHP_EOL;
1174 }
1175
1176 /**
1177 * Generate link tag(s) with the alternate post formats.
1178 */
1179 public static function get_html_link_rel_alternate_post_formats() {
1180 $separator = _x( '&raquo;', 'feed link' ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1181 $blog_title = get_bloginfo( 'name' );
1182
1183 $links = array();
1184 foreach ( get_post_format_strings() as $format => $title ) {
1185 // translators: 1: Blog title, 2: Separator (raquo), 3: Post Format.
1186 $title = sprintf( __( '%1$s %2$s %3$s Feed', 'friends' ), $blog_title, $separator, $title );
1187 $links[] = array(
1188 'rel' => 'alternate',
1189 'type' => 'application/rss+xml',
1190 'href' => esc_url( home_url( '/type/' . $format . '/feed/' ) ),
1191 'title' => $title,
1192 );
1193 }
1194
1195 return $links;
1196 }
1197
1198 /**
1199 * Check whether this is a valid URL
1200 *
1201 * @param string $url The URL to check.
1202 * @return false|string URL or false on failure.
1203 */
1204 public static function check_url( $url ) {
1205 $pre = apply_filters( 'friends_pre_check_url', null );
1206 if ( ! is_null( $pre ) ) {
1207 return $pre;
1208 }
1209 $host = parse_url( $url, PHP_URL_HOST );
1210
1211 $check_url = apply_filters( 'friends_host_is_valid', null, $host );
1212 if ( ! is_null( $check_url ) ) {
1213 return $check_url;
1214 }
1215
1216 return wp_http_validate_url( $url );
1217 }
1218
1219 public static function url_truncate( $url, $max_length = 50 ) {
1220 $p = wp_parse_url( untrailingslashit( $url ) );
1221 $parts = array( $p['host'] );
1222 if ( trim( $p['path'] ) ) {
1223 $parts = array_merge( $parts, explode( '/', $p['path'] ) );
1224 }
1225
1226 $url = join( '/', $parts );
1227 $reduce = 4;
1228 while ( strlen( $url ) > $max_length ) {
1229 $last_part = array_pop( $parts );
1230 $last_part = substr( $last_part, strlen( $last_part ) - $reduce );
1231 foreach ( $parts as $k => $part ) {
1232 $parts[ $k ] = substr( $part, 0, strlen( $part ) - $reduce );
1233 }
1234 $url = join( '../', array_filter( $parts ) ) . '../..' . $last_part;
1235 array_push( $parts, $last_part );
1236 $reduce = 1;
1237
1238 }
1239
1240 return $url;
1241 }
1242
1243 /**
1244 * Delete all the data the plugin has stored in WordPress
1245 */
1246 public static function uninstall_plugin() {
1247 $taxonomies = array(
1248 User_Feed::TAXONOMY,
1249 );
1250
1251 $affected_users = new \WP_User_Query( array( 'role__in' => array( 'friend', 'acquaintance', 'friend_request', 'pending_friend_request', 'subscription' ) ) );
1252 foreach ( $affected_users as $user ) {
1253 $in_token = get_user_option( 'friends_in_token', $user->ID );
1254 delete_option( 'friends_in_token_' . $in_token );
1255 delete_user_option( $user->ID, 'friends_out_token' );
1256 delete_user_option( $user->ID, 'friends_in_token' );
1257 delete_user_option( $user->ID, 'friends_new_friend' );
1258 $taxonomies[] = 'friend-reaction-' . $user->ID;
1259 }
1260
1261 delete_option( 'friends_main_user_id' );
1262 remove_role( 'friend' );
1263 remove_role( 'acquaintance' );
1264 remove_role( 'friend_request' );
1265 remove_role( 'pending_friend_request' );
1266 remove_role( 'subscription' );
1267
1268 $friend_posts = new \WP_Query(
1269 array(
1270 'post_type' => Friends::CPT,
1271 'post_status' => array( 'publish', 'private', 'trash' ),
1272 )
1273 );
1274
1275 while ( $friend_posts->have_posts() ) {
1276 $post = $friend_posts->next_post();
1277 wp_delete_post( $post->ID, true );
1278 }
1279
1280 // We have to resort to using direct database statements since the taxonomy is not registered because the plugin is deactivated.
1281 global $wpdb;
1282 foreach ( $taxonomies as $taxonomy ) {
1283 $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 ) );
1284
1285 if ( $terms ) {
1286 foreach ( $terms as $term ) {
1287 $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $term->term_taxonomy_id ) );
1288 $wpdb->delete( $wpdb->terms, array( 'term_id' => $term->term_id ) );
1289 delete_option( 'prefix_' . $term->slug . '_option_name' );
1290 }
1291 }
1292
1293 $wpdb->delete( $wpdb->term_taxonomy, array( 'taxonomy' => $taxonomy ), array( '%s' ) );
1294 }
1295 }
1296 }
1297