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-friends.php

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

1,263 lines 36.4 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, '0.20.1', '<' ) ) {
448 $users = User_Query::all_associated_users();
449 foreach ( $users->get_results() as $user ) {
450 $gravatar = get_user_option( 'friends_gravatar', $user->ID );
451 $user_icon_url = get_user_option( 'friends_user_icon_url', $user->ID );
452 if ( $gravatar ) {
453 if ( ! $user_icon_url ) {
454 update_user_option( $user->ID, 'friends_user_icon_url', $gravatar );
455 }
456 delete_user_option( $user->ID, 'friends_gravatar' );
457 }
458 }
459 }
460
461 if ( version_compare( $previous_version, '2.1.3', '<' ) ) {
462 self::setup_roles();
463 }
464
465 if ( version_compare( $previous_version, '2.6.0', '<' ) ) {
466 $users = User_Query::all_associated_users();
467 foreach ( $users->get_results() as $user ) {
468 if ( get_option( 'friends_feed_rules_' . $user->ID ) ) {
469 $user->update_user_option( 'friends_feed_rules', get_option( 'friends_feed_rules_' . $user->ID ) );
470 }
471 if ( get_option( 'friends_feed_catch_all_' . $user->ID ) ) {
472 $user->update_user_option( 'friends_feed_catch_all', get_option( 'friends_feed_catch_all_' . $user->ID ) );
473 }
474 }
475 }
476
477 update_option( 'friends_plugin_version', Friends::VERSION );
478 }
479
480 /**
481 * Actions to take upon plugin activation.
482 *
483 * @param bool $network_activate Whether the plugin has been activated network-wide.
484 */
485 public static function activate_plugin( $network_activate = null ) {
486 if ( function_exists( 'is_multisite' ) && is_multisite() ) {
487 if ( $network_activate ) {
488 // Only Super Admins can use Network Activate.
489 if ( ! is_super_admin() ) {
490 return;
491 }
492
493 // Activate for each site.
494 foreach ( get_sites() as $blog ) {
495 switch_to_blog( $blog->blog_id );
496 self::setup();
497 restore_current_blog();
498 }
499 } elseif ( current_user_can( 'activate_plugins' ) ) {
500 self::setup();
501 }
502 return;
503 }
504
505 self::setup();
506 }
507
508 /**
509 * Actions to take upon plugin activation.
510 *
511 * @param int|WP_Site $blog_id Blog ID.
512 */
513 public static function activate_for_blog( $blog_id ) {
514 if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
515 require_once ABSPATH . 'wp-admin/includes/plugin.php';
516 }
517
518 if ( $blog_id instanceof \WP_Site ) {
519 $blog_id = (int) $blog_id->blog_id;
520 }
521
522 if ( is_plugin_active_for_network( FRIENDS_PLUGIN_BASENAME ) ) {
523 switch_to_blog( $blog_id );
524 self::setup();
525 restore_current_blog();
526 }
527 }
528
529 /**
530 * Actions to take upon plugin activation.
531 */
532 private static function setup() {
533 self::setup_roles();
534 self::create_friends_page();
535
536 self::upgrade_plugin(
537 null,
538 array(
539 'action' => 'update',
540 'plugins' => array( FRIENDS_PLUGIN_BASENAME ),
541 'type' => 'plugin',
542 )
543 );
544
545 if ( false === get_option( 'friends_main_user_id' ) ) {
546 update_option( 'friends_main_user_id', get_current_user_id() );
547 }
548
549 if ( false === get_option( 'friends_private_rss_key' ) ) {
550 update_option( 'friends_private_rss_key', wp_generate_password( 128, false ) );
551 }
552
553 if ( false === get_option( 'friends_default_friend_role' ) ) {
554 update_option( 'friends_default_friend_role', 'friend' );
555 }
556
557 if ( ! wp_next_scheduled( 'cron_friends_refresh_feeds' ) ) {
558 wp_schedule_event( time(), 'hourly', 'cron_friends_refresh_feeds' );
559 }
560
561 self::add_default_sidebars_widgets();
562 flush_rewrite_rules();
563 }
564
565 /**
566 * Add default widgets to the sidebars.
567 */
568 public static function add_default_sidebars_widgets() {
569 $sidebars_widgets = get_option( 'sidebars_widgets' );
570
571 if ( ! is_array( $sidebars_widgets ) ) {
572 $sidebars_widgets = array();
573 }
574 $id = count( $sidebars_widgets );
575
576 foreach ( array(
577 'friends-topbar' => array(
578 'friends-widget-header' => array(
579 'title' => 'Friends',
580 ),
581 'friends-widget-new-private-post' => array(),
582 ),
583 'friends-sidebar' => array(
584 'friends-widget-refresh' => array(),
585 'friends-widget-post-formats' => array(),
586 'friends-widget-friend-list' => array(
587 'title' => 'Friends',
588 ),
589 'friends-widget-friend-request' => array(),
590 ),
591 ) as $sidebar_id => $default_widgets ) {
592 if ( ! empty( $sidebars_widgets[ $sidebar_id ] ) ) {
593 continue;
594 }
595 $sidebars_widgets[ $sidebar_id ] = array();
596 foreach ( $default_widgets as $widget_id => $options ) {
597 $id += 1;
598 $sidebars_widgets[ $sidebar_id ][] = $widget_id . '-' . $id;
599 update_option( 'widget_' . $widget_id, array( $id => $options ) );
600 }
601 }
602
603 update_option( 'sidebars_widgets', $sidebars_widgets );
604 }
605
606 /**
607 * Actions to take upon plugin deactivation.
608 */
609 public static function deactivate_plugin() {
610 $timestamp = wp_next_scheduled( 'cron_friends_refresh_feeds' );
611 wp_unschedule_event( $timestamp, 'cron_friends_refresh_feeds' );
612 }
613
614 public static function required_menu_role() {
615 return 'edit_private_posts';
616 }
617
618 public static function has_required_privileges() {
619 return self::is_main_user() || current_user_can( 'administrator' );
620 }
621
622 public static function is_main_user() {
623 return get_current_user_id() === self::get_main_friend_user_id();
624 }
625
626 /**
627 * Get the main friend user id.
628 *
629 * @return int The user_id.
630 */
631 public static function get_main_friend_user_id() {
632 $main_user_id = get_option( 'friends_main_user_id' );
633
634 if (
635 false === $main_user_id
636 || ( is_multisite() && ! is_user_member_of_blog( $main_user_id, get_current_blog_id() ) )
637 ) {
638 $users = User_Query::all_admin_users();
639 foreach ( $users->get_results() as $user ) {
640 $main_user_id = $user->ID;
641 break;
642 }
643 if ( $main_user_id ) {
644 update_option( 'friends_main_user_id', $main_user_id );
645 }
646 }
647 return intval( $main_user_id );
648 }
649
650 /**
651 * Override Avatar URL for friends
652 *
653 * @param array $args The avatar details array.
654 * @param mixed $id_or_email Identifies what to retrieve the avatar for.
655 * @return array The avatar (potentially modified) details array.
656 */
657 public function get_avatar_data( $args, $id_or_email ) {
658 if ( is_object( $id_or_email ) ) {
659 if ( $id_or_email instanceof User ) {
660 $id_or_email = $id_or_email->ID;
661 } elseif ( $id_or_email instanceof \WP_Post ) {
662 $id_or_email = $id_or_email->post_author;
663 } elseif ( $id_or_email instanceof \WP_Comment ) {
664 $id_or_email = $id_or_email->user_id;
665 }
666 }
667
668 $user = false;
669 if ( is_numeric( $id_or_email ) && $id_or_email > 0 ) {
670 $user = get_user_by( 'ID', $id_or_email );
671 if ( $user ) {
672 $user = new User( $user );
673 }
674 } elseif ( is_string( $id_or_email ) ) {
675 $user = User::get_by_username( $id_or_email );
676 }
677
678 if ( $user ) {
679 if ( is_wp_error( $user ) ) {
680 return $args;
681 }
682
683 $url = $user->get_avatar_url();
684 if ( $url ) {
685 $args['url'] = $url;
686 $args['found_avatar'] = true;
687 }
688 }
689 return $args;
690 }
691
692 /**
693 * Enables the post formats.
694 */
695 public function enable_post_formats() {
696 if ( get_option( 'friends_force_enable_post_formats' ) ) {
697 add_theme_support( 'post-formats', get_post_format_slugs() );
698 }
699 }
700
701 /**
702 * Determine whether we are on the /friends/ page or a subpage.
703 *
704 * @return boolean Whether we are on a friends page URL.
705 */
706 public static function on_frontend() {
707 global $wp_query;
708
709 if ( ! isset( $wp_query ) || ! isset( $wp_query->query['pagename'] ) ) {
710 // The request has not yet been parsed but we need to know, so this snippet is from wp->parse_request().
711 list( $req_uri ) = explode( '?', $_SERVER['REQUEST_URI'] );
712 $home_path = parse_url( home_url(), PHP_URL_PATH );
713 $home_path_regex = '';
714 if ( is_string( $home_path ) && '' !== $home_path ) {
715 $home_path = trim( $home_path, '/' );
716 $home_path_regex = sprintf( '|^%s|i', preg_quote( $home_path, '|' ) );
717 }
718 $req_uri = trim( $req_uri, '/' );
719
720 if ( ! empty( $home_path_regex ) ) {
721 $req_uri = preg_replace( $home_path_regex, '', $req_uri );
722 $req_uri = trim( $req_uri, '/' );
723 }
724 $pagename = $req_uri;
725 } else {
726 $pagename = $wp_query->query['pagename'];
727 }
728
729 if ( isset( $_GET['public'] ) ) {
730 return false;
731 }
732
733 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() ) ) ) {
734 return false;
735 }
736
737 $pagename_parts = explode( '/', trim( $pagename, '/' ) );
738 return count( $pagename_parts ) > 0 && 'friends' === $pagename_parts[0];
739 }
740
741 /**
742 * Check whether the request has been authenticated to display (private) posts.
743 *
744 * @return bool Whether the posts can be accessed.
745 */
746 public static function authenticated_for_posts() {
747 return Access_Control::private_rss_is_authenticated() || ( is_admin() && self::is_main_user() && apply_filters( 'friends_show_cached_posts', false ) );
748 }
749
750 /**
751 * Adds the post types to be displayed on the frontend.
752 *
753 * @param string $post_types The incoming post types.
754 *
755 * @return array The frontend post types.
756 */
757 public static function add_frontend_post_types( $post_types ) {
758 return array_merge( array( Friends::CPT ), $post_types );
759 }
760
761 /**
762 * Gets the post count by post format.
763 *
764 * @return array The post count by post format.
765 */
766 public function get_post_count_by_post_format() {
767 $cache_key = 'friends_post_count_by_post_format';
768
769 $counts = get_transient( $cache_key );
770 if ( false === $counts ) {
771 $counts = array();
772 $post_types = apply_filters( 'friends_frontend_post_types', array() );
773
774 global $wpdb;
775
776 $counts = array();
777
778 $post_format_counts = $wpdb->get_results(
779 $wpdb->prepare(
780 "SELECT terms.slug AS post_format, COUNT(terms.slug) AS count
781 FROM {$wpdb->posts} AS posts
782 JOIN {$wpdb->term_relationships} AS relationships
783 JOIN {$wpdb->term_taxonomy} AS taxonomy
784 JOIN {$wpdb->terms} AS terms
785
786 WHERE posts.post_status IN ( 'publish', 'private' )
787 AND posts.post_type IN ( " . implode( ',', array_fill( 0, count( $post_types ), '%s' ) ) . " )
788 AND relationships.object_id = posts.ID
789 AND relationships.term_taxonomy_id = taxonomy.term_taxonomy_id
790 AND taxonomy.taxonomy = 'post_format'
791
792 AND terms.term_id = taxonomy.term_id
793 GROUP BY terms.slug",
794 $post_types
795 )
796 );
797 $post_count = null;
798 foreach ( $post_types as $post_type ) {
799 $count = wp_count_posts( $post_type );
800 if ( is_null( $post_count ) ) {
801 $post_count = $count;
802 } else {
803 foreach ( (array) $count as $post_status => $c ) {
804 $post_count->$post_status = ( isset( $post_count->$post_status ) ? $post_count->$post_status : 0 ) + $c;
805 }
806 }
807 }
808 $counts['standard'] = $post_count->publish + $post_count->private;
809
810 foreach ( $post_format_counts as $row ) {
811 $counts[ str_replace( 'post-format-', '', $row->post_format ) ] = $row->count;
812 $counts['standard'] -= $row->count;
813 }
814
815 $counts = array_filter( $counts );
816
817 set_transient( $cache_key, $counts, HOUR_IN_SECONDS );
818 }
819
820 return $counts;
821 }
822
823 /**
824 * Get the retention by number of posts.
825 *
826 * @return int The retention by number of posts.
827 */
828 public static function get_retention_number() {
829 $number = get_option( 'friends_retention_number' );
830 if ( $number <= 0 ) {
831 return 2000;
832 }
833
834 return $number;
835 }
836
837 /**
838 * Get the retention by days of posts.
839 *
840 * @return int The retention by days of posts.
841 */
842 public static function get_retention_days() {
843 $days = get_option( 'friends_retention_days' );
844 if ( $days <= 0 ) {
845 return 30;
846 }
847
848 return $days;
849 }
850
851 /**
852 * Gets the post stats.
853 *
854 * @return object The post stats.
855 */
856 public static function get_post_stats() {
857 global $wpdb;
858 $post_types = apply_filters( 'friends_frontend_post_types', array() );
859 $post_stats = $wpdb->get_row(
860 $wpdb->prepare(
861 'SELECT SUM(
862 LENGTH( ID ) +
863 LENGTH( post_author ) +
864 LENGTH( post_date ) +
865 LENGTH( post_date_gmt ) +
866 LENGTH( post_content ) +
867 LENGTH( post_title ) +
868 LENGTH( post_excerpt ) +
869 LENGTH( post_status ) +
870 LENGTH( comment_status ) +
871 LENGTH( ping_status ) +
872 LENGTH( post_password ) +
873 LENGTH( post_name ) +
874 LENGTH( to_ping ) +
875 LENGTH( pinged ) +
876 LENGTH( post_modified ) +
877 LENGTH( post_modified_gmt ) +
878 LENGTH( post_content_filtered ) +
879 LENGTH( post_parent ) +
880 LENGTH( guid ) +
881 LENGTH( menu_order ) +
882 LENGTH( post_type ) +
883 LENGTH( post_mime_type ) +
884 LENGTH( comment_count )
885 ) AS total_size,
886 COUNT(*) as post_count
887 FROM ' . $wpdb->posts . ' WHERE post_type IN ( ' . implode( ', ', array_fill( 0, count( $post_types ), '%s' ) ) . ' )',
888 $post_types
889 ),
890 ARRAY_A
891 );
892 $post_stats['earliest_post_date'] = mysql2date(
893 'U',
894 $wpdb->get_var(
895 $wpdb->prepare(
896 "SELECT MIN(post_date) FROM $wpdb->posts WHERE post_status = 'publish' AND post_type IN ( " . implode( ', ', array_fill( 0, count( $post_types ), '%s' ) ) . ' )',
897 $post_types
898 )
899 )
900 );
901
902 return $post_stats;
903 }
904
905 /**
906 * Gets the main header data.
907 *
908 * @return array The main header data.
909 */
910 public function get_main_header_data() {
911 $data = array(
912 'description' => '',
913 'post_count_by_post_format' => $this->get_post_count_by_post_format(),
914 'post_count_by_post_status' => \wp_count_posts( self::CPT ),
915 );
916
917 return $data;
918 }
919
920 /**
921 * Disables the author page for friends users.
922 */
923 public function disable_friends_author_page() {
924 global $wp_query;
925
926 if ( is_author() && ! self::authenticated_for_posts() ) {
927 $author_obj = $wp_query->get_queried_object();
928 if ( $author_obj instanceof \WP_User && User::is_friends_plugin_user( $author_obj ) && ! self::on_frontend() ) {
929 $wp_query->set_404();
930 status_header( 404 );
931 }
932 }
933 }
934
935 /**
936 * Modify the main query to allow limiting the post format on the homepage.
937 *
938 * @param \WP_Query $query The query.
939 */
940 public function pre_get_posts_filter_by_post_format( $query ) {
941 global $wp_query;
942
943 if ( ! ( $query->is_main_query() || $query->is_feed() ) || ! empty( $wp_query->query['post_format'] ) || $query->is_friends_page ) {
944 return;
945 }
946
947 $tax_query = $this->wp_query_get_post_format_tax_query( array(), get_option( 'friends_limit_homepage_post_format', false ) );
948 if ( $tax_query ) {
949 $query->set( 'tax_query', $tax_query );
950 }
951 }
952
953 /**
954 * Fix a bug in core where it outputs cached friend posts.
955 *
956 * @param array $qvs Query variables.
957 * @return array
958 */
959 public function limit_post_format_request( $qvs ) {
960 if ( isset( $qvs['post_type'] ) && ! is_admin() ) {
961 if ( is_array( $qvs['post_type'] ) ) {
962 $qvs['post_type'] = array_filter( $qvs['post_type'], 'is_post_type_viewable' );
963 } elseif ( ! is_post_type_viewable( $qvs['post_type'] ) ) {
964 unset( $qvs['post_type'] );
965 }
966 }
967
968 return $qvs;
969 }
970 /**
971 * Add a post_format filter to a \WP_Query.
972 *
973 * @param array $tax_query The tax query.
974 * @param string|array $filter_by_post_format The filter by post format.
975 *
976 * @return array|null The tax query, if any.
977 */
978 public function wp_query_get_post_format_tax_query( $tax_query, $filter_by_post_format ) {
979 if ( empty( $filter_by_post_format ) ) {
980 return $tax_query;
981 }
982
983 if ( ! is_array( $filter_by_post_format ) ) {
984 $filter_by_post_format = array( $filter_by_post_format );
985 }
986
987 $post_formats = get_post_format_slugs();
988 $filter_by_post_format = array_filter(
989 $filter_by_post_format,
990 function ( $post_format ) use ( $post_formats ) {
991 return in_array( $post_format, $post_formats, true );
992 }
993 );
994 if ( empty( $filter_by_post_format ) ) {
995 return $tax_query;
996 }
997
998 if ( ! empty( $tax_query ) ) {
999 $tax_query['relation'] = 'AND';
1000 }
1001 $post_format_query = array(
1002 'taxonomy' => 'post_format',
1003 'field' => 'slug',
1004 );
1005
1006 if ( in_array( 'standard', $filter_by_post_format, true ) ) {
1007 $post_format_query['operator'] = 'NOT IN';
1008 $post_format_query['terms'] = array_values(
1009 array_map(
1010 function ( $post_format ) {
1011 return 'post-format-' . $post_format;
1012 },
1013 array_diff( $post_formats, $filter_by_post_format )
1014 )
1015 );
1016 } else {
1017 $post_format_query['operator'] = 'IN';
1018 $post_format_query['terms'] = array_map(
1019 function ( $post_format ) {
1020 return 'post-format-' . $post_format;
1021 },
1022 $filter_by_post_format
1023 );
1024 }
1025 $tax_query[] = $post_format_query;
1026
1027 return $tax_query;
1028 }
1029
1030 /**
1031 * Plural texts for post formats.
1032 *
1033 * @param string $format The post format.
1034 * @param int $count The count.
1035 *
1036 * @return string The plural string.
1037 */
1038 public function get_post_format_plural_string( $format, $count ) {
1039 if ( ! in_array( $format, get_post_format_slugs(), true ) ) {
1040 $format = 'standard';
1041 }
1042
1043 $plurals = array(
1044 // translators: %s is a count.
1045 'standard' => _nx_noop( '%s post', '%s posts', 'Post format', 'friends' ), // Special case. Any value that evals to false will be considered standard.
1046 // translators: %s is a count.
1047 'aside' => _nx_noop( '%s aside', '%s asides', 'Post format', 'friends' ),
1048 // translators: %s is a count.
1049 'chat' => _nx_noop( '%s chat', '%s chats', 'Post format', 'friends' ),
1050 // translators: %s is a count.
1051 'gallery' => _nx_noop( '%s gallery', '%s galleries', 'Post format', 'friends' ),
1052 // translators: %s is a count.
1053 'link' => _nx_noop( '%s link', '%s links', 'Post format', 'friends' ),
1054 // translators: %s is a count.
1055 'image' => _nx_noop( '%s image', '%s images', 'Post format', 'friends' ),
1056 // translators: %s is a count.
1057 'quote' => _nx_noop( '%s quote', '%s quotes', 'Post format', 'friends' ),
1058 // translators: %s is a count.
1059 'status' => _nx_noop( '%s status', '%s statuses', 'Post format', 'friends' ),
1060 // translators: %s is a count.
1061 'video' => _nx_noop( '%s video', '%s videos', 'Post format', 'friends' ),
1062 // translators: %s is a count.
1063 'audio' => _nx_noop( '%s audio', '%s audios', 'Post format', 'friends' ),
1064 );
1065
1066 return sprintf( translate_nooped_plural( $plurals[ $format ], $count, 'friends' ), number_format_i18n( $count ) );
1067 }
1068
1069 /**
1070 * Overwrite the must_log_in message.
1071 *
1072 * @param array $defaults The default strings.
1073 *
1074 * @return array The modified defaults.
1075 */
1076 public function comment_form_defaults( $defaults ) {
1077 $comment_registration_message = get_option( 'friends_comment_registration_message', __( 'Only people in my network can comment.', 'friends' ) );
1078 $comment_registration_message = str_replace( __( 'my network', 'friends' ), '<a href="' . esc_attr( home_url() . '/friends/' ) . '">' . __( 'my network', 'friends' ) . '</a>', $comment_registration_message );
1079 $defaults['must_log_in'] = $comment_registration_message;
1080 return $defaults;
1081 }
1082
1083 /**
1084 * Get all of the rel links for the HTML head.
1085 */
1086 public static function get_link_rels() {
1087 $rest_prefix = get_rest_url() . REST::PREFIX;
1088 $links = array(
1089 array(
1090 'rel' => 'friends-base-url',
1091 'href' => $rest_prefix,
1092 ),
1093 );
1094
1095 if ( get_option( 'friends_expose_post_format_feeds' ) && current_theme_supports( 'post-formats' ) ) {
1096 $links = array_merge( $links, self::get_html_link_rel_alternate_post_formats() );
1097 }
1098
1099 return $links;
1100 }
1101
1102 /**
1103 * Strip non-ascii bytes.
1104 *
1105 * @param string $text The text.
1106 *
1107 * @return string The text in ASCII only.
1108 */
1109 private static function strip_non_ascii( $text ) {
1110 $text = html_entity_decode( $text );
1111 $text = str_replace( '»', '>', $text );
1112 $text = strtr( $text, '"', '' );
1113 return filter_var( $text, FILTER_DEFAULT, FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_NO_ENCODE_QUOTES );
1114 }
1115
1116 /**
1117 * Get all of the rel links for the HTTP header.
1118 */
1119 public static function http_header() {
1120 foreach ( self::get_link_rels() as $link ) {
1121 $header = 'Link: <' . esc_url( $link['href'] ) . '>; rel="' . esc_attr( $link['rel'] ) . '"';
1122 if ( isset( $link['type'] ) ) {
1123 $header .= '; type="' . self::strip_non_ascii( $link['type'] ) . '"';
1124 }
1125 if ( isset( $link['title'] ) ) {
1126 $header .= '; title="' . self::strip_non_ascii( $link['title'] ) . '"';
1127 }
1128 header( $header, false );
1129 }
1130 }
1131
1132 /**
1133 * Get all of the rel links for the HTML head.
1134 */
1135 public static function get_html_rel_links() {
1136 return array_map(
1137 function ( $link ) {
1138 return '<link' .
1139 ' rel="' . esc_attr( $link['rel'] ) . '"' .
1140 ' href="' . esc_url( $link['href'] ) . '"' .
1141 ( isset( $link['type'] ) ? ( ' type="' . esc_attr( $link['type'] ) . '"' ) : '' ) .
1142 ( isset( $link['title'] ) ? ( ' title="' . esc_attr( $link['title'] ) . '"' ) : '' ) .
1143 ' />';
1144 },
1145 self::get_link_rels()
1146 );
1147 }
1148
1149 /**
1150 * Output all of the rel links for the HTML head.
1151 */
1152 public static function html_rel_links() {
1153 echo wp_kses(
1154 implode( PHP_EOL, self::get_html_rel_links() ),
1155 array(
1156 'link' => array(
1157 'rel' => array(),
1158 'type' => array(),
1159 'href' => array(),
1160 'title' => array(),
1161 ),
1162 )
1163 ), PHP_EOL;
1164 }
1165
1166 /**
1167 * Generate link tag(s) with the alternate post formats.
1168 */
1169 public static function get_html_link_rel_alternate_post_formats() {
1170 $separator = _x( '&raquo;', 'feed link' ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1171 $blog_title = get_bloginfo( 'name' );
1172
1173 $links = array();
1174 foreach ( get_post_format_strings() as $format => $title ) {
1175 // translators: 1: Blog title, 2: Separator (raquo), 3: Post Format.
1176 $title = sprintf( __( '%1$s %2$s %3$s Feed', 'friends' ), $blog_title, $separator, $title );
1177 $links[] = array(
1178 'rel' => 'alternate',
1179 'type' => 'application/rss+xml',
1180 'href' => esc_url( home_url( '/type/' . $format . '/feed/' ) ),
1181 'title' => $title,
1182 );
1183 }
1184
1185 return $links;
1186 }
1187
1188 /**
1189 * Check whether this is a valid URL
1190 *
1191 * @param string $url The URL to check.
1192 * @return false|string URL or false on failure.
1193 */
1194 public static function check_url( $url ) {
1195 $pre = apply_filters( 'friends_pre_check_url', null );
1196 if ( ! is_null( $pre ) ) {
1197 return $pre;
1198 }
1199 $host = parse_url( $url, PHP_URL_HOST );
1200
1201 $check_url = apply_filters( 'friends_host_is_valid', null, $host );
1202 if ( ! is_null( $check_url ) ) {
1203 return $check_url;
1204 }
1205
1206 return wp_http_validate_url( $url );
1207 }
1208
1209 /**
1210 * Delete all the data the plugin has stored in WordPress
1211 */
1212 public static function uninstall_plugin() {
1213 $taxonomies = array(
1214 User_Feed::TAXONOMY,
1215 );
1216
1217 $affected_users = new \WP_User_Query( array( 'role__in' => array( 'friend', 'acquaintance', 'friend_request', 'pending_friend_request', 'subscription' ) ) );
1218 foreach ( $affected_users as $user ) {
1219 $in_token = get_user_option( 'friends_in_token', $user->ID );
1220 delete_option( 'friends_in_token_' . $in_token );
1221 delete_user_option( $user->ID, 'friends_out_token' );
1222 delete_user_option( $user->ID, 'friends_in_token' );
1223 delete_user_option( $user->ID, 'friends_new_friend' );
1224 $taxonomies[] = 'friend-reaction-' . $user->ID;
1225 }
1226
1227 delete_option( 'friends_main_user_id' );
1228 remove_role( 'friend' );
1229 remove_role( 'acquaintance' );
1230 remove_role( 'friend_request' );
1231 remove_role( 'pending_friend_request' );
1232 remove_role( 'subscription' );
1233
1234 $friend_posts = new \WP_Query(
1235 array(
1236 'post_type' => Friends::CPT,
1237 'post_status' => array( 'publish', 'private', 'trash' ),
1238 )
1239 );
1240
1241 while ( $friend_posts->have_posts() ) {
1242 $post = $friend_posts->next_post();
1243 wp_delete_post( $post->ID, true );
1244 }
1245
1246 // We have to resort to using direct database statements since the taxonomy is not registered because the plugin is deactivated.
1247 global $wpdb;
1248 foreach ( $taxonomies as $taxonomy ) {
1249 $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 ) );
1250
1251 if ( $terms ) {
1252 foreach ( $terms as $term ) {
1253 $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $term->term_taxonomy_id ) );
1254 $wpdb->delete( $wpdb->terms, array( 'term_id' => $term->term_id ) );
1255 delete_option( 'prefix_' . $term->slug . '_option_name' );
1256 }
1257 }
1258
1259 $wpdb->delete( $wpdb->term_taxonomy, array( 'taxonomy' => $taxonomy ), array( '%s' ) );
1260 }
1261 }
1262 }
1263