PluginProbe
Friends / trunk
Friends vtrunk
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 2.9.2 All 87 releases
friends / includes / class-admin.php

class-admin.php in Friends trunk, at includes/class-admin.php

3,418 lines 116.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Friends Admin
4 *
5 * This contains the functions for the admin section.
6 *
7 * @package Friends
8 */
9
10 namespace Friends;
11
12 /**
13 * This is the class for the Friends Plugin Admin section.
14 *
15 * @since 0.6
16 *
17 * @package Friends
18 * @author Alex Kirk
19 */
20 class Admin {
21 /**
22 * Contains a reference to the Friends class.
23 *
24 * @var Friends
25 */
26 private $friends;
27
28 /**
29 * Constructor
30 *
31 * @param Friends $friends A reference to the Friends object.
32 */
33 public function __construct( Friends $friends ) {
34 $this->friends = $friends;
35 $this->register_hooks();
36 }
37
38 /**
39 * Register the WordPress hooks
40 */
41 private function register_hooks() {
42 add_action( 'admin_menu', array( $this, 'admin_menu' ) );
43 add_filter( 'users_list_table_query_args', array( $this, 'allow_role_multi_select' ) );
44 add_filter( 'the_title', array( $this, 'override_post_format_title' ), 10, 2 );
45 add_filter( 'get_edit_user_link', array( $this, 'admin_edit_user_link' ), 10, 2 );
46 add_action( 'admin_bar_menu', array( $this, 'admin_bar_friends_menu' ), 39 );
47 add_action( 'admin_bar_menu', array( $this, 'admin_bar_new_content' ), 71 );
48 add_action( 'wp_head', array( $this, 'admin_bar_mobile' ) );
49 add_action( 'admin_head', array( $this, 'admin_bar_mobile' ) );
50 add_action( 'current_screen', array( $this, 'register_help' ) );
51 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ), 39 );
52 add_action( 'gettext_with_context', array( $this->friends, 'translate_user_role' ), 10, 4 );
53 add_action( 'wp_ajax_friends_preview_rules', array( $this, 'ajax_preview_friend_rules' ) );
54 add_action( 'wp_ajax_friends_fetch_feeds', array( $this, 'ajax_fetch_feeds' ) );
55 add_action( 'wp_ajax_friends_set_avatar', array( $this, 'ajax_set_avatar' ) );
56 add_action( 'wp_ajax_friends-refresh-feeds', array( $this, 'ajax_refresh_feeds' ) );
57 add_action( 'wp_ajax_friends-preview-subscription', array( $this, 'ajax_preview_subscription' ) );
58 add_action( 'wp_ajax_friends-preview-subscription-feed', array( $this, 'ajax_preview_subscription_feed' ) );
59 add_action( 'wp_ajax_friends-subscribe-frontend', array( $this, 'ajax_subscribe_frontend' ) );
60 add_action( 'delete_user_form', array( $this, 'delete_user_form' ), 10, 2 );
61 add_action( 'delete_user', array( $this, 'delete_user' ) );
62 add_action( 'remove_user_from_blog', array( $this, 'delete_user' ) );
63 add_action( 'tool_box', array( $this, 'toolbox_bookmarklets' ) );
64 add_action( 'dashboard_glance_items', array( $this, 'dashboard_glance_items' ) );
65 add_action( 'wp_dashboard_setup', array( $this, 'add_dashboard_widgets' ), 8 );
66 add_action( 'wp_ajax_friends_dashboard', array( $this, 'ajax_friends_dashboard' ) );
67 add_filter( 'site_status_test_php_modules', array( $this, 'site_status_test_php_modules' ) );
68 add_filter( 'friends_create_and_follow', array( $this, 'create_and_follow' ), 10, 4 );
69 add_action( 'friends_edit_feed_content_top', array( $this, 'maybe_render_activitypub_inactive_notice' ), 10, 3 );
70
71 if ( ! get_option( 'permalink_structure' ) ) {
72 add_action( 'admin_notices', array( $this, 'admin_notice_unsupported_permalink_structure' ) );
73 }
74 if ( get_option( 'friends_welcome_version' ) ) {
75 add_action( 'admin_notices', array( $this, 'admin_notice_welcome' ) );
76 }
77 add_filter( 'pre_get_posts', array( $this, 'admin_friend_posts_query' ) );
78 }
79
80 /**
81 * Display admin notice about an unsupported permalink structure
82 */
83 public function admin_notice_unsupported_permalink_structure() {
84 $screen = get_current_screen();
85
86 if ( 'plugins' !== $screen->id ) {
87 return;
88 }
89
90 ?>
91 <div class="friends-notice notice notice-error">
92 <p style="max-width:800px;"><b><?php esc_html_e( 'Friends', 'friends' ); ?></b><?php esc_html_e( '&#151; You are running an unsupported permalink structure.', 'friends' ); ?></p>
93 <p style="max-width:800px;">
94 <?php
95 echo wp_kses_post(
96 sprintf(
97 // translators: 1: URL to permalink settings, 2: the name of the Permalink Settings page.
98 __( 'In order to be able to view the Friends page, you need to enable a custom permalink structure. Please go to <a href="%1$s">%2$s</a> and enable an option other than Plain.', 'friends' ),
99 admin_url( 'options-permalink.php' ),
100 __( 'Permalink Settings' ) // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
101 )
102 );
103 ?>
104 </p>
105 </div>
106 <?php
107 }
108
109 /**
110 * Registers the admin menus
111 */
112 public function admin_menu() {
113 if ( isset( $_REQUEST['rerun-activate'] ) && isset( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'friends-settings' ) ) {
114 Friends::activate_plugin();
115 wp_safe_redirect( add_query_arg( array( 'reran-activation' => 'friends' ), wp_get_referer() ) );
116 exit;
117 }
118 $required_role = Friends::required_menu_role();
119 $unread_badge = $this->get_unread_badge();
120
121 $menu_title = __( 'Friends', 'friends' ) . $unread_badge;
122 $page_type = sanitize_title( $menu_title );
123 $current_page = isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : '';
124 add_menu_page( __( 'Friends', 'friends' ), $menu_title, $required_role, 'friends', null, 'dashicons-groups', 3 );
125 add_submenu_page( 'friends', __( 'Friends', 'friends' ), __( 'Home', 'friends' ), $required_role, 'friends', array( $this, 'render_admin_home' ) );
126 add_action( 'load-' . $page_type . '_page_friends-page', array( $this, 'redirect_to_friends_page' ) );
127 add_submenu_page( 'friends', __( 'Add Friend', 'friends' ), __( 'Add Friend', 'friends' ), $required_role, 'add-friend', array( $this, 'render_admin_add_friend' ) );
128 // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
129 add_submenu_page( 'friends', __( 'Settings' ), __( 'Settings' ), $required_role, 'friends-settings', array( $this, 'render_admin_settings' ) );
130 if (
131 in_array(
132 $current_page,
133 apply_filters( 'friends_admin_settings_slugs', array( 'friends-settings', 'friends-notification-manager', 'friends-wp-friendships', 'friends-import-export', 'friends-migrations' ) )
134 )
135 ) {
136 add_submenu_page( 'friends', __( 'Notifications', 'friends' ), '- ' . __( 'Notifications', 'friends' ), $required_role, 'friends-notification-manager', array( $this, 'render_admin_notification_manager' ) );
137 add_submenu_page( 'friends', __( 'Import/Export', 'friends' ), '- ' . __( 'Import/Export', 'friends' ), $required_role, 'friends-import-export', array( $this, 'render_admin_import_export' ) );
138 do_action( 'friends_admin_menu_settings', $page_type );
139 }
140
141 if ( 'friends-migrations' === $current_page && current_user_can( 'manage_options' ) ) {
142 add_submenu_page( 'friends', __( 'Migrations', 'friends' ), __( 'Migrations', 'friends' ), 'manage_options', 'friends-migrations', array( Migration::class, 'render_admin_page' ) );
143 }
144 add_action( 'load-' . $page_type . '_page_friends-notification-manager', array( $this, 'process_admin_notification_manager' ) );
145 add_action( 'load-' . $page_type . '_page_friends-import-export', array( $this, 'process_admin_import_export' ) );
146 add_action( 'load-' . $page_type . '_page_friends-settings', array( $this, 'process_admin_settings' ) );
147
148 if (
149 isset( $_GET['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'friends-refresh' ) && 'friends-refresh' === $current_page
150 ) {
151 add_submenu_page( 'friends', __( 'Refresh', 'friends' ), __( 'Refresh', 'friends' ), $required_role, 'friends-refresh', array( $this, 'admin_refresh_friend_posts' ) );
152 }
153
154 $friend_submenu_items = array(
155 'edit-friend' => __( 'Edit User', 'friends' ),
156 'edit-friend-feeds' => __( 'Edit Feeds', 'friends' ),
157 'edit-friend-notifications' => __( 'Edit Notifications', 'friends' ),
158 'edit-friend-rules' => __( 'Edit Rules', 'friends' ),
159 'duplicate-remover' => __( 'Duplicates', 'friends' ),
160 );
161 if ( isset( $friend_submenu_items[ $current_page ] ) ) {
162 foreach ( $friend_submenu_items as $slug => $title ) {
163 $user_param = '';
164 if ( isset( $_GET['user'] ) ) {
165 $username = sanitize_user( wp_unslash( $_GET['user'] ) );
166 $user_param = '&user=' . $username . '&_wpnonce=' . wp_create_nonce( $slug . '-' . $username );
167 }
168 $slug_ = strtr( $slug, '-', '_' );
169
170 add_submenu_page(
171 'friends',
172 $title,
173 $title,
174 $required_role,
175 $slug . ( $slug === $current_page ? '' : $user_param ),
176 array( $this, 'render_admin_' . $slug_ )
177 );
178
179 add_action(
180 'load-' . $page_type . '_page_' . $slug,
181 array( $this, 'process_admin_' . $slug_ )
182 );
183 }
184 }
185
186 if ( isset( $_GET['page'] ) && 'friends-logs' === $_GET['page'] ) {
187 // translators: as in log file.
188 $title = __( 'Log', 'friends' );
189 add_submenu_page( 'friends', $title, $title, $required_role, 'friends-logs', array( $this, 'render_friends_logs' ) );
190 }
191
192 $title = __( 'Browser Extension', 'friends' );
193 add_submenu_page( 'friends', $title, $title, $required_role, 'friends-browser-extension', array( $this, 'render_browser_extension' ) );
194
195 if ( isset( $_GET['page'] ) && 'unfriend' === $_GET['page'] ) {
196 $user = new User( intval( $_GET['user'] ) );
197 if ( $user ) {
198 $title = /* translators: %s is a username. */ sprintf( __( 'Unfriend %s', 'friends' ), $user->user_login );
199 add_submenu_page( 'friends', $title, $title, $required_role, 'unfriend', array( $this, 'render_admin_unfriend' ) );
200 add_action( 'load-' . $page_type . '_page_unfriend', array( $this, 'process_admin_unfriend' ) );
201 }
202 }
203 }
204
205 /**
206 * Allow making use of the role__in query.
207 *
208 * @param array $args The arguments.
209 *
210 * @return array The modified array.
211 */
212 public function allow_role_multi_select( $args ) {
213 if ( isset( $args['role'] ) && ! isset( $args['role__in'] ) ) {
214 if ( false !== strpos( $args['role'], ',' ) ) {
215 $args['role__in'] = explode( ',', $args['role'] );
216 unset( $args['role'] );
217 }
218
219 $roles = self::get_associated_roles();
220 if (
221 ( isset( $args['role__in'] ) && array_intersect( $args['role__in'], array_keys( $roles ) ) )
222 || ( isset( $args['role'] ) && isset( $roles[ $args['role'] ] ) )
223 ) {
224 add_action( 'admin_head-users.php', array( $this, 'keep_friends_open_on_users_screen' ) );
225 }
226 }
227 return $args;
228 }
229
230 /**
231 * Use JavaScript to keep the Friends menu open when responding to a Friend Request.
232 */
233 public function keep_friends_open_on_users_screen() {
234 ?>
235 <script type="text/javascript">
236 jQuery( document ).ready( function ( $ ) {
237 $( '#toplevel_page_friends-settings, #toplevel_page_friends-settings > a' ).addClass( 'wp-has-current-submenu wp-menu-open' ).removeClass( 'wp-not-current-submenu' );
238 $( '#menu-users > a' ).removeClass( 'wp-has-current-submenu wp-menu-open' );
239 $( "#toplevel_page_friends-settings ul li a[href='<?php echo esc_html( self::get_users_url() ); ?>']" ).closest( 'li' ).addClass( 'current' );
240 } );
241 </script>
242 <?php
243 }
244
245 /**
246 * Add our help information
247 *
248 * @param \WP_Screen $screen The current wp-admin screen.
249 */
250 public function register_help( $screen ) {
251 if ( ! ( $screen instanceof \WP_Screen ) ) {
252 return;
253 }
254
255 switch ( $screen->id ) {
256 case 'toplevel_page_friends-settings':
257 $screen->add_help_tab(
258 array(
259 'id' => 'overview',
260 'title' => __( 'Overview', 'friends' ),
261 'content' =>
262 '<p>' .
263 __( 'Welcome to the Friends Settings! You can configure the Friends plugin here to your liking.', 'friends' ) .
264 '</p>' .
265 '<p>' .
266 sprintf(
267 // translators: %1$s is a URL, %2$s is the name of a wp-admin screen.
268 __( 'There are more settings available for each friend or subscription individually. To get there, click on the user on the <a href=%1$s>%2$s</a> screen.', 'friends' ),
269 '"' . esc_attr( self_admin_url( self::get_users_url() ) ) . '"',
270 __( 'Friends &amp; Requests', 'friends' )
271 ) .
272 '</p>',
273 )
274 );
275 break;
276 case 'users':
277 $screen->add_help_tab(
278 array(
279 'id' => 'friends',
280 'title' => __( 'Friends', 'friends' ),
281 'content' => '<p>' . __( 'Here you can find your friends and subscriptions.', 'friends' ) . '</p><p>' . __( 'If you no longer want to be friends with someone or stop a subscription, you can simply delete that user.', 'friends' ) . '</p>',
282 )
283 );
284 break;
285 }
286 }
287
288 /**
289 * Reference our script for the /friends page
290 */
291 public function admin_enqueue_scripts() {
292 $handle = 'friends-admin';
293 $file = 'friends-admin.js';
294 $version = Friends::VERSION;
295 wp_enqueue_script( $handle, plugins_url( $file, FRIENDS_PLUGIN_FILE ), array( 'jquery' ), apply_filters( 'friends_debug_enqueue', $version, $handle, dirname( FRIENDS_PLUGIN_FILE ) . '/' . $file ), true );
296
297 $variables = array(
298 'ajax_url' => admin_url( 'admin-ajax.php' ),
299 'add_friend_url' => self_admin_url( 'admin.php?page=add-friend' ),
300 'add_friend_text' => __( 'Add a Friend', 'friends' ),
301 'copy_text' => __( 'Copy', 'friends' ),
302 'copied_text' => __( 'Copied!', 'friends' ),
303 'delete_feed_question' => __( 'Delete the feed? You need to click "Save Changes" to really delete it.', 'friends' ),
304 'role_subscription' => __( 'Following', 'friends' ),
305 'role_connection' => __( 'Connection', 'friends' ),
306 'role_contact' => __( 'Contact', 'friends' ),
307 'role_connection_request' => __( 'Connection Request', 'friends' ),
308 'role_pending_connection_request' => __( 'Pending Connection Request', 'friends' ),
309 'role_following' => __( 'Following', 'friends' ),
310 );
311 wp_localize_script( 'friends-admin', 'friends', $variables );
312
313 $handle = 'friends-admin';
314 $file = 'friends-admin.css';
315 $version = Friends::VERSION;
316 wp_enqueue_style( $handle, plugins_url( $file, FRIENDS_PLUGIN_FILE ), array(), apply_filters( 'friends_debug_enqueue', $version, $handle, dirname( FRIENDS_PLUGIN_FILE ) . '/' . $file ) );
317 }
318
319 /**
320 * Admin menu to refresh the friend posts.
321 */
322 public function admin_refresh_friend_posts() {
323 ?>
324 <h1><?php esc_html_e( "Refreshing Your Friends' Posts", 'friends' ); ?></h1>
325 <?php
326
327 add_filter( 'notify_about_new_friend_post', '__return_false', 999 );
328
329 add_filter(
330 'friends_friend_private_feed_url',
331 function ( $feed_url, $friend_user ) {
332 echo wp_kses(
333 // translators: %1s is the name of the friend, %2$s is the feed URL.
334 sprintf( __( 'Refreshing %1$s at %2$s', 'friends' ) . '<br/>', '<a href="' . esc_url( $friend_user->get_local_friends_page_url() ) . '">' . esc_html( $friend_user->user_login ) . '</a>', '<a href="' . esc_url( $feed_url ) . '">' . esc_html( $feed_url ) . '</a>' ),
335 array(
336 'a' => array(
337 'href' => array(),
338 ),
339 )
340 );
341 return $feed_url;
342 },
343 10,
344 2
345 );
346
347 add_action(
348 'friends_retrieved_new_posts',
349 function ( $user_feed, $new_posts, $modified_posts ) {
350 // translators: %s is the number of new posts found.
351 echo esc_html( sprintf( _n( 'Found %d new post.', 'Found %d new posts.', count( $new_posts ), 'friends' ), count( $new_posts ) ) );
352 ?>
353 <br />
354 <?php
355 // translators: %s is the number of modified posts.
356 echo esc_html( sprintf( _n( '%d post was modified.', '%d posts were modified.', count( $modified_posts ), 'friends' ), count( $modified_posts ) ) );
357 ?>
358 <br />
359 <?php
360 },
361 10,
362 3
363 );
364
365 add_action(
366 'friends_incoming_feed_items',
367 function ( $items ) {
368 // translators: %s is the number of posts found.
369 echo esc_html( sprintf( _n( 'Found %d item in the feed.', 'Found %d items in the feed.', count( $items ), 'friends' ) . ' ', count( $items ) ) );
370 }
371 );
372
373 add_action(
374 'friends_retrieve_friends_error',
375 function ( $user_feed, $error ) {
376 esc_html_e( 'An error occurred while retrieving the posts.', 'friends' );
377 echo esc_html( $error->get_error_message() ), '<br/>';
378 },
379 10,
380 2
381 );
382
383 if ( isset( $_GET['user'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
384 $friend_user = User::get_by_username( sanitize_user( wp_unslash( $_GET['user'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification
385 if ( ! $friend_user || is_wp_error( $friend_user ) || ! $friend_user->can_refresh_feeds() ) {
386 wp_die( esc_html__( 'Invalid user ID.' ) ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
387 }
388 $friend_user->retrieve_posts_from_active_feeds();
389 } else {
390 $this->friends->feed->retrieve_friend_posts();
391 }
392 }
393
394 /**
395 * Don't show the edit link for friend posts
396 *
397 * @param string $link The edit link.
398 * @param int|User $user The user.
399 * @return string|bool The edit link or false.
400 */
401 public static function admin_edit_user_link( $link, $user ) {
402 static $cache = array();
403 if ( $user instanceof \WP_User ) {
404 $cache_key = $user->ID;
405 } else {
406 $cache_key = $user;
407 }
408
409 if ( isset( $cache[ $cache_key ] ) ) {
410 if ( false === $cache[ $cache_key ] ) {
411 return $link;
412 }
413 return $cache[ $cache_key ];
414 }
415 if ( ! $user instanceof \WP_User ) {
416 if ( is_string( $user ) ) {
417 $user = User::get_by_username( $user );
418 } else {
419 $user = new \WP_User( $user );
420 }
421 }
422
423 if ( ! $user || is_wp_error( $user ) ) {
424 $cache[ $cache_key ] = false;
425 return $link;
426 }
427
428 if ( is_multisite() && is_super_admin( $user->ID ) ) {
429 $cache[ $cache_key ] = false;
430 return $link;
431 }
432 if ( ! $user->has_cap( 'friends_plugin' ) ) {
433 $cache[ $cache_key ] = false;
434 return $link;
435 }
436
437 $cache[ $cache_key ] = self_admin_url( 'admin.php?page=edit-friend&user=' . $user->user_login );
438 return $cache[ $cache_key ];
439 }
440
441 public static function get_edit_friend_link( $user ) {
442 if ( is_string( $user ) ) {
443 $user = User::get_by_username( $user );
444 } elseif ( ! $user instanceof User && ! $user instanceof Subscription ) {
445 $user = new User( $user );
446 }
447
448 if ( ! $user || is_wp_error( $user ) ) {
449 return '';
450 }
451
452 return apply_filters( 'get_edit_user_link', $user->user_url, $user->user_login );
453 }
454
455 public static function get_unfriend_link( $user ) {
456 if ( ! $user->has_cap( 'friends_plugin' ) ) {
457 return '';
458 }
459
460 return wp_nonce_url( self_admin_url( 'admin.php?page=unfriend&user=' . $user->user_login ), 'unfriend_' . $user->user_login );
461 }
462
463 /**
464 * Redirect to the Friends page
465 */
466 public function redirect_to_friends_page() {
467 wp_safe_redirect( home_url( '/friends/' ) );
468 exit;
469 }
470
471 /**
472 * Check access for the Friends Admin settings page
473 */
474 public function check_admin_settings() {
475 if ( ! Friends::has_required_privileges() ) {
476 wp_die( esc_html__( 'Sorry, you are not allowed to change the settings.', 'friends' ) );
477 }
478 }
479
480 /**
481 * Process the Friends Admin settings page
482 */
483 public function process_admin_settings() {
484 if ( empty( $_REQUEST ) || ! isset( $_REQUEST['_wpnonce'] ) ) {
485 return;
486 }
487
488 if ( ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'friends-settings' ) ) {
489 return;
490 }
491
492 $this->check_admin_settings();
493 foreach ( array( 'disable_auto_tagging', 'disable_link_previews' ) as $checkbox ) {
494 if ( isset( $_POST[ $checkbox ] ) && boolval( $_POST[ $checkbox ] ) ) {
495 update_option( 'friends_' . $checkbox, true );
496 } else {
497 delete_option( 'friends_' . $checkbox );
498 }
499 }
500
501 if ( current_user_can( 'manage_options' ) ) {
502 foreach ( array( 'force_enable_post_formats', 'expose_post_format_feeds', 'exclude_compose_format_from_feed' ) as $checkbox ) {
503 if ( isset( $_POST[ $checkbox ] ) && boolval( $_POST[ $checkbox ] ) ) {
504 update_option( 'friends_' . $checkbox, true );
505 } else {
506 delete_option( 'friends_' . $checkbox );
507 }
508 }
509
510 $post_format_slugs = get_post_format_slugs();
511 if ( isset( $_POST['friends_compose_post_format'] ) && in_array( sanitize_key( $_POST['friends_compose_post_format'] ), array_merge( array( 'standard' ), $post_format_slugs ), true ) ) {
512 update_option( 'friends_compose_post_format', sanitize_key( $_POST['friends_compose_post_format'] ) );
513 } else {
514 delete_option( 'friends_compose_post_format' );
515 }
516 }
517
518 if ( isset( $_POST['available_emojis'] ) && is_array( $_POST['available_emojis'] ) ) {
519 $available_emojis = array();
520 foreach ( wp_unslash( $_POST['available_emojis'] ) as $id ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
521 $id = sanitize_key( $id );
522 $data = Reactions::get_emoji_data( $id );
523 if ( $data ) {
524 $available_emojis[ $id ] = $data;
525 }
526 }
527 update_option( 'friends_selected_emojis', $available_emojis );
528 } else {
529 delete_option( 'friends_selected_emojis' );
530 }
531
532 // Global retention.
533 $retention_number_enabled = boolval( isset( $_POST['friends_enable_retention_number'] ) && $_POST['friends_enable_retention_number'] );
534 update_option( 'friends_enable_retention_number', $retention_number_enabled );
535 if ( $retention_number_enabled && isset( $_POST['friends_retention_number'] ) ) {
536 update_option( 'friends_retention_number', max( 1, intval( $_POST['friends_retention_number'] ) ) );
537 }
538 $retention_days_enabled = boolval( isset( $_POST['friends_enable_retention_days'] ) && $_POST['friends_enable_retention_days'] );
539 update_option( 'friends_enable_retention_days', $retention_days_enabled );
540 if ( $retention_days_enabled && isset( $_POST['friends_retention_days'] ) ) {
541 update_option( 'friends_retention_days', max( 1, intval( $_POST['friends_retention_days'] ) ) );
542 }
543
544 if ( isset( $_POST['retention_delete_reacted'] ) && 1 === intval( $_POST['retention_delete_reacted'] ) ) {
545 delete_option( 'friends_retention_delete_reacted' );
546 } else {
547 update_option( 'friends_retention_delete_reacted', true );
548 }
549
550 if ( isset( $_POST['frontend_default_view'] ) && in_array(
551 wp_unslash( $_POST['frontend_default_view'] ),
552 array(
553 'collapsed',
554 )
555 ) ) {
556 update_user_option( get_current_user_id(), 'friends_frontend_default_view', wp_unslash( $_POST['frontend_default_view'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
557 } else {
558 delete_user_option( get_current_user_id(), 'friends_frontend_default_view' );
559 }
560
561 foreach ( array_merge( array( '' ), get_post_format_slugs() ) as $post_type ) {
562 $name = 'friends_frontend_theme';
563 if ( $post_type ) {
564 $name = 'friends_frontend_theme_' . $post_type;
565 }
566 $theme = 'default';
567 if ( isset( $_POST[ $name ] ) && in_array( $theme, array_keys( Frontend::get_themes() ) ) ) {
568 $theme = wp_unslash( $_POST[ $name ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
569 }
570 if ( 'default' === $theme ) {
571 delete_user_option( get_current_user_id(), $name );
572 } else {
573 update_user_option( get_current_user_id(), $name, $theme );
574 }
575 }
576
577 $redirect_args = array( 'updated' => '1' );
578
579 if ( isset( $_GET['_wp_http_referer'] ) ) {
580 wp_safe_redirect( wp_get_referer() );
581 } else {
582 wp_safe_redirect( add_query_arg( $redirect_args, remove_query_arg( array( '_wp_http_referer', '_wpnonce' ) ) ) );
583 }
584 exit;
585 }
586
587 /**
588 * Gets the frontend locale.
589 *
590 * @return string The frontend locale.
591 */
592 public function get_frontend_locale() {
593 $locale = get_option( 'WPLANG' );
594 return empty( $locale ) ? 'en_US' : $locale;
595 }
596
597 /**
598 * Get the registry of news entries, newest first.
599 *
600 * Each entry has: version, title, template, and optionally migration_version
601 * for entries that should show migration status.
602 *
603 * @return array
604 */
605 public static function get_news_entries() {
606 return apply_filters(
607 'friends_news_entries',
608 array(
609 array(
610 'version' => '4.3',
611 'title' => __( '4.3: Link Previews', 'friends' ),
612 'template' => 'admin/news-4-3',
613 ),
614 array(
615 'version' => '4.2',
616 'title' => __( '4.2: Direct Messages', 'friends' ),
617 'template' => 'admin/news-4-2',
618 ),
619 array(
620 'version' => '4.1',
621 'title' => __( '4.1: Add Friend Frontend, Twitter Theme & Browser Extension', 'friends' ),
622 'template' => 'admin/news-4-1',
623 ),
624 array(
625 'version' => '4.0',
626 'title' => __( '4.0: A Major Update', 'friends' ),
627 'template' => 'admin/welcome-4-0',
628 'migration_version' => '4.0.0',
629 ),
630 array(
631 'version' => '3.3',
632 'title' => __( '3.3: Styling Overhaul', 'friends' ),
633 'template' => 'admin/news-3-3',
634 ),
635 array(
636 'version' => '3.0',
637 'title' => __( '3.0: Followers & Notifications', 'friends' ),
638 'template' => 'admin/news-3-0',
639 ),
640 array(
641 'version' => '2.4',
642 'title' => __( '2.4: Mastodon Compatibility', 'friends' ),
643 'template' => 'admin/news-2-4',
644 ),
645 array(
646 'version' => '2.1',
647 'title' => __( '2.1: Frontend & Plugins', 'friends' ),
648 'template' => 'admin/news-2-1',
649 ),
650 array(
651 'version' => '2.0',
652 'title' => __( '2.0: Revisions & Site Health', 'friends' ),
653 'template' => 'admin/news-2-0',
654 ),
655 array(
656 'version' => '0',
657 'title' => __( 'Welcome to the Friends Plugin!', 'friends' ),
658 'template' => 'admin/welcome',
659 ),
660 )
661 );
662 }
663
664 /**
665 * Get migration statuses for a specific version.
666 *
667 * @param string $migration_version The version to filter migrations for.
668 * @return array With keys: statuses, all_complete, has_in_progress.
669 */
670 public static function get_migration_data( $migration_version ) {
671 $all_statuses = Migration::get_all_statuses();
672 $statuses = array();
673 $all_complete = true;
674 $has_in_progress = false;
675
676 foreach ( $all_statuses as $id => $status ) {
677 if ( $status['version'] !== $migration_version ) {
678 continue;
679 }
680 $statuses[ $id ] = $status;
681 if ( empty( $status['completed'] ) ) {
682 $all_complete = false;
683 }
684 if ( ! empty( $status['in_progress'] ) ) {
685 $has_in_progress = true;
686 }
687 }
688
689 return array(
690 'statuses' => $statuses,
691 'all_complete' => $all_complete,
692 'has_in_progress' => $has_in_progress,
693 );
694 }
695
696 /**
697 * Render the Friends Admin home page.
698 *
699 * Shows the welcome page for new users (no subscriptions),
700 * or a news/changelog view for existing users.
701 */
702 public function render_admin_home() {
703 // Dismiss the update notice permanently when visiting this page.
704 if ( get_option( 'friends_welcome_version' ) ) {
705 delete_option( 'friends_welcome_version' );
706 }
707
708 $friends_subscriptions = User_Query::all_associated_users();
709 $is_new_user = 0 === $friends_subscriptions->get_total();
710
711 wp_enqueue_script( 'plugin-install' );
712 add_thickbox();
713 wp_enqueue_script( 'updates' );
714
715 Friends::template_loader()->get_template_part(
716 'admin/settings-header',
717 null,
718 array(
719 'active' => 'friends',
720 )
721 );
722
723 $news_entries = self::get_news_entries();
724
725 if ( $is_new_user ) {
726 // New users: welcome entry first, rest after.
727 $news_entries = array_reverse( $news_entries );
728 }
729
730 Friends::template_loader()->get_template_part(
731 'admin/news',
732 null,
733 array(
734 'entries' => $news_entries,
735 )
736 );
737
738 Friends::template_loader()->get_template_part( 'admin/settings-footer' );
739 }
740
741 /**
742 * Process the response after adding a friend/subscription.
743 *
744 * @param User|\WP_Error $friend_user The friend user object.
745 * @param array $vars The form variables.
746 *
747 * @return bool Whether the operation was successful.
748 */
749 private function process_admin_add_friend_response( $friend_user, $vars ) {
750 if ( is_wp_error( $friend_user ) ) {
751 $this->display_errors( $friend_user );
752 return false;
753 }
754
755 if ( ! $friend_user instanceof User ) {
756 ?>
757 <div id="message" class="updated notice is-dismissible"><p>
758 <?php esc_html_e( 'Unknown error', 'friends' ); ?>
759 </p></div>
760 <?php
761 return false;
762 }
763
764 $feed_options = array();
765 if ( ! isset( $vars['feeds'] ) ) {
766 $vars['feeds'] = array();
767 }
768 foreach ( $vars['feeds'] as $feed ) {
769 if ( isset( $feed['type'] ) ) {
770 $feed['mime-type'] = $feed['type'];
771 unset( $feed['type'] );
772 }
773 $feed_options[ $feed['url'] ] = $feed;
774 }
775
776 $friend_user->save_feeds( $feed_options );
777
778 if ( ! isset( $vars['subscribe'] ) ) {
779 $vars['subscribe'] = array();
780 }
781
782 $count = 0;
783 foreach ( $vars['subscribe'] as $feed_url ) {
784 if ( ! isset( $feed_options[ $feed_url ] ) ) {
785 continue;
786 }
787 $new_feed = $friend_user->subscribe( $feed_url, $feed_options[ $feed_url ] );
788 if ( ! is_wp_error( $new_feed ) ) {
789 do_action( 'friends_user_feed_activated', $new_feed );
790 ++$count;
791 }
792 }
793
794 add_filter( 'notify_about_new_friend_post', '__return_false', 999 );
795 wp_schedule_single_event( time(), 'friends_retrieve_user_feeds', array( $friend_user->ID ) );
796
797 $friend_link = '<a href="' . esc_url( $this->admin_edit_user_link( $friend_user->get_local_friends_page_url(), $friend_user ) ) . '" target="_blank" rel="noopener noreferrer">' . esc_html( $friend_user->display_name ) . '</a>';
798
799 // translators: %s is a Site URL.
800 $message = sprintf( __( "You're now subscribed to %s.", 'friends' ), $friend_link );
801
802 ?>
803 <div id="message" class="updated notice is-dismissible"><p>
804 <?php
805 echo wp_kses( $message, array( 'a' => array( 'href' => array() ) ) );
806 // translators: %s is the friends page URL.
807 echo ' ', wp_kses( sprintf( __( 'Go to your <a href=%s>friends page</a> to view their posts.', 'friends' ), '"' . esc_url( $friend_user->get_local_friends_page_url() ) . '"' ), array( 'a' => array( 'href' => array() ) ) );
808 echo ' <span id="fetch-feeds" data-nonce="', esc_attr( wp_create_nonce( 'fetch-feeds-' . sanitize_user( $friend_user->user_login ) ) ), '" data-friend=', esc_attr( $friend_user->user_login ), '>', esc_html__( 'Fetching feeds...', 'friends' ), '</span>';
809 ?>
810 </p></div>
811 <?php
812 return true;
813 }
814
815 /**
816 * Process the Add Friend form.
817 *
818 * @param array $vars The POST or GET variables.
819 *
820 * @return \WP_Error|null|bool A \WP_Error, null, or true on success.
821 */
822 public function process_admin_add_friend( $vars ) {
823 $errors = new \WP_Error();
824
825 $friend_url = isset( $vars['friend_url'] ) ? trim( $vars['friend_url'] ) : '';
826
827 $friend_user = false;
828
829 $protocol = wp_parse_url( $friend_url, PHP_URL_SCHEME );
830 if ( ! $protocol ) {
831 if ( is_multisite() ) {
832 $friend_user = get_user_by( 'login', $friend_url );
833 if ( $friend_user ) {
834 $site = get_active_blog_for_user( $friend_user->ID );
835 $friend_url = set_url_scheme( $site->siteurl );
836 }
837 }
838
839 if ( ! $friend_user ) {
840 $friend_url = apply_filters( 'friends_rewrite_incoming_url', 'https://' . $friend_url, $friend_url );
841 }
842 }
843 $friend_user_login = apply_filters( 'friends_suggest_user_login', User::get_user_login_for_url( $friend_url ), $friend_url );
844 $friend_display_name = apply_filters( 'friends_suggest_display_name', User::get_display_name_for_url( $friend_url ), $friend_url );
845
846 $friend_user = get_user_by( 'login', $friend_user_login );
847
848 $args = array();
849 if ( $friend_user ) {
850 $args['friends_multisite_user_login'] = $friend_user_login;
851 $args['friends_multisite_display_name'] = $friend_display_name;
852 }
853
854 if ( ( isset( $vars['step2'] ) && isset( $vars['feeds'] ) && is_array( $vars['feeds'] ) ) || isset( $vars['step3'] ) ) {
855 $friend_user_login = trim( str_replace( ' ', '-', sanitize_user( $vars['user_login'] ) ), '-' );
856 $friend_display_name = sanitize_text_field( $vars['display_name'] );
857 if ( ! $friend_user_login ) {
858 // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
859 $errors->add( 'user_login', __( '<strong>Error</strong>: This username is invalid because it uses illegal characters. Please enter a valid username.' ) );
860 } elseif ( ! is_multisite() && username_exists( $friend_user_login ) ) {
861 // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
862 $errors->add( 'user_login', __( '<strong>Error</strong>: This username is already registered. Please choose another one.' ) );
863 }
864
865 $feeds = $vars['feeds'];
866 if ( ! $errors->has_errors() ) {
867 $avatar = null;
868 $description = null;
869 foreach ( $feeds as $feed_details ) {
870 if ( ! $avatar && ! empty( $feed_details['avatar'] ) ) {
871 $avatar = $feed_details['avatar'];
872 }
873 if ( ! $description && ! empty( $feed_details['description'] ) ) {
874 $description = wp_encode_emoji( $feed_details['description'] );
875 }
876 }
877
878 $friend_user = User::create( $friend_user_login, 'subscription', $friend_url, $friend_display_name, $avatar, $description );
879
880 return $this->process_admin_add_friend_response( $friend_user, $vars );
881 }
882 } else {
883 if ( str_starts_with( $friend_url, home_url() ) ) {
884 return new \WP_Error( 'friend-yourself', __( 'It seems like you sent a friend request to yourself.', 'friends' ) );
885 }
886
887 if ( preg_match( '#https://.*?@threads.net#', $friend_url ) ) {
888 return new \WP_Error(
889 'threads-net',
890 sprintf(
891 // translators: %s is a URL.
892 __( '⚠️ This user has <a href="%s">not enabled Fediverse sharing on their Threads.net account</a>.', 'friends' ),
893 'https://about.fb.com/news/2023/07/introducing-threads-new-app-text-sharing/'
894 )
895 );
896 }
897
898 if ( ! Friends::check_url( $friend_url ) ) {
899 return new \WP_Error( 'invalid-url', __( 'You entered an invalid URL.', 'friends' ) );
900 }
901
902 $friend_user = User::get_user( $friend_user_login );
903 if ( $friend_user && ! is_wp_error( $friend_user ) ) {
904 // translators: %s is the name of a friend / site.
905 return new \WP_Error( 'already-subscribed', sprintf( __( 'You are already subscribed to this site: %s', 'friends' ), '<a href="' . esc_url( $this->admin_edit_user_link( $friend_user->get_local_friends_page_url(), $friend_user ) ) . '">' . esc_html( $friend_user->display_name ) . '</a>' ) );
906 }
907
908 $feeds = $this->friends->feed->discover_available_feeds( $friend_url );
909 if ( is_wp_error( $feeds ) ) {
910 return $feeds;
911 }
912 if ( ! $feeds ) {
913 return new \WP_Error( 'no-feed-found', __( 'No suitable feed was found at the provided address.', 'friends' ) );
914 }
915 $has_subscribable_feeds = false;
916 $has_threads_net = false;
917 foreach ( $feeds as $url => $feed ) {
918 if ( 0 === strpos( $url, 'https://threads.net/' ) ) {
919 $has_threads_net = true;
920 }
921 if ( isset( $feed['autoselect'] ) && $feed['autoselect'] ) {
922 $has_subscribable_feeds = true;
923 break;
924 }
925 if ( 'unsupported' !== $feed['parser'] ) {
926 $has_subscribable_feeds = true;
927 break;
928 }
929 }
930
931 if ( ! $has_subscribable_feeds && $has_threads_net ) {
932 $args['feeds_notice'] = sprintf(
933 // translators: %s is a URL.
934 __( '⚠️ This user has <a href="%s">not enabled Fediverse sharing on their Threads.net account</a>.', 'friends' ),
935 'https://about.fb.com/news/2023/07/introducing-threads-new-app-text-sharing/'
936 );
937 }
938
939 $better_user_login = User::get_user_login_from_feeds( $feeds );
940 if ( $better_user_login ) {
941 $friend_user_login = trim( $better_user_login, '-' );
942 }
943
944 $better_display_name = User::get_display_name_from_feeds( $feeds );
945 if ( $better_display_name ) {
946 $friend_display_name = $better_display_name;
947 if ( ! $better_user_login ) {
948 $friend_user_login = trim( strtolower( str_replace( ' ', '-', sanitize_user( $better_display_name ) ) ), '-' );
949 }
950 }
951 }
952
953 if ( isset( $vars['quick-subscribe'] ) ) {
954 $vars['feeds'] = $feeds;
955 $vars['subscribe'] = array();
956 foreach ( $feeds as $feed_url => $details ) {
957 if ( isset( $details['autoselect'] ) && $details['autoselect'] ) {
958 $vars['subscribe'][] = $feed_url;
959 }
960 }
961
962 $avatar = null;
963 $description = null;
964 foreach ( $feeds as $feed_details ) {
965 if ( ! $avatar && ! empty( $feed_details['avatar'] ) ) {
966 $avatar = $feed_details['avatar'];
967 }
968 if ( ! $description && ! empty( $feed_details['description'] ) ) {
969 $description = $feed_details['description'];
970 }
971 }
972
973 $friend_user = User::create( $friend_user_login, 'subscription', $friend_url, $friend_display_name, $avatar, $description );
974
975 return $this->process_admin_add_friend_response( $friend_user, $vars );
976 }
977
978 Friends::template_loader()->get_template_part(
979 'admin/settings-header',
980 null,
981 array(
982 'active' => 'add-friend-confirm',
983 'title' => __( 'Add Friend', 'friends' ),
984 'menu' => array(
985 '1. ' . __( 'Enter Details', 'friends' ) => array(
986 'page' => 'add-friend',
987 'url' => ! empty( $friend_url ) ? $friend_url : false,
988 ),
989 '2. ' . __( 'Confirm', 'friends' ) => 'add-friend-confirm',
990 ),
991 )
992 );
993
994 if ( $errors->has_errors() ) {
995 ?>
996 <div id="message" class="updated notice is-dismissible"><p><?php echo wp_kses( $errors->get_error_message(), array( 'strong' => array() ) ); ?></p>
997 </div>
998 <?php
999 }
1000
1001 Friends::template_loader()->get_template_part(
1002 'admin/select-feeds',
1003 null,
1004 array_merge(
1005 $args,
1006 array(
1007 'friend_url' => $friend_url,
1008 'friend_user_login' => $friend_user_login,
1009 'friend_display_name' => $friend_display_name,
1010 'post_formats' => array_merge( array( 'autodetect' => __( 'Autodetect Post Format', 'friends' ) ), get_post_format_strings() ),
1011 'registered_parsers' => $this->friends->feed->get_registered_parsers(),
1012 'feeds' => $feeds,
1013 )
1014 )
1015 );
1016 }
1017
1018 /**
1019 * Render the admin form for following someone.
1020 */
1021 public function render_admin_add_friend() {
1022 if ( ! Friends::has_required_privileges() ) {
1023 wp_die( esc_html__( 'Sorry, you are not allowed to do this.', 'friends' ) );
1024 }
1025
1026 if ( ! empty( $_GET['preview'] ) ) {
1027 $url = sanitize_text_field( wp_unslash( $_GET['preview'] ) );
1028
1029 ?>
1030 <h1>
1031 <?php
1032 // translators: %s is a URL.
1033 echo esc_html( sprintf( __( 'Preview for %s', 'friends' ), $url ) );
1034 ?>
1035 </h1>
1036 <?php
1037
1038 if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'preview-feed' ) ) {
1039 ?>
1040 <div id="message" class="updated notice is-dismissible"><p><?php esc_html_e( 'For security reasons, this preview is not available.', 'friends' ); ?></p>
1041 </div>
1042 <?php
1043 return;
1044 }
1045 $parser = false;
1046 if ( isset( $_GET['parser'] ) ) {
1047 $parser_name = $this->friends->feed->get_registered_parser( sanitize_text_field( wp_unslash( $_GET['parser'] ) ) );
1048 $parser = $this->friends->feed->get_feed_parser( sanitize_text_field( wp_unslash( $_GET['parser'] ) ) );
1049 }
1050 if ( ! $parser ) {
1051 ?>
1052 <div id="message" class="updated notice is-dismissible"><p><?php esc_html_e( 'An unknown parser name was supplied.', 'friends' ); ?></p>
1053 </div>
1054 <?php
1055 return;
1056 }
1057 ?>
1058 <h3><?php esc_html_e( 'Parser Details', 'friends' ); ?></h3>
1059 <ul id="parser">
1060 <li>
1061 <?php
1062 echo wp_kses(
1063 // translators: %s is the name of a parser, e.g. simplepie.
1064 sprintf( __( 'Parser: %s', 'friends' ), $parser_name ),
1065 array(
1066 'a' => array(
1067 'href' => array(),
1068 'rel' => array(),
1069 'target' => array(),
1070 ),
1071 )
1072 );
1073 ?>
1074 </li>
1075 </ul>
1076 <h3><?php esc_html_e( 'Items in the Feed', 'friends' ); ?></h3>
1077
1078 <?php
1079 $feed_id = null;
1080 if ( isset( $_GET['feed'] ) ) {
1081 $feed_id = intval( $_GET['feed'] );
1082 }
1083 $items = $this->friends->feed->preview( $parser, $url, $feed_id );
1084 if ( is_wp_error( $items ) ) {
1085 ?>
1086 <div id="message" class="updated notice is-dismissible"><p><?php echo esc_html( $items->get_error_message() ); ?></p>
1087 </div>
1088 <?php
1089 return;
1090 }
1091 ?>
1092
1093 <ul>
1094 <?php
1095 foreach ( $items as $item ) {
1096 $title = $item->title;
1097 if ( 'status' === $item->post_format ) {
1098 $title = wp_strip_all_tags( $item->content );
1099 }
1100 ?>
1101 <li>
1102 <?php if ( $title ) : ?>
1103 <details><summary>
1104 <?php endif; ?>
1105 <a href="<?php echo esc_url( $item->permalink ); ?>" target="_blank" rel="noopener noreferrer"><?php echo esc_html( $item->date ); ?></a> (author: <?php echo esc_html( $item->author ); ?>, type: <?php echo esc_html( $item->post_format ); ?>):
1106 <?php if ( $title ) : ?>
1107 <a href="<?php echo esc_url( $item->permalink ); ?>" target="_blank" rel="noopener noreferrer"><?php echo esc_html( $title ); ?></a> <?php echo esc_html( str_word_count( wp_strip_all_tags( $item->content ) ) ); ?> words</summary>
1108 <?php else : ?>
1109 <p>
1110 <?php endif; ?>
1111 <?php echo esc_textarea( $item->content ); ?>
1112 <?php if ( $title ) : ?>
1113 </details>
1114 <?php else : ?>
1115 </p>
1116 <?php endif; ?>
1117 </li>
1118 <?php
1119 }
1120 ?>
1121 </ul>
1122 <?php
1123 return;
1124 }
1125
1126 if ( apply_filters( 'friends_debug', false ) && isset( $_GET['next'] ) ) {
1127 $_POST = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1128 $_POST['_wpnonce'] = wp_create_nonce( 'add-friend' );
1129 if ( ! empty( $_POST['url'] ) && ! isset( $_POST['friend_url'] ) ) {
1130 $friend_url = sanitize_text_field( wp_unslash( $_POST['url'] ) );
1131 $parsed_url = wp_parse_url( $friend_url );
1132 if ( isset( $parsed_url['host'] ) ) {
1133 if ( ! isset( $parsed_url['scheme'] ) ) {
1134 $friend_url = 'https://' . ltrim( $friend_url, '/' );
1135 }
1136 }
1137 $_POST['friend_url'] = $friend_url;
1138 }
1139 }
1140
1141 $response = null;
1142 $postdata = apply_filters( 'friends_add_friend_postdata', $_POST );
1143 if ( ! empty( $postdata ) ) {
1144 if ( ! wp_verify_nonce( sanitize_key( $postdata['_wpnonce'] ), 'add-friend' ) ) {
1145 $response = new \WP_Error( 'invalid-nonce', __( 'For security reasons, please verify the URL and click next if you want to proceed.', 'friends' ) );
1146 } else {
1147 $response = $this->process_admin_add_friend( $postdata );
1148 }
1149 if ( is_wp_error( $response ) ) {
1150 ?>
1151 <div id="message" class="updated notice is-dismissible"><p>
1152 <?php
1153 $message = $response->get_error_message();
1154 if ( $response->get_error_data() ) {
1155 $message .= ' (' . $response->get_error_data() . ')';
1156 }
1157 echo wp_kses(
1158 $message,
1159 array(
1160 'strong' => array(),
1161 'a' => array(
1162 'href' => array(),
1163 'rel' => array(),
1164 'target' => array(),
1165 ),
1166 )
1167 );
1168 ?>
1169 </p>
1170 </div>
1171 <?php
1172 }
1173 if ( is_null( $response ) ) {
1174 return;
1175 }
1176 }
1177
1178 $args = array(
1179 'friend_url' => '',
1180 'add-friends-placeholder' => apply_filters( 'friends_add_friends_input_placeholder', __( 'Enter URL', 'friends' ) ),
1181 );
1182
1183 if ( ! empty( $_REQUEST['url'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1184 $friend_url = sanitize_text_field( wp_unslash( $_REQUEST['url'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1185 $parsed_url = wp_parse_url( $friend_url );
1186 if ( isset( $parsed_url['host'] ) ) {
1187 if ( ! isset( $parsed_url['scheme'] ) ) {
1188 $args['friend_url'] = apply_filters( 'friends_rewrite_incoming_url', 'https://' . ltrim( $friend_url, '/' ), $friend_url, $parsed_url );
1189 } else {
1190 $args['friend_url'] = $friend_url;
1191 }
1192 } elseif ( class_exists( 'Friends\Feed_Parser_ActivityPub' ) && preg_match( '/^@?' . Feed_Parser_ActivityPub::ACTIVITYPUB_USERNAME_REGEXP . '$/i', $friend_url ) ) {
1193 $args['friend_url'] = $friend_url;
1194 }
1195 }
1196
1197 Friends::template_loader()->get_template_part(
1198 'admin/settings-header',
1199 null,
1200 array(
1201 'active' => 'add-friend',
1202 'title' => __( 'Add Friend', 'friends' ),
1203 'menu' => array(
1204 '1. ' . __( 'Enter Details', 'friends' ) => array(
1205 'page' => 'add-friend',
1206 'url' => ! empty( $friend_url ) ? $friend_url : false,
1207 ),
1208 '2. ' . __( 'Confirm', 'friends' ) => false,
1209 ),
1210 )
1211 );
1212
1213 Friends::template_loader()->get_template_part( 'admin/add-friend', null, $args );
1214
1215 Friends::template_loader()->get_template_part(
1216 'admin/latest-friends',
1217 null,
1218 array(
1219 'friend_requests' => User_Query::recent_friends_subscriptions( 25 )->get_results(),
1220 )
1221 );
1222 Friends::template_loader()->get_template_part( 'admin/settings-footer', null, $args );
1223 }
1224
1225 /**
1226 * Display admin notice about a new version.
1227 */
1228 public function admin_notice_welcome() {
1229 if ( ! current_user_can( 'manage_options' ) ) {
1230 return;
1231 }
1232
1233 if ( isset( $_GET['page'] ) && 'friends' === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1234 return;
1235 }
1236
1237 $version = get_option( 'friends_welcome_version' );
1238 $url = admin_url( 'admin.php?page=friends' );
1239 ?>
1240 <div class="friends-notice notice notice-info">
1241 <p>
1242 <b><?php esc_html_e( 'Friends', 'friends' ); ?></b>
1243 <?php
1244 echo wp_kses(
1245 sprintf(
1246 // translators: %1$s is the version number, %2$s is a URL to the What's New page.
1247 __( '&#151; You have been updated to version %1$s! <a href="%2$s">See what\'s new and check the migration status</a>.', 'friends' ),
1248 esc_html( $version ),
1249 esc_url( $url )
1250 ),
1251 array( 'a' => array( 'href' => array() ) )
1252 );
1253 ?>
1254 </p>
1255 </div>
1256 <?php
1257 }
1258
1259 /**
1260 * Render the Friends Admin settings page
1261 */
1262 public function render_admin_settings() {
1263 Friends::template_loader()->get_template_part(
1264 'admin/settings-header',
1265 null,
1266 array(
1267 'active' => 'friends-settings',
1268 )
1269 );
1270 $this->check_admin_settings();
1271
1272 if ( isset( $_GET['updated'] ) && boolval( $_GET['updated'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
1273 ?>
1274 <div id="message" class="updated notice is-dismissible"><p>
1275 <?php
1276 esc_html_e( 'Your settings were updated.', 'friends' );
1277 ?>
1278 </p></div>
1279 <?php
1280 }
1281
1282 $post_stats = Friends::get_post_stats();
1283 $post_type_themes = array();
1284 foreach ( get_post_format_slugs() as $slug ) {
1285 $post_type_themes[ 'frontend_theme_' . $slug ] = get_user_option( 'friends_frontend_theme_' . $slug );
1286 }
1287
1288 Friends::template_loader()->get_template_part(
1289 'admin/settings',
1290 null,
1291 array_merge(
1292 Friends::get_post_stats(),
1293 $post_type_themes,
1294 array(
1295 'force_enable_post_formats' => get_option( 'friends_force_enable_post_formats' ),
1296 'post_format_strings' => get_post_format_strings(),
1297 'limit_homepage_post_format' => get_option( 'friends_limit_homepage_post_format', false ),
1298 'expose_post_format_feeds' => get_option( 'friends_expose_post_format_feeds' ),
1299 'compose_post_format' => get_option( 'friends_compose_post_format', 'status' ),
1300 'exclude_compose_format_from_feed' => get_option( 'friends_exclude_compose_format_from_feed' ),
1301 'disable_auto_tagging' => get_option( 'friends_disable_auto_tagging' ),
1302 'disable_link_previews' => get_option( 'friends_disable_link_previews' ),
1303 'retention_days' => Friends::get_retention_days(),
1304 'retention_number' => Friends::get_retention_number(),
1305 'retention_days_enabled' => get_option( 'friends_enable_retention_days' ),
1306 'retention_number_enabled' => get_option( 'friends_enable_retention_number' ),
1307 'retention_delete_reacted' => get_option( 'friends_retention_delete_reacted' ),
1308 'frontend_default_view' => get_user_option( 'friends_frontend_default_view', get_current_user_id() ),
1309 'frontend_theme' => get_user_option( 'friends_frontend_theme' ),
1310 )
1311 )
1312 );
1313
1314 Friends::template_loader()->get_template_part( 'admin/settings-footer' );
1315 }
1316
1317 /**
1318 * Process access for the Friends Edit Rules page
1319 */
1320 private function check_admin_edit_friend_rules() {
1321 if ( ! Friends::is_main_user() ) {
1322 wp_die( esc_html__( 'Sorry, you are not allowed to edit the rules.', 'friends' ) );
1323 }
1324
1325 if ( ! isset( $_GET['user'] ) ) {
1326 wp_die( esc_html__( 'Invalid user.', 'friends' ) );
1327 }
1328
1329 if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'edit-friend-rules-' . sanitize_user( wp_unslash( $_GET['user'] ) ) ) ) {
1330 wp_die( esc_html__( 'Invalid nonce.', 'friends' ) );
1331 }
1332
1333 $friend = User::get_by_username( sanitize_user( wp_unslash( $_GET['user'] ) ) );
1334 if ( ! $friend || is_wp_error( $friend ) ) {
1335 wp_die( esc_html__( 'Invalid username.', 'friends' ) );
1336 }
1337
1338 if ( ! $friend->has_cap( 'subscription' ) ) {
1339 wp_die( esc_html__( 'This is not a user related to this plugin.', 'friends' ) );
1340 }
1341
1342 return $friend;
1343 }
1344
1345 /**
1346 * Process the Friends Edit Rules page
1347 */
1348 public function process_admin_edit_friend_rules() {
1349 $friend = $this->check_admin_edit_friend_rules();
1350 $arg = 'updated';
1351 $arg_value = 1;
1352 if ( isset( $_POST['_wpnonce'] ) && ! empty( $_POST['friend-rules-raw'] ) && wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), 'friend-rules-raw-' . $friend->user_login ) ) {
1353 $rules = validate_feed_rules( wp_unslash( $_POST['friend-rules-raw'] ) );
1354 if ( false === $rules ) {
1355 $arg = 'error';
1356 } else {
1357 $friend->update_user_option( 'friends_feed_rules', $rules );
1358 }
1359 } elseif ( isset( $_POST['_wpnonce'] ) && ! empty( $_POST['rules'] ) && ! empty( $_POST['catch_all'] ) && wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), 'edit-friend-rules-' . sanitize_user( $friend->user_login ) ) ) {
1360 $friend->update_user_option(
1361 'friends_feed_catch_all',
1362 validate_feed_catch_all( wp_unslash( $_POST['catch_all'] ) )
1363 );
1364 $friend->update_user_option(
1365 'friends_feed_rules',
1366 validate_feed_rules( wp_unslash( $_POST['rules'] ) )
1367 );
1368 } else {
1369 return;
1370 }
1371
1372 if ( isset( $_GET['_wp_http_referer'] ) ) {
1373 wp_safe_redirect( wp_get_referer() );
1374 } else {
1375 wp_safe_redirect( add_query_arg( $arg, $arg_value, remove_query_arg( '_wp_http_referer' ) ) );
1376 }
1377 exit;
1378 }
1379
1380 /**
1381 * Render the Friends Edit Rules page
1382 */
1383 public function render_admin_edit_friend_rules() {
1384 $friend = $this->check_admin_edit_friend_rules();
1385 $catch_all = $friend->get_feed_catch_all();
1386 $rules = $friend->get_feed_rules();
1387
1388 $this->header_edit_friend( $friend, 'edit-friend-rules' );
1389
1390 if ( isset( $_GET['updated'] ) ) {
1391 ?>
1392 <div id="message" class="updated notice is-dismissible"><p><?php esc_html_e( 'Rules were updated.', 'friends' ); ?></p></div>
1393 <?php
1394 } elseif ( isset( $_GET['error'] ) ) {
1395 ?>
1396 <div id="message" class="updated error is-dismissible"><p><?php esc_html_e( 'An error occurred.', 'friends' ); ?></p></div>
1397 <?php
1398 }
1399
1400 $rules = array_values( $rules );
1401 $rules[] = array(
1402 'field' => 'title',
1403 'regex' => '',
1404 'action' => in_array( $catch_all, array( 'trash', 'delete' ), true ) ? 'accept' : 'trash',
1405 'replace' => '',
1406 );
1407
1408 if ( isset( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'edit-friend-rules-' . sanitize_user( $friend->user_login ) ) ) {
1409 if ( isset( $_GET['post'] ) && intval( $_GET['post'] ) ) {
1410 $post = get_post( intval( $_GET['post'] ) );
1411 } else {
1412 $post = null;
1413 }
1414 }
1415
1416 $args = array(
1417 'rules' => $rules,
1418 'friend' => $friend,
1419 'catch_all' => $catch_all,
1420 'post' => $post,
1421 );
1422 Friends::template_loader()->get_template_part( 'admin/edit-rules', null, $args );
1423
1424 echo '<div id="preview-rules">';
1425 $this->render_preview_friend_rules( $rules, $catch_all, $post );
1426 echo '</div>';
1427
1428 array_pop( $args['rules'] );
1429 Friends::template_loader()->get_template_part( 'admin/edit-raw-rules', null, $args );
1430 }
1431
1432 /**
1433 * Respond to the Ajax request to the Friend rules preview
1434 */
1435 public function ajax_preview_friend_rules() {
1436 if ( ! Friends::has_required_privileges() ) {
1437 wp_die( -1 );
1438 }
1439 if ( ! isset( $_GET['user'] ) ) {
1440 wp_die( esc_html__( 'Invalid user.', 'friends' ) );
1441 }
1442
1443 check_ajax_referer( 'edit-friend-rules-' . sanitize_user( wp_unslash( $_GET['user'] ) ) );
1444
1445 if ( isset( $_GET['post'] ) && intval( $_GET['post'] ) ) {
1446 $post = get_post( intval( $_GET['post'] ) );
1447 } else {
1448 $post = null;
1449 }
1450 $rules = array();
1451 if ( isset( $_POST['rules'] ) ) {
1452 $rules = validate_feed_rules( wp_unslash( $_POST['rules'] ) );
1453 }
1454 $catch_all = array();
1455 if ( isset( $_POST['catch_all'] ) ) {
1456 $catch_all = validate_feed_rules( wp_unslash( $_POST['catch_all'] ) );
1457 }
1458 $this->render_preview_friend_rules( $rules, $catch_all, $post );
1459 wp_die( 1 );
1460 }
1461
1462 /**
1463 * Respond to the Ajax request to fetch feeds
1464 */
1465 public function ajax_fetch_feeds() {
1466 if ( ! isset( $_POST['friend'] ) ) {
1467 wp_send_json_error( 'missing-parameters' );
1468 }
1469
1470 check_ajax_referer( 'fetch-feeds-' . sanitize_user( wp_unslash( $_POST['friend'] ) ) );
1471
1472 $friend_user = User::get_by_username( sanitize_user( wp_unslash( $_POST['friend'] ) ) );
1473 if ( ! $friend_user ) {
1474 wp_send_json_error( 'unknown-user' );
1475 }
1476
1477 add_filter( 'notify_about_new_friend_post', '__return_false', 999 );
1478
1479 $friend_user->retrieve_posts_from_active_feeds();
1480
1481 wp_send_json_success();
1482 }
1483
1484 /**
1485 * Render the Friend rules preview
1486 *
1487 * @param array $rules The rules to apply.
1488 * @param string $catch_all The catch all behavior.
1489 * @param \WP_Post $post The post.
1490 */
1491 public function render_preview_friend_rules( $rules, $catch_all, ?\WP_Post $post = null ) {
1492 $friend = $this->check_admin_edit_friend_rules();
1493 $friend_posts = new \WP_Query();
1494
1495 $friend_posts->set( 'post_type', Friends::CPT );
1496 $friend_posts->set( 'post_status', array( 'publish', 'private', 'trash' ) );
1497 $friend_posts->set( 'posts_per_page', 25 );
1498 $friend_posts = $friend->modify_query_by_author( $friend_posts );
1499
1500 $args = array(
1501 'friend' => $friend,
1502 'friend_posts' => $friend_posts,
1503 'feed' => $this->friends->feed,
1504 'post' => $post,
1505 );
1506
1507 $friend->set_feed_rules( $rules );
1508 $friend->set_feed_catch_all( $catch_all );
1509
1510 Friends::template_loader()->get_template_part( 'admin/preview-rules', null, $args );
1511 }
1512
1513 /**
1514 * Process access for the Friends Edit User page
1515 */
1516 private function check_admin_edit_friend() {
1517 if ( ! friends::has_required_privileges() ) {
1518 wp_die( esc_html__( 'Sorry, you are not allowed to edit this user.' ) ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1519 }
1520
1521 if ( ! isset( $_GET['user'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
1522 wp_die( esc_html__( 'Invalid user.', 'friends' ) );
1523 }
1524
1525 $friend = User::get_by_username( sanitize_user( wp_unslash( $_GET['user'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification
1526 if ( ! $friend || is_wp_error( $friend ) ) {
1527 wp_die( esc_html__( 'Invalid username.', 'friends' ) );
1528 }
1529
1530 if ( ! $friend->has_cap( 'friends_plugin' ) ) {
1531 wp_die( esc_html__( 'This is not a user related to this plugin.', 'friends' ) );
1532 }
1533
1534 return $friend;
1535 }
1536
1537 /**
1538 * Process the Friends Edit User page
1539 */
1540 public function process_admin_edit_friend() {
1541 $friend = $this->check_admin_edit_friend();
1542 $arg = 'updated';
1543 $arg_value = 1;
1544
1545 if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), 'edit-friend-' . $friend->user_login ) ) {
1546 if ( isset( $_POST['friends_display_name'] ) ) {
1547 $friends_display_name = trim( sanitize_text_field( wp_unslash( $_POST['friends_display_name'] ) ) );
1548 if ( $friends_display_name ) {
1549 $friend->first_name = $friends_display_name;
1550 $friend->display_name = $friends_display_name;
1551 }
1552 }
1553 if ( isset( $_POST['friends_description'] ) ) {
1554 $friend->description = trim( sanitize_text_field( wp_unslash( $_POST['friends_description'] ) ) );
1555 }
1556 if ( isset( $_POST['user_url'] ) ) {
1557 $user_url = sanitize_text_field( wp_unslash( $_POST['user_url'] ) );
1558 if ( filter_var( $user_url, FILTER_VALIDATE_URL ) ) {
1559 $friend->user_url = $user_url;
1560 }
1561 }
1562 if ( isset( $_POST['friends_user_login'] ) ) {
1563 $new_user_login = User::sanitize_username( sanitize_text_field( wp_unslash( $_POST['friends_user_login'] ) ) );
1564 if ( $new_user_login && $new_user_login !== $friend->user_login ) {
1565 $friend->update_user_login( $new_user_login );
1566 }
1567 }
1568 $friend->save();
1569 } else {
1570 return;
1571 }
1572
1573 do_action( 'friends_edit_friend_after_form_submit', $friend );
1574
1575 $redirect_url = self_admin_url( 'admin.php?page=edit-friend&user=' . $friend->user_login );
1576 wp_safe_redirect( add_query_arg( $arg, rawurlencode( $arg_value ), $redirect_url ) );
1577 exit;
1578 }
1579
1580 /**
1581 * The Friends Edit User header
1582 *
1583 * @param User $friend The friend.
1584 * @param string $active The active menu entry.
1585 */
1586 public function header_edit_friend( User $friend, $active ) {
1587 $append = '&user=' . sanitize_user( $friend->user_login );
1588 Friends::template_loader()->get_template_part(
1589 'admin/settings-header',
1590 null,
1591 array(
1592 'active' => $active . $append,
1593 'title' => $friend->user_login,
1594 'menu' => array(
1595 __( 'Posts' ) => $friend->get_local_friends_page_url(), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1596 __( 'Settings' ) => 'edit-friend' . $append, // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1597 __( 'Feeds', 'friends' ) => 'edit-friend-feeds' . $append,
1598 __( 'Notifications', 'friends' ) => 'edit-friend-notifications' . $append,
1599 __( 'Rules', 'friends' ) => 'edit-friend-rules' . $append . '&_wpnonce=' . wp_create_nonce( 'edit-friend-rules-' . $friend->user_login ),
1600 ),
1601 )
1602 );
1603 }
1604
1605 /**
1606 * Render the Friends Edit User page
1607 */
1608 public function render_admin_edit_friend() {
1609 $friend = $this->check_admin_edit_friend();
1610
1611 $args = array_merge(
1612 $friend->get_post_stats(),
1613 array(
1614 'friend' => $friend,
1615 'friends_settings_url' => add_query_arg( '_wp_http_referer', remove_query_arg( '_wp_http_referer' ), self_admin_url( 'admin.php?page=friends-settings' ) ),
1616 'registered_parsers' => $this->friends->feed->get_registered_parsers(),
1617 )
1618 );
1619
1620 $this->header_edit_friend( $friend, 'edit-friend' );
1621 // phpcs:disable WordPress.Security.NonceVerification
1622 if ( isset( $_GET['updated'] ) ) {
1623 ?>
1624 <div id="message" class="updated notice is-dismissible"><p><?php esc_html_e( 'User was updated.', 'friends' ); ?></p></div>
1625 <?php
1626 } elseif ( isset( $_GET['friend'] ) ) {
1627 ?>
1628 <div id="message" class="updated notice is-dismissible"><p><?php esc_html_e( 'You are now friends.', 'friends' ); ?></p></div>
1629 <?php
1630 } elseif ( isset( $_GET['error'] ) ) {
1631 ?>
1632 <div id="message" class="updated error is-dismissible"><p>
1633 <?php
1634 if ( 1 === intval( $_GET['error'] ) ) {
1635 esc_html_e( 'An error occurred.', 'friends' );
1636 } else {
1637 echo esc_html( Rest::translate_error_message( sanitize_text_field( wp_unslash( $_GET['error'] ) ) ) );
1638 }
1639 ?>
1640 </p></div>
1641 <?php
1642 } elseif ( isset( $_GET['sent-request'] ) ) {
1643 ?>
1644 <div id="message" class="updated notice is-dismissible"><p><?php esc_html_e( 'Your request was sent.', 'friends' ); ?></p></div>
1645 <?php
1646 } elseif ( isset( $_GET['subscribed'] ) ) {
1647 ?>
1648 <div id="message" class="updated notice is-dismissible"><p><?php esc_html_e( 'Subscription activated.', 'friends' ); ?></p></div>
1649 <?php
1650 }
1651 // phpcs:enable WordPress.Security.NonceVerification
1652
1653 Friends::template_loader()->get_template_part( 'admin/edit-friend', null, $args );
1654 }
1655
1656 public function ajax_refresh_feeds() {
1657 check_ajax_referer( 'friends-refresh' );
1658
1659 if ( ! Friends::has_required_privileges() ) {
1660 wp_send_json_error( __( 'You do not have permission to do this.', 'friends' ) );
1661 }
1662
1663 add_filter( 'notify_about_new_friend_post', '__return_false', 999 );
1664
1665 if ( ! empty( $_POST['user'] ) ) {
1666 $friend_user = User::get_by_username( sanitize_user( wp_unslash( $_POST['user'] ) ) );
1667 if ( ! $friend_user || is_wp_error( $friend_user ) || ! $friend_user->can_refresh_feeds() ) {
1668 wp_send_json_error( __( 'Invalid user ID.' ) ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1669 }
1670 $friend_user->retrieve_posts_from_active_feeds();
1671 } else {
1672 $this->friends->feed->retrieve_friend_posts();
1673 }
1674
1675 wp_send_json_success();
1676 }
1677
1678 private function normalize_frontend_subscription_url( $url ) {
1679 if ( ! is_string( $url ) ) {
1680 return '';
1681 }
1682
1683 $url = trim( $url );
1684 if ( '' === $url ) {
1685 return '';
1686 }
1687
1688 $protocol = wp_parse_url( $url, PHP_URL_SCHEME );
1689 if ( ! $protocol ) {
1690 return apply_filters( 'friends_rewrite_incoming_url', 'https://' . $url, $url );
1691 }
1692
1693 return apply_filters( 'friends_rewrite_incoming_url', $url, $url );
1694 }
1695
1696 public function ajax_preview_subscription() {
1697 if ( ! isset( $_POST['url'] ) || is_array( $_POST['url'] ) ) {
1698 wp_send_json_error( __( 'No URL provided.', 'friends' ) );
1699 }
1700
1701 check_ajax_referer( 'friends_add_subscription' );
1702
1703 if ( ! Friends::has_required_privileges() ) {
1704 wp_send_json_error( __( 'You do not have permission to do this.', 'friends' ) );
1705 }
1706
1707 $url = $this->normalize_frontend_subscription_url( wp_unslash( $_POST['url'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1708
1709 if ( '' === $url ) {
1710 wp_send_json_error( __( 'No URL provided.', 'friends' ) );
1711 }
1712
1713 if ( str_starts_with( $url, home_url() ) ) {
1714 wp_send_json_error( __( 'It seems like you sent a friend request to yourself.', 'friends' ) );
1715 }
1716
1717 if ( ! Friends::check_url( $url ) ) {
1718 wp_send_json_error( __( 'You entered an invalid URL.', 'friends' ) );
1719 }
1720
1721 $user_login = apply_filters( 'friends_suggest_user_login', User::get_user_login_for_url( $url ), $url );
1722 $display_name = apply_filters( 'friends_suggest_display_name', User::get_display_name_for_url( $url ), $url );
1723
1724 $feeds = $this->friends->feed->discover_available_feeds( $url );
1725
1726 if ( is_wp_error( $feeds ) ) {
1727 wp_send_json_error( $feeds->get_error_message() );
1728 }
1729
1730 if ( empty( $feeds ) ) {
1731 wp_send_json_error( __( 'No suitable feed was found at the provided address.', 'friends' ) );
1732 }
1733
1734 $better_user_login = User::get_user_login_from_feeds( $feeds );
1735 if ( $better_user_login ) {
1736 $user_login = trim( $better_user_login, '-' );
1737 }
1738
1739 $better_display_name = User::get_display_name_from_feeds( $feeds );
1740 if ( $better_display_name ) {
1741 $display_name = $better_display_name;
1742 if ( ! $better_user_login ) {
1743 $user_login = trim( User::sanitize_username( $better_display_name ), '-' );
1744 }
1745 }
1746
1747 $friend_user = User::get_user( $user_login );
1748 if ( ! $friend_user || is_wp_error( $friend_user ) ) {
1749 $friend_user = Subscription::get_by_username( $user_login );
1750 }
1751
1752 if ( $friend_user && ! is_wp_error( $friend_user ) ) {
1753 // translators: %s is the name of a friend / site.
1754 wp_send_json_error( sprintf( __( 'You are already subscribed to this site: %s', 'friends' ), $friend_user->display_name ) );
1755 }
1756
1757 $avatar = null;
1758 $description = null;
1759 foreach ( $feeds as $feed_details ) {
1760 if ( ! $avatar && ! empty( $feed_details['avatar'] ) ) {
1761 $avatar = $feed_details['avatar'];
1762 }
1763 if ( ! $description && ! empty( $feed_details['description'] ) ) {
1764 $description = $feed_details['description'];
1765 }
1766 }
1767
1768 wp_send_json_success(
1769 array(
1770 'feeds' => $feeds,
1771 'display_name' => $display_name ? $display_name : '',
1772 'user_login' => $user_login ? $user_login : '',
1773 'avatar' => $avatar,
1774 'description' => $description,
1775 'url' => $url,
1776 )
1777 );
1778 }
1779
1780 public function ajax_preview_subscription_feed() {
1781 check_ajax_referer( 'friends_add_subscription' );
1782
1783 if ( ! Friends::has_required_privileges() ) {
1784 wp_send_json_error( __( 'You do not have permission to do this.', 'friends' ) );
1785 }
1786
1787 $url = isset( $_POST['url'] ) && ! is_array( $_POST['url'] ) ? $this->normalize_frontend_subscription_url( wp_unslash( $_POST['url'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1788 if ( '' === $url || ! Friends::check_url( $url ) ) {
1789 wp_send_json_error( __( 'You entered an invalid URL.', 'friends' ) );
1790 }
1791
1792 $parser = isset( $_POST['parser'] ) && ! is_array( $_POST['parser'] ) ? sanitize_key( wp_unslash( $_POST['parser'] ) ) : '';
1793 if ( ! $parser ) {
1794 wp_send_json_error( __( 'An invalid parser was supplied.', 'friends' ) );
1795 }
1796
1797 $items = $this->friends->feed->preview( $parser, $url );
1798 if ( is_wp_error( $items ) ) {
1799 wp_send_json_error( $items->get_error_message() );
1800 }
1801
1802 $preview_items = array();
1803 foreach ( array_slice( $items, 0, 5 ) as $item ) {
1804 $title = $item->title;
1805 if ( 'status' === $item->post_format || ! $title ) {
1806 $title = wp_strip_all_tags( $item->content );
1807 }
1808
1809 $preview_items[] = array(
1810 'title' => wp_trim_words( wp_strip_all_tags( $title ), 16 ),
1811 'excerpt' => wp_trim_words( wp_strip_all_tags( $item->content ), 40 ),
1812 'permalink' => $item->permalink,
1813 'date' => $item->date,
1814 'author' => $item->author,
1815 'post_format' => $item->post_format,
1816 );
1817 }
1818
1819 wp_send_json_success(
1820 array(
1821 'items' => $preview_items,
1822 )
1823 );
1824 }
1825
1826 public function ajax_subscribe_frontend() {
1827 check_ajax_referer( 'friends_add_subscription' );
1828
1829 if ( ! Friends::has_required_privileges() ) {
1830 wp_send_json_error( __( 'You do not have permission to do this.', 'friends' ) );
1831 }
1832
1833 $url = isset( $_POST['url'] ) && ! is_array( $_POST['url'] ) ? $this->normalize_frontend_subscription_url( wp_unslash( $_POST['url'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1834 $display_name = isset( $_POST['display_name'] ) && ! is_array( $_POST['display_name'] ) ? sanitize_text_field( wp_unslash( $_POST['display_name'] ) ) : '';
1835 $user_login = isset( $_POST['user_login'] ) && ! is_array( $_POST['user_login'] ) ? User::sanitize_username( wp_unslash( $_POST['user_login'] ) ) : User::get_user_login_for_url( $url ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1836 $feeds = isset( $_POST['feeds'] ) && is_array( $_POST['feeds'] ) ? wp_unslash( $_POST['feeds'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1837
1838 if ( empty( $url ) || empty( $feeds ) ) {
1839 wp_send_json_error( __( 'Missing required data.', 'friends' ) );
1840 }
1841
1842 if ( ! Friends::check_url( $url ) ) {
1843 wp_send_json_error( __( 'You entered an invalid URL.', 'friends' ) );
1844 }
1845
1846 $user_login = trim( $user_login, '-' );
1847 if ( ! $user_login ) {
1848 wp_send_json_error( __( 'Please enter a valid username.', 'friends' ) );
1849 }
1850
1851 if ( ! $display_name ) {
1852 $display_name = User::get_display_name_for_url( $url );
1853 }
1854
1855 if ( ! is_multisite() && username_exists( $user_login ) ) {
1856 wp_send_json_error( __( 'This username is already registered. Please choose another one.' ) ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1857 }
1858
1859 $avatar = null;
1860 $description = null;
1861 $feed_options = array();
1862 $subscribe = array();
1863 $post_formats = array_merge( array( 'autodetect' => true ), array_fill_keys( array_keys( get_post_format_strings() ), true ) );
1864
1865 foreach ( $feeds as $feed ) {
1866 if ( ! is_array( $feed ) ) {
1867 continue;
1868 }
1869
1870 $feed_url = '';
1871 if ( ! empty( $feed['url'] ) && is_scalar( $feed['url'] ) ) {
1872 $feed_url = esc_url_raw( trim( $feed['url'] ) );
1873 }
1874
1875 if ( ! $feed_url || ! Friends::check_url( $feed_url ) ) {
1876 continue;
1877 }
1878
1879 $parser = isset( $feed['parser'] ) && is_scalar( $feed['parser'] ) ? sanitize_key( $feed['parser'] ) : 'simplepie';
1880 if ( ! $parser || 'unsupported' === $parser ) {
1881 continue;
1882 }
1883
1884 $post_format = isset( $feed['post-format'] ) && is_scalar( $feed['post-format'] ) ? sanitize_key( $feed['post-format'] ) : 'standard';
1885 if ( ! isset( $post_formats[ $post_format ] ) ) {
1886 $post_format = 'standard';
1887 }
1888
1889 $mime_type = isset( $feed['mime-type'] ) && is_scalar( $feed['mime-type'] ) ? sanitize_text_field( $feed['mime-type'] ) : '';
1890 if ( ! $mime_type && ! empty( $feed['type'] ) && is_scalar( $feed['type'] ) ) {
1891 $mime_type = sanitize_text_field( $feed['type'] );
1892 }
1893
1894 $feed_options[ $feed_url ] = array(
1895 'url' => $feed_url,
1896 'parser' => $parser,
1897 'post-format' => $post_format,
1898 'title' => isset( $feed['title'] ) && is_scalar( $feed['title'] ) ? sanitize_text_field( $feed['title'] ) : $feed_url,
1899 );
1900
1901 if ( $mime_type ) {
1902 $feed_options[ $feed_url ]['mime-type'] = $mime_type;
1903 }
1904
1905 $is_selected = isset( $feed['selected'] ) && in_array( $feed['selected'], array( true, 'true', '1', 1, 'on' ), true );
1906 if ( $is_selected ) {
1907 $subscribe[] = $feed_url;
1908 }
1909
1910 if ( ! $avatar && ! empty( $feed['avatar'] ) && is_scalar( $feed['avatar'] ) ) {
1911 $avatar = esc_url_raw( $feed['avatar'] );
1912 }
1913 if ( ! $description && ! empty( $feed['description'] ) && is_scalar( $feed['description'] ) ) {
1914 $description = wp_encode_emoji( sanitize_textarea_field( $feed['description'] ) );
1915 }
1916 }
1917
1918 if ( empty( $feed_options ) ) {
1919 wp_send_json_error( __( 'No suitable feed was found at the provided address.', 'friends' ) );
1920 }
1921
1922 if ( empty( $subscribe ) ) {
1923 wp_send_json_error( __( 'Please select at least one feed.', 'friends' ) );
1924 }
1925
1926 $friend_user = User::get_user( $user_login );
1927 if ( ! $friend_user || is_wp_error( $friend_user ) ) {
1928 $friend_user = Subscription::get_by_username( $user_login );
1929 }
1930
1931 if ( $friend_user && ! is_wp_error( $friend_user ) ) {
1932 // translators: %s is the name of a friend / site.
1933 wp_send_json_error( sprintf( __( 'You are already subscribed to this site: %s', 'friends' ), $friend_user->display_name ) );
1934 }
1935
1936 $friend_user = User::create( $user_login, 'subscription', $url, $display_name, $avatar, $description );
1937
1938 if ( is_wp_error( $friend_user ) ) {
1939 wp_send_json_error( $friend_user->get_error_message() );
1940 }
1941
1942 $saved_feeds = $friend_user->save_feeds( $feed_options );
1943 if ( is_wp_error( $saved_feeds ) ) {
1944 wp_send_json_error( $saved_feeds->get_error_message() );
1945 }
1946
1947 foreach ( $subscribe as $feed_url ) {
1948 if ( ! isset( $feed_options[ $feed_url ] ) ) {
1949 continue;
1950 }
1951 $new_feed = $friend_user->subscribe( $feed_url, $feed_options[ $feed_url ] );
1952 if ( ! is_wp_error( $new_feed ) ) {
1953 do_action( 'friends_user_feed_activated', $new_feed );
1954 }
1955 }
1956
1957 add_filter( 'notify_about_new_friend_post', '__return_false', 999 );
1958 wp_schedule_single_event( time(), 'friends_retrieve_user_feeds', array( $friend_user->ID ) );
1959
1960 wp_send_json_success(
1961 array(
1962 'message' => sprintf(
1963 // translators: %s is the name of a friend.
1964 __( 'You are now following %s.', 'friends' ),
1965 $display_name
1966 ),
1967 'url' => $friend_user->get_local_friends_page_url(),
1968 )
1969 );
1970 }
1971
1972 public function ajax_set_avatar() {
1973 if ( ! isset( $_POST['user'] ) ) {
1974 wp_send_json_error( __( 'No user specified.', 'friends' ) );
1975 }
1976
1977 check_ajax_referer( 'set-avatar-' . sanitize_user( wp_unslash( $_POST['user'] ) ) );
1978
1979 if ( ! current_user_can( Friends::REQUIRED_ROLE ) ) {
1980 wp_send_json_error();
1981 exit;
1982 }
1983 if ( empty( $_POST['avatar'] ) ) {
1984 wp_send_json_error();
1985 exit;
1986 }
1987 $avatar = check_url( wp_unslash( $_POST['avatar'] ) );
1988 if ( empty( $avatar ) ) {
1989 wp_send_json_error();
1990 exit;
1991 }
1992
1993 $friend = User::get_by_username( sanitize_user( wp_unslash( $_POST['user'] ) ) );
1994 if ( ! $friend || is_wp_error( $friend ) ) {
1995 wp_send_json_error( __( 'Invalid user.', 'friends' ) );
1996 exit;
1997 }
1998
1999 // Use WordPress functions to check the image dimensions.
2000 $size = \wp_getimagesize( $avatar );
2001 if ( ! $size ) {
2002 wp_send_json_error( __( 'Image is in an unknown format.', 'friends' ) );
2003 exit;
2004 }
2005 // Needs to be square and not larger than 512x512.
2006 if ( $size[0] !== $size[1] || $size[0] > 512 ) {
2007 wp_send_json_error( __( 'Image must be square and not larger than 512x512.', 'friends' ) );
2008 exit;
2009 }
2010
2011 $url = $friend->update_user_icon_url( $avatar );
2012
2013 if ( ! $url || is_wp_error( $url ) ) {
2014 wp_send_json_error( $url );
2015 exit;
2016 }
2017
2018 wp_send_json_success(
2019 array(
2020 'url' => $url,
2021 )
2022 );
2023 }
2024
2025 /**
2026 * Process the Friends Edit Notifications page
2027 */
2028 public function process_admin_edit_friend_notifications() {
2029 $friend = $this->check_admin_edit_friend();
2030 $arg = 'updated';
2031 $arg_value = 1;
2032
2033 if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), 'edit-friend-notifications-' . $friend->user_login ) ) {
2034 if ( ! get_user_option( 'friends_no_new_post_notification' ) ) {
2035 if ( isset( $_POST['friends_new_post_notification'] ) && boolval( $_POST['friends_new_post_notification'] ) ) {
2036 delete_user_option( get_current_user_id(), 'friends_no_new_post_notification_' . $friend->user_login );
2037 } else {
2038 update_user_option( get_current_user_id(), 'friends_no_new_post_notification_' . $friend->user_login, 1 );
2039 }
2040 }
2041
2042 if ( ! get_user_option( 'friends_no_keyword_notification' ) ) {
2043 if ( isset( $_POST['friends_keyword_notification'] ) && boolval( $_POST['friends_keyword_notification'] ) ) {
2044 delete_user_option( get_current_user_id(), 'friends_no_keyword_notification_' . $friend->user_login );
2045 } else {
2046 update_user_option( get_current_user_id(), 'friends_no_keyword_notification_' . $friend->user_login, 1 );
2047 }
2048 }
2049
2050 do_action( 'friends_edit_friend_notifications_after_form_submit', $friend );
2051 } else {
2052 return;
2053 }
2054
2055 if ( isset( $_GET['_wp_http_referer'] ) ) {
2056 wp_safe_redirect( wp_get_referer() );
2057 } else {
2058 wp_safe_redirect( add_query_arg( $arg, $arg_value, remove_query_arg( array( '_wp_http_referer', '_wpnonce' ) ) ) );
2059 }
2060 exit;
2061 }
2062
2063 /**
2064 * Render the Friends Edit Notifications page
2065 */
2066 public function render_admin_edit_friend_notifications() {
2067 $friend = $this->check_admin_edit_friend();
2068 $post_stats = $friend->get_post_stats();
2069
2070 $this->header_edit_friend( $friend, 'edit-friend-notifications' );
2071
2072 // phpcs:disable WordPress.Security.NonceVerification
2073 if ( isset( $_GET['updated'] ) ) {
2074 ?>
2075 <div id="message" class="updated notice is-dismissible"><p><?php esc_html_e( 'Notification Settings were updated.', 'friends' ); ?></p></div>
2076 <?php
2077 } elseif ( isset( $_GET['error'] ) ) {
2078 ?>
2079 <div id="message" class="updated error is-dismissible"><p><?php esc_html_e( 'An error occurred.', 'friends' ); ?></p></div>
2080 <?php
2081 }
2082 // phpcs:enable WordPress.Security.NonceVerification
2083
2084 Friends::template_loader()->get_template_part(
2085 'admin/edit-notifications',
2086 null,
2087 array(
2088 'friend' => $friend,
2089 )
2090 );
2091 }
2092
2093 /**
2094 * Process the Friends Edit Feeds page
2095 */
2096 public function process_admin_edit_friend_feeds() {
2097 $friend = $this->check_admin_edit_friend();
2098 $arg = 'updated';
2099 $arg_value = 1;
2100
2101 if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), 'edit-friend-feeds-' . $friend->user_login ) ) {
2102 $hide_from_friends_page = get_user_option( 'friends_hide_from_friends_page' );
2103 if ( ! $hide_from_friends_page ) {
2104 $hide_from_friends_page = array();
2105 }
2106 if ( ! isset( $_POST['show_on_friends_page'] ) || ! boolval( $_POST['show_on_friends_page'] ) ) {
2107 if ( ! in_array( $friend->user_login, $hide_from_friends_page ) ) {
2108 $hide_from_friends_page[] = $friend->user_login;
2109 update_user_option( get_current_user_id(), 'friends_hide_from_friends_page', $hide_from_friends_page );
2110 }
2111 } elseif ( in_array( $friend->user_login, $hide_from_friends_page ) ) {
2112 $hide_from_friends_page = array_values( array_diff( $hide_from_friends_page, array( $friend->user_login ) ) );
2113 update_user_option( get_current_user_id(), 'friends_hide_from_friends_page', $hide_from_friends_page );
2114 }
2115
2116 if ( $friend->set_retention_number_enabled( boolval( filter_input( INPUT_POST, 'friends_enable_retention_number', FILTER_SANITIZE_NUMBER_INT ) ) ) && isset( $_POST['friends_retention_number'] ) ) {
2117 $friend->set_retention_number( filter_input( INPUT_POST, 'friends_retention_number', FILTER_SANITIZE_NUMBER_INT ) );
2118 }
2119 if ( $friend->set_retention_days_enabled( boolval( filter_input( INPUT_POST, 'friends_enable_retention_days', FILTER_SANITIZE_NUMBER_INT ) ) ) && isset( $_POST['friends_retention_days'] ) ) {
2120 $friend->set_retention_days( filter_input( INPUT_POST, 'friends_retention_days', FILTER_SANITIZE_NUMBER_INT ) );
2121 }
2122
2123 $hide_from_friends_page = get_user_option( 'friends_hide_from_friends_page' );
2124 if ( ! $hide_from_friends_page ) {
2125 $hide_from_friends_page = array();
2126 }
2127
2128 $show_on_dashboard = filter_input( INPUT_POST, 'show_on_dashboard', FILTER_VALIDATE_BOOLEAN );
2129 $already_on_dashboard = false;
2130 $widgets = get_user_option( 'friends_dashboard_widgets', get_current_user_id() );
2131 if ( ! $widgets ) {
2132 $widgets = array();
2133 }
2134 foreach ( $widgets as $k => $widget ) {
2135 if ( ! empty( $widget['friend'] ) && $widget['friend'] === $friend->user_login ) {
2136 $already_on_dashboard = true;
2137 if ( ! $show_on_dashboard ) {
2138 unset( $widgets[ $k ] );
2139 update_user_option( get_current_user_id(), 'friends_dashboard_widgets', $widgets );
2140 }
2141 break;
2142 }
2143 }
2144 if ( $show_on_dashboard && ! $already_on_dashboard ) {
2145 $widgets[] = array( 'friend' => $friend->user_login );
2146 update_user_option( get_current_user_id(), 'friends_dashboard_widgets', $widgets );
2147 }
2148
2149 if ( isset( $_POST['feeds'] ) ) {
2150 // Sanitized below.
2151 $feeds = wp_unslash( $_POST['feeds'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2152 $existing_feeds = $friend->get_feeds();
2153 if ( isset( $feeds['new'] ) ) {
2154 if ( ! isset( $feeds['new']['url'] ) || '' === trim( $feeds['new']['url'] ) ) {
2155 unset( $feeds['new'] );
2156 } else {
2157 foreach ( $existing_feeds as $term_id => $user_feed ) {
2158 if ( $user_feed->get_url() === trim( $feeds['new']['url'] ) ) {
2159 if ( isset( $feeds[ $term_id ] ) ) {
2160 // Let a newly entered feed overrule an existing one.
2161 $feeds[ $term_id ] = array_merge( $feeds[ $term_id ], $feeds['new'] );
2162 $feeds[ $term_id ]['active'] = 1;
2163 }
2164 unset( $feeds['new'] );
2165 break;
2166 }
2167 }
2168 }
2169 }
2170 foreach ( $feeds as $term_id => $feed ) {
2171 if ( 'new' === $term_id ) {
2172 if ( ! isset( $feed['url'] ) || '' === trim( $feed['url'] ) ) {
2173 continue;
2174 }
2175
2176 $feed['active'] = true;
2177 $protocol = wp_parse_url( $feed['url'], PHP_URL_SCHEME );
2178 if ( ! $protocol ) {
2179 $feed['url'] = apply_filters( 'friends_rewrite_incoming_url', 'https://' . $feed['url'], $feed['url'] );
2180 }
2181 $new_feed = $friend->subscribe( $feed['url'], $feed );
2182 if ( is_wp_error( $new_feed ) ) {
2183 do_action( 'friends_process_feed_item_submit_error', $new_feed, $feed );
2184 continue;
2185 }
2186
2187 do_action( 'friends_user_feed_activated', $new_feed );
2188 do_action( 'friends_process_feed_item_submit', $new_feed, $feed );
2189 continue;
2190 }
2191
2192 if ( ! isset( $existing_feeds[ $term_id ] ) ) {
2193 continue;
2194 }
2195 $user_feed = $existing_feeds[ $term_id ];
2196 unset( $existing_feeds[ $term_id ] );
2197
2198 $protocol = wp_parse_url( $feed['url'], PHP_URL_SCHEME );
2199 if ( ! $protocol ) {
2200 $feed['url'] = apply_filters( 'friends_rewrite_incoming_url', 'https://' . $feed['url'], $feed['url'] );
2201 }
2202
2203 if ( $user_feed->get_url() !== $feed['url'] ) {
2204 do_action( 'friends_user_feed_deactivated', $user_feed );
2205
2206 if ( ! isset( $feed['mime-type'] ) ) {
2207 $feed['mime-type'] = $user_feed->get_mime_type();
2208 }
2209
2210 if ( $feed['active'] ) {
2211 $new_feed = $friend->subscribe( $feed['url'], $feed );
2212 if ( ! is_wp_error( $new_feed ) ) {
2213 do_action( 'friends_user_feed_activated', $new_feed );
2214 }
2215 } else {
2216 $new_feed = $friend->save_feed( $feed['url'], $feed );
2217 }
2218
2219 // Since the URL has changed, the above will create a new feed, therefore we need to delete the old one.
2220 $user_feed->delete();
2221
2222 if ( is_wp_error( $new_feed ) ) {
2223 do_action( 'friends_process_feed_item_submit_error', $new_feed, $feed );
2224 continue;
2225 }
2226
2227 do_action( 'friends_process_feed_item_submit', $new_feed, $feed );
2228 continue;
2229 }
2230
2231 if ( $user_feed->get_title() !== $feed['title'] ) {
2232 $user_feed->update_metadata( 'title', $feed['title'] );
2233 }
2234
2235 if ( $user_feed->get_parser() !== $feed['parser'] ) {
2236 $user_feed->update_metadata( 'parser', $feed['parser'] );
2237 }
2238
2239 if ( $user_feed->get_post_format() !== $feed['post-format'] ) {
2240 $user_feed->update_metadata( 'post-format', $feed['post-format'] );
2241 }
2242
2243 if ( isset( $feed['mime-type'] ) && $user_feed->get_mime_type() !== $feed['mime-type'] ) {
2244 $user_feed->update_metadata( 'mime-type', $feed['mime-type'] );
2245 }
2246
2247 $was_active = $user_feed->is_active();
2248 $is_active = isset( $feed['active'] ) && $feed['active'];
2249 $user_feed->update_metadata( 'active', $is_active );
2250 if ( $was_active !== $is_active ) {
2251 if ( $is_active ) {
2252 do_action( 'friends_user_feed_activated', $user_feed );
2253 } else {
2254 do_action( 'friends_user_feed_deactivated', $user_feed );
2255 }
2256 }
2257
2258 do_action( 'friends_process_feed_item_submit', $user_feed, $feed );
2259 }
2260
2261 // Delete remaining existing feeds since they were not submitted.
2262 foreach ( $existing_feeds as $term_id => $user_feed ) {
2263 do_action( 'friends_user_feed_deactivated', $user_feed );
2264 $user_feed->delete();
2265 }
2266 }
2267 do_action( 'friends_edit_feeds_after_form_submit', $friend );
2268 } else {
2269 return;
2270 }
2271
2272 if ( isset( $_GET['_wp_http_referer'] ) ) {
2273 wp_safe_redirect( wp_get_referer() );
2274 } else {
2275 wp_safe_redirect( add_query_arg( $arg, $arg_value, remove_query_arg( array( '_wp_http_referer', '_wpnonce' ) ) ) );
2276 }
2277 exit;
2278 }
2279
2280 /**
2281 * Render the Friends Edit Feeds page
2282 */
2283 public function render_admin_edit_friend_feeds() {
2284 $friend = $this->check_admin_edit_friend();
2285
2286 $already_on_dashboard = false;
2287 $widgets = get_user_option( 'friends_dashboard_widgets', get_current_user_id() );
2288
2289 if ( ! $widgets ) {
2290 $widgets = array();
2291 }
2292 foreach ( $widgets as $widget ) {
2293 if ( ! empty( $widget['friend'] ) && $widget['friend'] === $friend->user_login ) {
2294 $already_on_dashboard = true;
2295 break;
2296 }
2297 }
2298
2299 $args = array_merge(
2300 $friend->get_post_stats(),
2301 array(
2302 'friend' => $friend,
2303 'rules' => $friend->get_feed_rules(),
2304 'hide_from_friends_page' => get_user_option( 'friends_hide_from_friends_page' ),
2305 'post_formats' => array_merge( array( 'autodetect' => __( 'Autodetect Post Format', 'friends' ) ), get_post_format_strings() ),
2306 'friends_settings_url' => add_query_arg( '_wp_http_referer', remove_query_arg( '_wp_http_referer' ), self_admin_url( 'admin.php?page=friends-settings' ) ),
2307 'registered_parsers' => $this->friends->feed->get_registered_parsers(),
2308 'global_retention_days' => Friends::get_retention_days(),
2309 'global_retention_number' => Friends::get_retention_number(),
2310 'global_retention_days_enabled' => get_option( 'friends_enable_retention_days' ),
2311 'global_retention_number_enabled' => get_option( 'friends_enable_retention_number' ),
2312 'show_on_dashboard' => $already_on_dashboard,
2313 )
2314 );
2315 if ( ! $args['hide_from_friends_page'] ) {
2316 $args['hide_from_friends_page'] = array();
2317 }
2318 $this->header_edit_friend( $friend, 'edit-friend-feeds' );
2319
2320 // phpcs:disable WordPress.Security.NonceVerification
2321 if ( isset( $_GET['updated'] ) ) {
2322 ?>
2323 <div id="message" class="updated notice is-dismissible"><p><?php esc_html_e( 'Feeds were updated.', 'friends' ); ?></p></div>
2324 <?php
2325 } elseif ( isset( $_GET['error'] ) ) {
2326 ?>
2327 <div id="message" class="updated error is-dismissible"><p><?php esc_html_e( 'An error occurred.', 'friends' ); ?></p></div>
2328 <?php
2329 }
2330 // phpcs:enable WordPress.Security.NonceVerification
2331
2332 Friends::template_loader()->get_template_part( 'admin/edit-feeds', null, $args );
2333 }
2334
2335 /**
2336 * Process the Unfriend page
2337 */
2338 public function process_admin_unfriend() {
2339 $friend = $this->check_admin_edit_friend();
2340 $arg = 'deleted';
2341 $arg_value = $friend->user_login;
2342
2343 if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), 'unfriend-' . $friend->user_login ) ) {
2344 $friend->delete();
2345 } else {
2346 return;
2347 }
2348
2349 if ( isset( $_GET['_wp_http_referer'] ) ) {
2350 wp_safe_redirect( wp_get_referer() );
2351 } else {
2352 wp_safe_redirect( add_query_arg( $arg, $arg_value, self_admin_url( 'admin.php?page=friends-list' ) ) );
2353 }
2354 exit;
2355 }
2356
2357 /**
2358 * Render the Unfriend page
2359 */
2360 public function render_admin_unfriend() {
2361 $friend = $this->check_admin_edit_friend();
2362 $post_stats = $friend->get_post_stats();
2363
2364 $args = array(
2365 'friend' => $friend,
2366 'friend_posts' => $post_stats['post_count'],
2367 'total_size' => $post_stats['total_size'],
2368 );
2369
2370 Friends::template_loader()->get_template_part( 'admin/unfriend', null, $args );
2371 }
2372
2373 /**
2374 * Display error messages.
2375 *
2376 * @param object $errors The errors.
2377 */
2378 private function display_errors( $errors ) {
2379 if ( ! is_wp_error( $errors ) ) {
2380 return;
2381 }
2382
2383 ?>
2384 <div id="message" class="updated error is-dismissible"><p><?php echo esc_html( $errors->get_error_message() ); ?></p>
2385 <?php
2386 $error_data = $errors->get_error_data();
2387 if ( isset( $error_data->error ) ) {
2388 $error = unserialize( $error_data->error ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
2389 if ( is_wp_error( $error ) ) {
2390 ?>
2391 <pre>
2392 <?php
2393 print_r( $error ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
2394 ?>
2395 </pre>
2396 <?php
2397 } elseif ( is_array( $error ) && isset( $error['body'] ) ) {
2398 ?>
2399 <textarea>
2400 <?php
2401 echo esc_html( $error['body'] );
2402 ?>
2403 </textarea>
2404 <?php
2405 }
2406 }
2407 ?>
2408 </div>
2409 <?php
2410 }
2411
2412 public function create_and_follow( $user_id, $url ) {
2413 // TODO: replace with frontend functionality.
2414 }
2415
2416 /**
2417 * Process the admin notification manager form submission.
2418 */
2419 public function process_admin_notification_manager() {
2420 if ( empty( $_POST ) ) {
2421 return;
2422 }
2423
2424 if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), 'notification-manager' ) ) {
2425 return;
2426 }
2427
2428 $this->check_admin_settings();
2429
2430 if ( ! empty( $_POST['notification_keywords'] ) && is_array( $_POST['notification_keywords'] ) ) {
2431 $keywords = array();
2432 foreach ( wp_unslash( $_POST['notification_keywords'] ) as $i => $keyword ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2433 if ( trim( $keyword ) ) {
2434 $keywords[] = array(
2435 'enabled' => isset( $_POST['notification_keywords_enabled'][ $i ] ) && boolval( $_POST['notification_keywords_enabled'][ $i ] ),
2436 'keyword' => sanitize_text_field( $keyword ),
2437 );
2438 }
2439 }
2440 update_option( 'friends_notification_keywords', $keywords );
2441 }
2442
2443 if ( isset( $_POST['keyword_notification_override'] ) && boolval( $_POST['keyword_notification_override'] ) ) {
2444 delete_user_option( get_current_user_id(), 'friends_keyword_notification_override_disabled' );
2445 } else {
2446 update_user_option( get_current_user_id(), 'friends_keyword_notification_override_disabled', 1 );
2447 }
2448
2449 if ( isset( $_POST['new_post_notification'] ) && boolval( $_POST['new_post_notification'] ) ) {
2450 delete_user_option( get_current_user_id(), 'friends_no_new_post_notification' );
2451 } else {
2452 update_user_option( get_current_user_id(), 'friends_no_new_post_notification', 1 );
2453 }
2454
2455 if ( isset( $_POST['friend_follower_notification'] ) && boolval( $_POST['friend_follower_notification'] ) ) {
2456 delete_user_option( get_current_user_id(), 'friends_no_friend_follower_notification' );
2457 } else {
2458 update_user_option( get_current_user_id(), 'friends_no_friend_follower_notification', 1 );
2459 }
2460
2461 foreach ( get_post_format_slugs() as $post_format ) {
2462 if ( isset( $_POST[ 'new_post_format_notification_' . $post_format ] ) && boolval( $_POST[ 'new_post_format_notification_' . $post_format ] ) ) {
2463 delete_user_option( get_current_user_id(), 'friends_no_new_post_format_notification_' . $post_format );
2464 } else {
2465 update_user_option( get_current_user_id(), 'friends_no_new_post_format_notification_' . $post_format, 1 );
2466 }
2467 }
2468
2469 foreach ( array_keys( $this->friends->feed->get_registered_parsers() ) as $parser ) {
2470 if ( isset( $_POST[ 'new_post_by_parser_notification_' . $parser ] ) && boolval( $_POST[ 'new_post_by_parser_notification_' . $parser ] ) ) {
2471 delete_user_option( get_current_user_id(), 'friends_no_new_post_by_parser_notification_' . $parser );
2472 } else {
2473 update_user_option( get_current_user_id(), 'friends_no_new_post_by_parser_notification_' . $parser, 1 );
2474 }
2475 }
2476
2477 if ( empty( $_POST['friend_listed'] ) ) {
2478 return;
2479 }
2480 // This is an array, it is checked before use below.
2481 $friend_usernames = wp_unslash( $_POST['friend_listed'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2482 $current_user_id = get_current_user_id();
2483 $hide_from_friends_page = array();
2484
2485 foreach ( $friend_usernames as $friend_username ) {
2486 $friend_user = User::get_by_username( $friend_username );
2487 if ( ! $friend_user ) {
2488 continue;
2489 }
2490 $friend_username = $friend_user->user_login;
2491 if ( ! isset( $_POST['show_on_friends_page'][ $friend_username ] ) ) {
2492 $hide_from_friends_page[] = $friend_username;
2493 }
2494
2495 $no_new_post_notification = ! isset( $_POST['new_friend_post_notification'][ $friend_username ] ) || '0' === $_POST['new_friend_post_notification'][ $friend_username ];
2496 if ( get_user_option( 'friends_no_new_post_notification_' . $friend_username ) !== $no_new_post_notification ) {
2497 update_user_option( $current_user_id, 'friends_no_new_post_notification_' . $friend_username, $no_new_post_notification );
2498 }
2499
2500 $no_keyword_notification = ! isset( $_POST['keyword_notification'][ $friend_username ] );
2501 if ( get_user_option( 'friends_no_keyword_notification_' . $friend_username ) !== $no_keyword_notification ) {
2502 update_user_option( $current_user_id, 'friends_no_keyword_notification_' . $friend_username, $no_keyword_notification );
2503 }
2504 }
2505
2506 update_user_option( $current_user_id, 'friends_hide_from_friends_page', $hide_from_friends_page );
2507
2508 do_action( 'friends_notification_manager_after_form_submit', $friend_usernames );
2509
2510 if ( isset( $_GET['_wp_http_referer'] ) ) {
2511 wp_safe_redirect( wp_get_referer() );
2512 } else {
2513 wp_safe_redirect( add_query_arg( 'updated', '1', remove_query_arg( array( '_wp_http_referer', '_wpnonce' ) ) ) );
2514 }
2515 exit;
2516 }
2517
2518 /**
2519 * Render the admin notification manager.
2520 */
2521 public function render_admin_notification_manager() {
2522 Friends::template_loader()->get_template_part(
2523 'admin/settings-header',
2524 null,
2525 array(
2526 'active' => 'friends-notification-manager',
2527 'title' => __( 'Friends', 'friends' ),
2528 )
2529 );
2530 $this->check_admin_settings();
2531
2532 $friend_users = User_Query::all_subscriptions();
2533
2534 $hide_from_friends_page = get_user_option( 'friends_hide_from_friends_page' );
2535 if ( ! $hide_from_friends_page ) {
2536 $hide_from_friends_page = array();
2537 }
2538
2539 $args = array(
2540 'friend_users' => $friend_users->get_results(),
2541 'friends_settings_url' => add_query_arg( '_wp_http_referer', remove_query_arg( '_wp_http_referer' ), self_admin_url( 'admin.php?page=friends-settings' ) ),
2542 'hide_from_friends_page' => $hide_from_friends_page,
2543 'keyword_override_disabled' => get_user_option( 'friends_keyword_notification_override_disabled' ),
2544 'no_new_post_notification' => get_user_option( 'friends_no_new_post_notification' ),
2545 'no_keyword_notification' => get_user_option( 'friends_no_keyword_notification' ),
2546 'notification_keywords' => Feed::get_all_notification_keywords(),
2547 'active_keywords' => Feed::get_active_notification_keywords(),
2548 'feed_parsers' => $this->friends->feed->get_registered_parsers(),
2549 );
2550
2551 if ( class_exists( '\Activitypub\Notification' ) ) {
2552 $args['no_friend_follower_notification'] = get_user_option( 'friends_no_friend_follower_notification' );
2553 }
2554
2555 Friends::template_loader()->get_template_part(
2556 'admin/notification-manager',
2557 null,
2558 $args
2559 );
2560
2561 Friends::template_loader()->get_template_part( 'admin/settings-footer' );
2562 }
2563
2564 public function render_admin_import_export() {
2565 Friends::template_loader()->get_template_part(
2566 'admin/settings-header',
2567 null,
2568 array(
2569 'active' => 'friends-import-export',
2570 'title' => __( 'Friends', 'friends' ),
2571 )
2572 );
2573 $this->check_admin_settings();
2574
2575 ?>
2576 <h1><?php esc_html_e( 'Import/Export', 'friends' ); ?></h1>
2577 <?php
2578
2579 Friends::template_loader()->get_template_part(
2580 'admin/import-export',
2581 null,
2582 array(
2583 'private_rss_key' => get_option( 'friends_private_rss_key' ),
2584 )
2585 );
2586
2587 Friends::template_loader()->get_template_part( 'admin/settings-footer' );
2588 }
2589
2590 public function process_admin_import_export() {
2591 if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'friends-settings' ) ) {
2592 return;
2593 }
2594
2595 if ( ! Friends::has_required_privileges() ) {
2596 return;
2597 }
2598
2599 if ( isset( $_FILES['opml']['tmp_name'] ) ) {
2600 $opml = file_get_contents( $_FILES['opml']['tmp_name'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
2601 $feeds = Import::opml( $opml );
2602 $users_created = count( $feeds );
2603 $feeds_imported = 0;
2604 foreach ( $feeds as $user => $user_feeds ) {
2605 $feeds_imported += count( $user_feeds );
2606 }
2607 ?>
2608 <div class="friends-notice notice notice-success is-dismissible">
2609 <p>
2610 <?php
2611 echo esc_html(
2612 sprintf(
2613 // translators: %d is the number of users imported.
2614 _n( 'Imported %d user.', 'Imported %d users.', $users_created, 'friends' ),
2615 $users_created
2616 )
2617 );
2618 ?>
2619 <?php
2620 echo esc_html(
2621 sprintf(
2622 // translators: %d is the number of feeds imported.
2623 _n( 'They had %d feed.', 'They had %d feeds.', $feeds_imported, 'friends' ),
2624 $feeds_imported
2625 )
2626 );
2627 ?>
2628 </p>
2629 </div>
2630 <?php
2631 }
2632 }
2633
2634 public function process_admin_duplicate_remover() {
2635 $friend = $this->check_admin_duplicate_remover();
2636
2637 // Nonce verification done in check_admin_duplicate_remover.
2638 // phpcs:disable WordPress.Security.NonceVerification.Missing
2639
2640 // We iterate over this array and then we sanitize _id.
2641 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2642 if ( empty( $_POST['deleteduplicate'] ) || ! is_array( $_POST['deleteduplicate'] ) ) {
2643 return;
2644 }
2645
2646 $deleted = 0;
2647 foreach ( array_keys( wp_unslash( $_POST['deleteduplicate'] ) ) as $_id ) {
2648 if ( ! is_numeric( $_id ) ) {
2649 continue;
2650 }
2651
2652 if ( wp_delete_post( intval( $_id ) ) ) {
2653 ++$deleted;
2654 }
2655 }
2656 // phpcs:enable WordPress.Security.NonceVerification.Missing
2657 // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2658
2659 if ( $deleted ) {
2660 wp_safe_redirect( add_query_arg( 'deleted', $deleted ) );
2661 exit;
2662 }
2663 }
2664 public function check_admin_duplicate_remover() {
2665 if ( ! Friends::is_main_user() ) {
2666 wp_die( esc_html__( 'Sorry, you are not allowed to edit the rules.', 'friends' ) );
2667 }
2668
2669 if ( ! isset( $_GET['user'] ) ) {
2670 wp_die( esc_html__( 'Invalid user.', 'friends' ) );
2671 }
2672
2673 if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'duplicate-remover-' . sanitize_user( wp_unslash( $_GET['user'] ) ) ) ) {
2674 wp_die( esc_html__( 'Invalid nonce.', 'friends' ) );
2675 }
2676
2677 $friend = User::get_by_username( sanitize_user( wp_unslash( $_GET['user'] ) ) );
2678 if ( ! $friend || is_wp_error( $friend ) ) {
2679 wp_die( esc_html__( 'Invalid username.', 'friends' ) );
2680 }
2681
2682 if ( ! $friend->has_cap( 'subscription' ) ) {
2683 wp_die( esc_html__( 'This is not a user related to this plugin.', 'friends' ) );
2684 }
2685
2686 return $friend;
2687 }
2688 /**
2689 * Render the duplicates remover
2690 */
2691 public function render_admin_duplicate_remover() {
2692 $friend = $this->check_admin_duplicate_remover();
2693
2694 $this->header_edit_friend( $friend, 'duplicate-remover' );
2695 // phpcs:disable WordPress.Security.NonceVerification
2696 if ( isset( $_GET['deleted'] ) ) {
2697 ?>
2698 <div id="message" class="updated notice is-dismissible"><p>
2699 <?php
2700 $deleted = intval( $_GET['deleted'] );
2701 echo esc_html(
2702 sprintf(
2703 // translators: %d is the number of duplicates deleted.
2704 _n( 'Deleted %d selected duplicate.', 'Deleted %d selected duplicates.', $deleted, 'friends' ),
2705 $deleted
2706 )
2707 );
2708 ?>
2709 </p></div>
2710 <?php
2711 }
2712 // phpcs:enable WordPress.Security.NonceVerification
2713
2714 $friend_posts = new \WP_Query();
2715
2716 $friend_posts->set( 'post_type', Friends::CPT );
2717 $friend_posts->set( 'post_status', array( 'publish', 'private', 'trash' ) );
2718 $friend_posts->set( 'posts_per_page', 100 );
2719 $friend_posts = $friend->modify_query_by_author( $friend_posts );
2720
2721 $uniques = array();
2722 foreach ( $friend_posts->get_posts() as $_post ) {
2723 $permalink = get_permalink( $_post );
2724 if ( ! isset( $uniques[ $permalink ] ) ) {
2725 $uniques[ $permalink ] = $_post->ID;
2726 }
2727 }
2728
2729 $args = array(
2730 'friend' => $friend,
2731 'friend_posts' => $friend_posts,
2732 'uniques' => array_flip( $uniques ),
2733 'feed' => $this->friends->feed,
2734 );
2735
2736 Friends::template_loader()->get_template_part( 'admin/duplicates', null, $args );
2737 }
2738
2739
2740 public static function get_browser_api_key_user( $key ) {
2741 $key = (string) $key;
2742 if ( ! $key ) {
2743 return false;
2744 }
2745
2746 $parts = explode( '-', $key, 3 );
2747 if ( 3 !== count( $parts ) ) {
2748 return false;
2749 }
2750
2751 $user_id = (int) $parts[1];
2752 if ( ! $user_id ) {
2753 return false;
2754 }
2755
2756 $desired_key = get_user_option( 'friends_browser_api_key', $user_id );
2757 if ( ! $desired_key || ! hash_equals( (string) $desired_key, (string) $key ) ) {
2758 return false;
2759 }
2760
2761 $user = get_user_by( 'ID', $user_id );
2762 if ( ! $user ) {
2763 return false;
2764 }
2765
2766 return $user;
2767 }
2768
2769 public static function check_browser_api_key( $key ) {
2770 return false !== self::get_browser_api_key_user( $key );
2771 }
2772
2773 public static function revoke_browser_api_key( $user_id = false ) {
2774 if ( ! $user_id ) {
2775 $user_id = get_current_user_id();
2776 }
2777
2778 delete_user_option( $user_id, 'friends_browser_api_key' );
2779 }
2780
2781 public static function get_browser_api_key( $user_id = false ) {
2782 if ( ! $user_id ) {
2783 $user_id = get_current_user_id();
2784 }
2785
2786 $key = get_user_option( 'friends_browser_api_key', $user_id );
2787 if ( ! $key ) {
2788 $key = 'friends-' . $user_id . '-' . wp_generate_password( 32, false );
2789 update_user_option( $user_id, 'friends_browser_api_key', $key );
2790 }
2791
2792 return $key;
2793 }
2794
2795 public function render_browser_extension() {
2796 add_filter(
2797 'friends_admin_tabs',
2798 function ( $menu ) {
2799 $menu[ __( 'Browser Extension', 'friends' ) ] = 'friends-browser-extension';
2800 return $menu;
2801 }
2802 );
2803 Friends::template_loader()->get_template_part(
2804 'admin/settings-header',
2805 null,
2806 array(
2807 'active' => 'friends-browser-extension',
2808 )
2809 );
2810 $this->check_admin_settings();
2811 $browser_api_key = self::get_browser_api_key();
2812
2813 if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), 'friends-browser-extension' ) ) {
2814 if ( isset( $_POST['revoke-api-key'] ) ) {
2815 self::revoke_browser_api_key();
2816 $browser_api_key = self::get_browser_api_key();
2817 }
2818 }
2819
2820 Friends::template_loader()->get_template_part(
2821 'admin/browser-extension',
2822 null,
2823 array(
2824 'browser-api-key' => $browser_api_key,
2825 )
2826 );
2827
2828 Friends::template_loader()->get_template_part( 'admin/settings-footer' );
2829 }
2830
2831 public function render_friends_logs() {
2832 add_filter(
2833 'friends_admin_tabs',
2834 function ( $menu ) {
2835 $menu[ __( 'Logs', 'friends' ) ] = 'friends-logs';
2836 return $menu;
2837 }
2838 );
2839
2840 Friends::template_loader()->get_template_part(
2841 'admin/settings-header',
2842 null,
2843 array(
2844 'active' => 'friends-logs',
2845 )
2846 );
2847 $this->check_admin_settings();
2848
2849 Friends::template_loader()->get_template_part(
2850 'admin/logs',
2851 null,
2852 array(
2853 'logs' => Logging::get_logs(),
2854 )
2855 );
2856
2857 Friends::template_loader()->get_template_part( 'admin/settings-footer' );
2858 }
2859
2860 /**
2861 * Gets the roles associated with the Friends plugin.
2862 *
2863 * @return array The associated roles.
2864 */
2865 public static function get_associated_roles() {
2866 $roles = new \WP_Roles();
2867 $friend_roles = array();
2868 foreach ( $roles->roles as $role => $data ) {
2869 if ( isset( $data['capabilities']['friends_plugin'] ) ) {
2870 $friend_roles[ $role ] = $data['name'];
2871 }
2872 }
2873 return $friend_roles;
2874 }
2875
2876 public static function get_users_url() {
2877 return 'admin.php?page=friends-list';
2878 }
2879
2880 /**
2881 * Override the post title for specific post formats.
2882 *
2883 * @param string $title The title.
2884 * @param int $post_id The post id.
2885 *
2886 * @return string The potentially overriden title.
2887 */
2888 public function override_post_format_title( $title, $post_id = null ) {
2889 if ( $post_id && empty( $title ) && is_admin() && function_exists( 'get_current_screen' ) ) {
2890 $screen = get_current_screen();
2891 if ( $screen && 'edit-post' === $screen->id ) {
2892 if ( 'status' === get_post_format() ) {
2893 $post = get_post( $post_id );
2894 return wp_trim_words( wp_strip_all_tags( $post->post_content ) );
2895 }
2896 }
2897 }
2898 return $title;
2899 }
2900
2901 /**
2902 * Get the unread badge HTML
2903 *
2904 * @return string The unread badge HTML.
2905 */
2906 public function get_unread_badge() {
2907 $unread_count = apply_filters( 'friends_unread_count', 0 );
2908 if ( 0 === intval( $unread_count ) ) {
2909 return '';
2910 }
2911
2912 if ( get_user_option( 'friends_unobtrusive_badge' ) ) {
2913 return ' (' . $unread_count . ')';
2914 }
2915 $unread_badge = ' <div class="wp-core-ui wp-ui-notification friends-open-requests" style="display: inline; font-size: 11px; padding: .1em .5em .1em .4em; border-radius: 9px; background-color: #d63638; color: #fff; text-align: center; height: 18px"><span aria-hidden="true">' . $unread_count . '</span><span class="screen-reader-text">';
2916 // translators: %s is the number of unread items.
2917 $unread_badge .= sprintf( _n( '%s unread item', '%s unread items', $unread_count, 'friends' ), $unread_count );
2918 $unread_badge .= '</span></div>';
2919 return $unread_badge;
2920 }
2921
2922 /**
2923 * Add a Friends menu to the admin bar
2924 *
2925 * @param \WP_Admin_Bar $wp_menu The admin bar to modify.
2926 */
2927 public function admin_bar_friends_menu( \WP_Admin_Bar $wp_menu ) {
2928 if ( ! Friends::has_required_privileges() ) {
2929 return;
2930 }
2931
2932 $my_url = home_url();
2933 $my_admin_url = site_url();
2934
2935 $unread = $this->get_unread_badge();
2936
2937 $wp_menu->add_node(
2938 array(
2939 'id' => 'friends-menu',
2940 'parent' => '',
2941 'title' => '<span class="ab-icon"></span> <span class="ab-label">' . esc_html( __( 'Friends', 'friends' ) ) . $unread . '</span>',
2942 'href' => $my_url . '/friends/',
2943 )
2944 );
2945
2946 do_action( 'friends_own_site_menu_top', $wp_menu, $my_url, $my_admin_url );
2947 do_action( 'friends_current_site_menu_top', $wp_menu, $my_url, $my_admin_url );
2948
2949 $wp_menu->add_menu(
2950 array(
2951 'id' => 'your-feed',
2952 'parent' => 'friends-menu',
2953 'title' => esc_html__( 'Main Feed', 'friends' ),
2954 'href' => home_url( '/friends/' ),
2955 )
2956 );
2957
2958 $wp_menu->add_menu(
2959 array(
2960 'id' => 'add-friend',
2961 'parent' => 'friends-menu',
2962 'title' => esc_html__( 'Add a friend', 'friends' ),
2963 'href' => home_url( '/friends/add-friend' ),
2964 )
2965 );
2966 $wp_menu->add_menu(
2967 array(
2968 'id' => 'friends',
2969 'parent' => 'friends-menu',
2970 'title' => esc_html__( 'Settings' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
2971 'href' => $my_admin_url . '/wp-admin/admin.php?page=friends-settings',
2972 )
2973 );
2974 }
2975
2976 /**
2977 * Add Friend entries to the New Content admin section
2978 *
2979 * @param \WP_Admin_Bar $wp_menu The admin bar to modify.
2980 */
2981 public function admin_bar_new_content( \WP_Admin_Bar $wp_menu ) {
2982 if ( Friends::has_required_privileges() ) {
2983 $wp_menu->add_menu(
2984 array(
2985 'id' => 'new-friend-request',
2986 'parent' => 'new-content',
2987 'title' => esc_html__( 'Friend', 'friends' ),
2988 'href' => self_admin_url( 'admin.php?page=add-friend' ),
2989 )
2990 );
2991 $wp_menu->add_menu(
2992 array(
2993 'id' => 'new-subscription',
2994 'parent' => 'new-content',
2995 'title' => esc_html__( 'Subscription', 'friends' ),
2996 'href' => self_admin_url( 'admin.php?page=add-friend' ),
2997 )
2998 );
2999 }
3000 }
3001
3002 /**
3003 * Show friends admin bar item on mobile.
3004 */
3005 public function admin_bar_mobile() {
3006 if ( ! is_user_logged_in() ) {
3007 return;
3008 }
3009 $logo_mask = "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-10 53 154 187'%3E%3Cpath d='M 132.29 90.93 C 119.28 54.95 70.12 63.99 38.89 88.85 -7.9 126.11 11.81 177.74 25.75 200.93 40.32 225.15 60.67 237.5 74.87 225.14 83.57 217.57 86.99 209.19 77.64 194.01 74.25 188.51 76.44 170.04 85.94 165.64 94.55 161.65 94.95 149.38 83.17 149.73 75.25 149.97 53.78 148.25 61.03 144.89 67.56 141.86 143.08 120.75 132.29 90.93 Z'/%3E%3C/svg%3E\") center/contain no-repeat";
3010 ?>
3011 <style type="text/css" media="screen">
3012 #wpadminbar #wp-admin-bar-friends-menu .ab-icon:before {
3013 content: "";
3014 float: left;
3015 width: 20px;
3016 height: 20px;
3017 margin-top: 2px;
3018 background-color: currentColor;
3019 -webkit-mask: <?php echo $logo_mask; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>;
3020 mask: <?php echo $logo_mask; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>;
3021 }
3022 @media screen and (max-width: 782px) {
3023 #wpadminbar #wp-admin-bar-friends-menu, #wpadminbar #wp-admin-bar-friends-menu .ab-icon {
3024 display: block !important;
3025 }
3026 #wpadminbar #wp-admin-bar-friends-menu .ab-label {
3027 display: none !important;
3028 }
3029 #wpadminbar #wp-admin-bar-friends-menu .ab-icon:before {
3030 width: 32px;
3031 height: 32px;
3032 margin-top: 6px;
3033 margin-left: 6px;
3034 }
3035 body.friends-page #wpadminbar li#wp-admin-bar-comments {
3036 display: none;
3037 }
3038 }
3039 </style>
3040 <?php
3041 }
3042
3043
3044 /**
3045 * Fires at the end of the delete users form prior to the confirm button.
3046 *
3047 * @param \WP_User $current_user \WP_User object for the current user.
3048 * @param array $userids Array of IDs for users being deleted.
3049 */
3050 public function delete_user_form( $current_user, $userids ) {
3051 $only_friends_affiliated = true;
3052 foreach ( $userids as $user_id ) {
3053 $user = new \WP_User( $user_id );
3054 if ( ! $user->has_cap( 'subscription' ) ) {
3055 $only_friends_affiliated = false;
3056 break;
3057 }
3058 }
3059
3060 if ( $only_friends_affiliated ) {
3061 ?>
3062 <script type="text/javascript">
3063 jQuery( function () {
3064 jQuery( '#delete_option1' ).closest( 'li' ).hide();
3065 } );
3066 </script>
3067 <?php
3068 }
3069 }
3070
3071 /**
3072 * Actions when a (friend) user is deleted.
3073 *
3074 * @param integer $user_id The user identifier.
3075 */
3076 public function delete_user( $user_id ) {
3077 $friend_user = User::get_user_by_id( $user_id );
3078 if ( ! $friend_user ) {
3079 return; // user was already deleted?
3080 }
3081 // Allow unsubscribing to all these feeds.
3082 foreach ( $friend_user->get_active_feeds() as $feed ) {
3083 do_action( 'friends_user_feed_deactivated', $feed );
3084 $feed->delete();
3085 }
3086
3087 // Delete the rest.
3088 foreach ( $friend_user->get_feeds() as $feed ) {
3089 $feed->delete();
3090 }
3091
3092 foreach ( $friend_user->get_all_post_ids() as $post_id ) {
3093 wp_delete_post( $post_id );
3094 }
3095 }
3096
3097 /**
3098 * Display the Bookmarklets at the Tools section of wp-admin
3099 */
3100 public function toolbox_bookmarklets() {
3101 ?>
3102 <div class="card">
3103 <h2 class="title"><?php esc_html_e( 'Friends', 'friends' ); ?></h2>
3104 <h3><?php esc_html_e( 'Bookmarklets', 'friends' ); ?></h3>
3105
3106 <p><?php esc_html_e( "Drag one of these bookmarklets to your bookmarks bar and click it when you're on a site around the web for the appropriate action.", 'friends' ); ?></p>
3107 <p>
3108 <a href="javascript:void(location.href='<?php echo esc_attr( self_admin_url( 'admin.php?page=add-friend&url=' ) ); ?>'+encodeURIComponent(location.href))" style="display: inline-block; padding: .5em; border: 1px solid #999; border-radius: 4px; background-color: #ddd;text-decoration: none; margin-right: 3em"><?php esc_html_e( 'Add friend', 'friends' ); ?></a>
3109 <a href="javascript:void(location.href='<?php echo esc_attr( self_admin_url( 'admin.php?page=add-friend&url=' ) ); ?>'+encodeURIComponent(location.href))" style="display: inline-block; padding: .5em; border: 1px solid #999; border-radius: 4px; background-color: #ddd; text-decoration: none; margin-right: 3em"><?php esc_html_e( 'Subscribe', 'friends' ); ?></a>
3110 </p>
3111 <h3><?php esc_html_e( 'Browser Extension', 'friends' ); ?></h3>
3112
3113 <p><?php esc_html_e( 'For a smoother experience, install the Friends browser extension. It adds a toolbar button to subscribe to the current site with one click, plus quick actions provided by other Friends-aware plugins.', 'friends' ); ?></p>
3114 <p>
3115 <a href="https://chromewebstore.google.com/detail/friends/ledbghpaplkpclndlommpbokndieflhl"><?php esc_html_e( 'Chrome Extension', 'friends' ); ?></a>
3116 &nbsp;·&nbsp;
3117 <a href="https://addons.mozilla.org/en-US/firefox/addon/wpfriends/"><?php esc_html_e( 'Firefox Extension', 'friends' ); ?></a>
3118 </p>
3119 </div>
3120 <?php
3121 }
3122
3123 /**
3124 * Add more "at a glance" items
3125
3126 * @param array $items Items inserted by another plugin.
3127 * @return array Items + our items.
3128 */
3129 public function dashboard_glance_items( $items ) {
3130 $subscription_count = User_Query::all_subscriptions()->get_total();
3131 $friend_post_count = wp_count_posts( Friends::CPT );
3132 $friend_post_count = $friend_post_count->publish + $friend_post_count->private;
3133
3134 if ( $subscription_count ) {
3135 // translators: %s is the number of subscriptions.
3136 $items[] = '<a class="subscriptions" href="' . self_admin_url( 'users.php?role=subscription' ) . '">' . sprintf( _n( '%s Subscription', '%s Subscriptions', $subscription_count, 'friends' ), $subscription_count ) . '</a>';
3137 }
3138
3139 if ( $friend_post_count ) {
3140 // translators: %s is the number of friend posts.
3141 $items[] = '<a class="friend-posts" href="' . home_url( '/friends/' ) . '">' . sprintf( _n( '%s Post by Friends', '%s Posts by Friends', $friend_post_count, 'friends' ), number_format_i18n( $friend_post_count ) ) . '</a>';
3142 }
3143 return $items;
3144 }
3145
3146 public function add_dashboard_widgets() {
3147 if ( ! Friends::has_required_privileges() ) {
3148 return;
3149 }
3150 $user_id = get_current_user_id();
3151 $widgets = get_user_option( 'friends_dashboard_widgets', $user_id );
3152 if ( ! $widgets ) {
3153 $widgets = array( array() );
3154 update_user_option( $user_id, 'friends_dashboard_widgets', $widgets );
3155 }
3156 foreach ( $widgets as $i => $widget ) {
3157 if ( ! is_array( $widget ) ) {
3158 continue;
3159 }
3160 $title = __( 'Latest Posts', 'friends' );
3161 if ( isset( $widget['format'] ) ) {
3162 $title = get_post_format_string( sanitize_key( $widget['format'] ) );
3163 }
3164
3165 if ( ! empty( $widget['friend'] ) ) {
3166 $user = User::get_by_username( $widget['friend'] );
3167 $title = ' by ' . $user->display_name;
3168 }
3169 $title = sprintf(
3170 // translators: %s is an author name or "Latest Posts".
3171 __( 'Friends: %s', 'friends' ),
3172 $title
3173 );
3174 wp_add_dashboard_widget( 'friends_dashboard_widget' . $i, $title, array( $this, 'render_dashboard_widget' ), array( $this, 'render_dashboard_widget_controls' ), $widget, 'side', 'high' );
3175 }
3176 }
3177
3178 public function add_new_dashboard_widget( $friend = null, $format = null ) {
3179 $user_id = get_current_user_id();
3180 $widgets = get_user_option( 'friends_dashboard_widgets', $user_id );
3181 if ( ! $widgets ) {
3182 $widgets = array();
3183 }
3184 $widget = array();
3185 if ( $friend ) {
3186 $widget['friend'] = $friend;
3187 }
3188 if ( $format ) {
3189 $widget['format'] = $format;
3190 }
3191 $widgets[] = $widget;
3192 update_user_option( $user_id, 'friends_dashboard_widgets', $widgets );
3193 }
3194
3195 public function render_dashboard_widget_controls( $id, $widget = false ) {
3196 if ( empty( $id ) && $widget ) {
3197 $id = intval( str_replace( 'friends_dashboard_widget', '', $widget['id'] ) );
3198 }
3199 $user_id = get_current_user_id();
3200 $widgets = get_user_option( 'friends_dashboard_widgets', $user_id );
3201 if ( ! $widgets ) {
3202 $widgets = array( array() );
3203 }
3204
3205 // phpcs:disable WordPress.Security.NonceVerification
3206 if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] && isset( $_POST['widget_id'] ) ) {
3207
3208 $id = intval( str_replace( 'friends_dashboard_widget', '', sanitize_text_field( wp_unslash( $_POST['widget_id'] ) ) ) );
3209 if ( isset( $_POST['add-new'] ) ) {
3210 $id = count( $widgets );
3211 $widgets[ $id ] = array();
3212 }
3213 if ( ! empty( $_POST['friend'] ) ) {
3214 $widgets[ $id ]['friend'] = sanitize_text_field( wp_unslash( $_POST['friend'] ) );
3215 } else {
3216 unset( $widgets[ $id ]['friend'] );
3217 }
3218 if ( ! empty( $_POST['format'] ) ) {
3219 $widgets[ $id ]['format'] = sanitize_text_field( wp_unslash( $_POST['format'] ) );
3220 } else {
3221 unset( $widgets[ $id ]['format'] );
3222 }
3223 if ( isset( $_POST['delete'] ) ) {
3224 unset( $widgets[ $id ] );
3225 }
3226
3227 update_user_option( $user_id, 'friends_dashboard_widgets', $widgets );
3228 }
3229 // phpcs:enable WordPress.Security.NonceVerification
3230 $args = array();
3231 if ( isset( $widgets[ $id ] ) ) {
3232 $args = $widgets[ $id ];
3233 }
3234 echo '<p>';
3235 echo '<label>';
3236 esc_html_e( 'Friend:', 'friends' );
3237 echo '<select name="friend">';
3238 echo '<option value="">' . esc_html__( 'Any Friend', 'friends' ) . '</option>';
3239 $users = User_Query::all_associated_users();
3240 foreach ( $users->get_results() as $user ) {
3241 echo '<option value="' . esc_attr( $user->user_login ) . '"';
3242 if ( isset( $args['friend'] ) && $args['friend'] === $user->user_login ) {
3243 echo ' selected="selected"';
3244 }
3245 echo '>' . esc_html( $user->display_name ) . ' (' . esc_html( $user->user_login ) . ')</option>';
3246 }
3247 echo '</select>';
3248 echo '</label>';
3249 echo '</p>';
3250 echo '<p>';
3251 echo '<label>';
3252 esc_html_e( 'Post Format:', 'friends' );
3253 echo '<select name="format">';
3254 echo '<option value="">' . esc_html__( 'Any Post Format', 'friends' ) . '</option>';
3255 foreach ( get_post_format_strings() as $format => $label ) {
3256 echo '<option value="' . esc_attr( $format ) . '"';
3257 if ( isset( $args['format'] ) && $args['format'] === $format ) {
3258 echo ' selected="selected"';
3259 }
3260 echo '>' . esc_html( $label ) . '</option>';
3261 }
3262 echo '</select>';
3263 echo '</label>';
3264 echo '</p>';
3265 echo '<p>';
3266 echo ' <button name="add-new" class="button button-secondary">' . esc_html__( 'Save as a new widget', 'friends' ) . '</button>';
3267 echo ' <button name="delete" class="button">' . esc_html__( 'Delete this widget', 'friends' ) . '</button>';
3268 echo '</p>';
3269 }
3270
3271 public function render_dashboard_widget( $args, $widget ) {
3272 $args = $widget['args'];
3273 echo '<div class="friends-dashboard-widget" data-nonce="';
3274 echo esc_attr( wp_create_nonce( 'friends-dashboard' ) );
3275 echo '"';
3276 if ( ! empty( $args['friend'] ) ) {
3277 echo ' data-friend="' . esc_attr( $args['friend'] ) . '"';
3278 }
3279 if ( ! empty( $args['format'] ) ) {
3280 echo ' data-format="' . esc_attr( $args['format'] ) . '"';
3281 }
3282 echo '></div>';
3283 }
3284
3285 public function ajax_friends_dashboard() {
3286 check_ajax_referer( 'friends-dashboard' );
3287
3288 $query_args = array();
3289 $args = array();
3290
3291 if ( isset( $_POST['friend'] ) ) {
3292 $friend = User::get_by_username( sanitize_text_field( wp_unslash( $_POST['friend'] ) ) );
3293 if ( $friend ) {
3294 $args['friend_user'] = $friend;
3295 $query_args = $friend->modify_get_posts_args_by_author( $query_args );
3296 }
3297 }
3298
3299 if ( isset( $_POST['format'] ) ) {
3300 $post_formats = get_post_format_slugs();
3301 $format = sanitize_text_field( wp_unslash( $_POST['format'] ) );
3302
3303 if ( isset( $post_formats[ $format ] ) ) {
3304 $args['post_format'] = $format;
3305 if ( 'standard' !== $format ) {
3306 $query_args['tax_query'] = array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
3307 array(
3308 'taxonomy' => 'post_format',
3309 'field' => 'slug',
3310 'terms' => array( 'post-format-' . $format ),
3311 ),
3312 );
3313 } else {
3314 $query_args['tax_query'] = array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
3315 array(
3316 'taxonomy' => 'post_format',
3317 'operator' => 'NOT EXISTS',
3318 ),
3319 );
3320 }
3321 }
3322 }
3323
3324 $any_friends = User_Query::all_associated_users();
3325
3326 ob_start();
3327 if ( 0 === $any_friends->get_total() && empty( $query_args ) ) {
3328 Friends::template_loader()->get_template_part(
3329 'admin/dashboard-widget-welcome',
3330 null,
3331 array()
3332 );
3333
3334 } else {
3335 $query_args['post_type'] = apply_filters( 'friends_frontend_post_types', array( 'post' ) );
3336 $args['posts'] = get_posts( $query_args );
3337 Friends::template_loader()->get_template_part( 'admin/dashboard-widget', null, $args );
3338 }
3339 $data = ob_get_contents();
3340 ob_end_clean();
3341
3342 wp_send_json_success(
3343 $data
3344 );
3345 }
3346
3347 public function site_status_test_php_modules( $modules ) {
3348 $modules['mbstring']['required'] = true;
3349 return $modules;
3350 }
3351
3352 public function admin_friend_posts_query( $query ) {
3353 global $wp_query, $wp, $authordata;
3354 if ( $wp_query !== $query || ! is_admin() ) {
3355 return $query;
3356 }
3357 if ( ! isset( $query->query['post_type'] ) || ! in_array( $query->query['post_type'], apply_filters( 'friends_frontend_post_types', array( 'post' ) ), true ) ) {
3358 return $query;
3359 }
3360
3361 if ( empty( $query->query['author'] ) ) {
3362 return $query;
3363 }
3364
3365 $author = User::get_user_by_id( $query->query['author'] );
3366 if ( ! $author ) {
3367 return $query;
3368 }
3369 $query->query_vars['author'] = '';
3370 $query = $author->modify_query_by_author( $query );
3371
3372 return $query;
3373 }
3374
3375 /**
3376 * Render an "ActivityPub plugin not active" notice for activitypub-parser feeds
3377 * when the ActivityPub plugin is not loaded (so Feed_Parser_ActivityPub never fires).
3378 *
3379 * @param User_Feed $feed The feed.
3380 * @param int $term_id The term ID.
3381 * @param string $parser The parser slug.
3382 */
3383 public function maybe_render_activitypub_inactive_notice( $feed, $term_id, $parser ) {
3384 if ( 'activitypub' !== $parser ) {
3385 return;
3386 }
3387
3388 if ( class_exists( '\Activitypub\Activitypub' ) ) {
3389 return;
3390 }
3391 ?>
3392 <div class="activitypub-subscription-check">
3393 <div class="ap-section-header"><?php esc_html_e( 'ActivityPub Plugin', 'friends' ); ?></div>
3394 <div class="ap-data-grid">
3395 <span class="ap-data-label"><?php esc_html_e( 'Status', 'friends' ); ?></span>
3396 <span class="ap-data-value"><em style="color: orange;"><?php esc_html_e( 'not active', 'friends' ); ?></em></span>
3397 </div>
3398 <div class="ap-section-footer">
3399 <?php
3400 if ( current_user_can( 'activate_plugins' ) ) {
3401 echo wp_kses(
3402 sprintf(
3403 /* translators: %s is a link to the plugin search page */
3404 __( 'The <a href="%s">ActivityPub plugin</a> is required to receive posts from this feed.', 'friends' ),
3405 esc_url( admin_url( 'plugin-install.php?s=activitypub&tab=search&type=term' ) )
3406 ),
3407 array( 'a' => array( 'href' => array() ) )
3408 );
3409 } else {
3410 esc_html_e( 'The ActivityPub plugin is required to receive posts from this feed.', 'friends' );
3411 }
3412 ?>
3413 </div>
3414 </div>
3415 <?php
3416 }
3417 }
3418