friends = $friends; $this->register_hooks(); } /** * Register the WordPress hooks */ private function register_hooks() { add_action( 'admin_menu', array( $this, 'admin_menu' ) ); add_filter( 'users_list_table_query_args', array( $this, 'allow_role_multi_select' ) ); add_filter( 'the_title', array( $this, 'override_post_format_title' ), 10, 2 ); add_filter( 'get_edit_user_link', array( $this, 'admin_edit_user_link' ), 10, 2 ); add_action( 'admin_bar_menu', array( $this, 'admin_bar_friends_menu' ), 39 ); add_action( 'admin_bar_menu', array( $this, 'admin_bar_new_content' ), 71 ); add_action( 'wp_head', array( $this, 'admin_bar_mobile' ) ); add_action( 'admin_head', array( $this, 'admin_bar_mobile' ) ); add_action( 'current_screen', array( $this, 'register_help' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ), 39 ); add_action( 'gettext_with_context', array( $this->friends, 'translate_user_role' ), 10, 4 ); add_action( 'wp_ajax_friends_preview_rules', array( $this, 'ajax_preview_friend_rules' ) ); add_action( 'wp_ajax_friends_fetch_feeds', array( $this, 'ajax_fetch_feeds' ) ); add_action( 'wp_ajax_friends_set_avatar', array( $this, 'ajax_set_avatar' ) ); add_action( 'wp_ajax_friends-refresh-feeds', array( $this, 'ajax_refresh_feeds' ) ); add_action( 'wp_ajax_friends-preview-subscription', array( $this, 'ajax_preview_subscription' ) ); add_action( 'wp_ajax_friends-preview-subscription-feed', array( $this, 'ajax_preview_subscription_feed' ) ); add_action( 'wp_ajax_friends-subscribe-frontend', array( $this, 'ajax_subscribe_frontend' ) ); add_action( 'delete_user_form', array( $this, 'delete_user_form' ), 10, 2 ); add_action( 'delete_user', array( $this, 'delete_user' ) ); add_action( 'remove_user_from_blog', array( $this, 'delete_user' ) ); add_action( 'tool_box', array( $this, 'toolbox_bookmarklets' ) ); add_action( 'dashboard_glance_items', array( $this, 'dashboard_glance_items' ) ); add_action( 'wp_dashboard_setup', array( $this, 'add_dashboard_widgets' ), 8 ); add_action( 'wp_ajax_friends_dashboard', array( $this, 'ajax_friends_dashboard' ) ); add_filter( 'site_status_test_php_modules', array( $this, 'site_status_test_php_modules' ) ); add_filter( 'friends_create_and_follow', array( $this, 'create_and_follow' ), 10, 4 ); add_action( 'friends_edit_feed_content_top', array( $this, 'maybe_render_activitypub_inactive_notice' ), 10, 3 ); if ( ! get_option( 'permalink_structure' ) ) { add_action( 'admin_notices', array( $this, 'admin_notice_unsupported_permalink_structure' ) ); } if ( get_option( 'friends_welcome_version' ) ) { add_action( 'admin_notices', array( $this, 'admin_notice_welcome' ) ); } add_filter( 'pre_get_posts', array( $this, 'admin_friend_posts_query' ) ); } /** * Display admin notice about an unsupported permalink structure */ public function admin_notice_unsupported_permalink_structure() { $screen = get_current_screen(); if ( 'plugins' !== $screen->id ) { return; } ?>

%2$s and enable an option other than Plain.', 'friends' ), admin_url( 'options-permalink.php' ), __( 'Permalink Settings' ) // phpcs:ignore WordPress.WP.I18n.MissingArgDomain ) ); ?>

'friends' ), wp_get_referer() ) ); exit; } $required_role = Friends::required_menu_role(); $unread_badge = $this->get_unread_badge(); $menu_title = __( 'Friends', 'friends' ) . $unread_badge; $page_type = sanitize_title( $menu_title ); $current_page = isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : ''; add_menu_page( __( 'Friends', 'friends' ), $menu_title, $required_role, 'friends', null, 'dashicons-groups', 3 ); add_submenu_page( 'friends', __( 'Friends', 'friends' ), __( 'Home', 'friends' ), $required_role, 'friends', array( $this, 'render_admin_home' ) ); add_action( 'load-' . $page_type . '_page_friends-page', array( $this, 'redirect_to_friends_page' ) ); add_submenu_page( 'friends', __( 'Add Friend', 'friends' ), __( 'Add Friend', 'friends' ), $required_role, 'add-friend', array( $this, 'render_admin_add_friend' ) ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain add_submenu_page( 'friends', __( 'Settings' ), __( 'Settings' ), $required_role, 'friends-settings', array( $this, 'render_admin_settings' ) ); if ( in_array( $current_page, apply_filters( 'friends_admin_settings_slugs', array( 'friends-settings', 'friends-notification-manager', 'friends-wp-friendships', 'friends-import-export', 'friends-migrations' ) ) ) ) { add_submenu_page( 'friends', __( 'Notifications', 'friends' ), '- ' . __( 'Notifications', 'friends' ), $required_role, 'friends-notification-manager', array( $this, 'render_admin_notification_manager' ) ); add_submenu_page( 'friends', __( 'Import/Export', 'friends' ), '- ' . __( 'Import/Export', 'friends' ), $required_role, 'friends-import-export', array( $this, 'render_admin_import_export' ) ); do_action( 'friends_admin_menu_settings', $page_type ); } if ( 'friends-migrations' === $current_page && current_user_can( 'manage_options' ) ) { add_submenu_page( 'friends', __( 'Migrations', 'friends' ), __( 'Migrations', 'friends' ), 'manage_options', 'friends-migrations', array( Migration::class, 'render_admin_page' ) ); } add_action( 'load-' . $page_type . '_page_friends-notification-manager', array( $this, 'process_admin_notification_manager' ) ); add_action( 'load-' . $page_type . '_page_friends-import-export', array( $this, 'process_admin_import_export' ) ); add_action( 'load-' . $page_type . '_page_friends-settings', array( $this, 'process_admin_settings' ) ); if ( isset( $_GET['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'friends-refresh' ) && 'friends-refresh' === $current_page ) { add_submenu_page( 'friends', __( 'Refresh', 'friends' ), __( 'Refresh', 'friends' ), $required_role, 'friends-refresh', array( $this, 'admin_refresh_friend_posts' ) ); } $friend_submenu_items = array( 'edit-friend' => __( 'Edit User', 'friends' ), 'edit-friend-feeds' => __( 'Edit Feeds', 'friends' ), 'edit-friend-notifications' => __( 'Edit Notifications', 'friends' ), 'edit-friend-rules' => __( 'Edit Rules', 'friends' ), 'duplicate-remover' => __( 'Duplicates', 'friends' ), ); if ( isset( $friend_submenu_items[ $current_page ] ) ) { foreach ( $friend_submenu_items as $slug => $title ) { $user_param = ''; if ( isset( $_GET['user'] ) ) { $username = sanitize_user( wp_unslash( $_GET['user'] ) ); $user_param = '&user=' . $username . '&_wpnonce=' . wp_create_nonce( $slug . '-' . $username ); } $slug_ = strtr( $slug, '-', '_' ); add_submenu_page( 'friends', $title, $title, $required_role, $slug . ( $slug === $current_page ? '' : $user_param ), array( $this, 'render_admin_' . $slug_ ) ); add_action( 'load-' . $page_type . '_page_' . $slug, array( $this, 'process_admin_' . $slug_ ) ); } } if ( isset( $_GET['page'] ) && 'friends-logs' === $_GET['page'] ) { // translators: as in log file. $title = __( 'Log', 'friends' ); add_submenu_page( 'friends', $title, $title, $required_role, 'friends-logs', array( $this, 'render_friends_logs' ) ); } $title = __( 'Browser Extension', 'friends' ); add_submenu_page( 'friends', $title, $title, $required_role, 'friends-browser-extension', array( $this, 'render_browser_extension' ) ); if ( isset( $_GET['page'] ) && 'unfriend' === $_GET['page'] ) { $user = new User( intval( $_GET['user'] ) ); if ( $user ) { $title = /* translators: %s is a username. */ sprintf( __( 'Unfriend %s', 'friends' ), $user->user_login ); add_submenu_page( 'friends', $title, $title, $required_role, 'unfriend', array( $this, 'render_admin_unfriend' ) ); add_action( 'load-' . $page_type . '_page_unfriend', array( $this, 'process_admin_unfriend' ) ); } } } /** * Allow making use of the role__in query. * * @param array $args The arguments. * * @return array The modified array. */ public function allow_role_multi_select( $args ) { if ( isset( $args['role'] ) && ! isset( $args['role__in'] ) ) { if ( false !== strpos( $args['role'], ',' ) ) { $args['role__in'] = explode( ',', $args['role'] ); unset( $args['role'] ); } $roles = self::get_associated_roles(); if ( ( isset( $args['role__in'] ) && array_intersect( $args['role__in'], array_keys( $roles ) ) ) || ( isset( $args['role'] ) && isset( $roles[ $args['role'] ] ) ) ) { add_action( 'admin_head-users.php', array( $this, 'keep_friends_open_on_users_screen' ) ); } } return $args; } /** * Use JavaScript to keep the Friends menu open when responding to a Friend Request. */ public function keep_friends_open_on_users_screen() { ?> id ) { case 'toplevel_page_friends-settings': $screen->add_help_tab( array( 'id' => 'overview', 'title' => __( 'Overview', 'friends' ), 'content' => '

' . __( 'Welcome to the Friends Settings! You can configure the Friends plugin here to your liking.', 'friends' ) . '

' . '

' . sprintf( // translators: %1$s is a URL, %2$s is the name of a page. __( 'There are more settings available for each friend or subscription individually. To get there, click on the user on the %2$s page.', 'friends' ), '"' . esc_attr( self::get_users_url() ) . '"', __( 'Following', 'friends' ) ) . '

', ) ); break; case 'users': $screen->add_help_tab( array( 'id' => 'friends', 'title' => __( 'Friends', 'friends' ), 'content' => '

' . __( 'Here you can find your friends and subscriptions.', 'friends' ) . '

' . __( 'If you no longer want to be friends with someone or stop a subscription, you can simply delete that user.', 'friends' ) . '

', ) ); break; } } /** * Reference our script for the /friends page */ public function admin_enqueue_scripts() { $handle = 'friends-admin'; $file = 'friends-admin.js'; $version = Friends::VERSION; 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 ); $variables = array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'add_friend_url' => self_admin_url( 'admin.php?page=add-friend' ), 'add_friend_text' => __( 'Add a Friend', 'friends' ), 'copy_text' => __( 'Copy', 'friends' ), 'copied_text' => __( 'Copied!', 'friends' ), 'delete_feed_question' => __( 'Delete the feed? You need to click "Save Changes" to really delete it.', 'friends' ), 'role_subscription' => __( 'Following', 'friends' ), 'role_connection' => __( 'Connection', 'friends' ), 'role_contact' => __( 'Contact', 'friends' ), 'role_connection_request' => __( 'Connection Request', 'friends' ), 'role_pending_connection_request' => __( 'Pending Connection Request', 'friends' ), 'role_following' => __( 'Following', 'friends' ), ); wp_localize_script( 'friends-admin', 'friends', $variables ); $handle = 'friends-admin'; $file = 'friends-admin.css'; $version = Friends::VERSION; wp_enqueue_style( $handle, plugins_url( $file, FRIENDS_PLUGIN_FILE ), array(), apply_filters( 'friends_debug_enqueue', $version, $handle, dirname( FRIENDS_PLUGIN_FILE ) . '/' . $file ) ); } /** * Admin menu to refresh the friend posts. */ public function admin_refresh_friend_posts() { ?>

', '' . esc_html( $friend_user->user_login ) . '', '' . esc_html( $feed_url ) . '' ), array( 'a' => array( 'href' => array(), ), ) ); return $feed_url; }, 10, 2 ); add_action( 'friends_retrieved_new_posts', function ( $user_feed, $new_posts, $modified_posts ) { // translators: %s is the number of new posts found. echo esc_html( sprintf( _n( 'Found %d new post.', 'Found %d new posts.', count( $new_posts ), 'friends' ), count( $new_posts ) ) ); ?>

get_error_message() ), '
'; }, 10, 2 ); if ( isset( $_GET['user'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification $friend_user = User::get_by_username( sanitize_user( wp_unslash( $_GET['user'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification if ( ! $friend_user || is_wp_error( $friend_user ) || ! $friend_user->can_refresh_feeds() ) { wp_die( esc_html__( 'Invalid user ID.' ) ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain } $friend_user->retrieve_posts_from_active_feeds(); } else { $this->friends->feed->retrieve_friend_posts(); } } /** * Don't show the edit link for friend posts * * @param string $link The edit link. * @param int|User $user The user. * @return string|bool The edit link or false. */ public static function admin_edit_user_link( $link, $user ) { static $cache = array(); if ( $user instanceof \WP_User ) { $cache_key = $user->ID; } else { $cache_key = $user; } if ( isset( $cache[ $cache_key ] ) ) { if ( false === $cache[ $cache_key ] ) { return $link; } return $cache[ $cache_key ]; } if ( ! $user instanceof \WP_User ) { if ( is_string( $user ) ) { $user = User::get_by_username( $user ); } else { $user = new \WP_User( $user ); } } if ( ! $user || is_wp_error( $user ) ) { $cache[ $cache_key ] = false; return $link; } if ( is_multisite() && is_super_admin( $user->ID ) ) { $cache[ $cache_key ] = false; return $link; } if ( ! $user->has_cap( 'friends_plugin' ) ) { $cache[ $cache_key ] = false; return $link; } $cache[ $cache_key ] = self_admin_url( 'admin.php?page=edit-friend&user=' . $user->user_login ); return $cache[ $cache_key ]; } public static function get_edit_friend_link( $user ) { if ( is_string( $user ) ) { $user = User::get_by_username( $user ); } elseif ( ! $user instanceof User && ! $user instanceof Subscription ) { $user = new User( $user ); } if ( ! $user || is_wp_error( $user ) ) { return ''; } return apply_filters( 'get_edit_user_link', $user->user_url, $user->user_login ); } public static function get_unfriend_link( $user ) { if ( ! $user->has_cap( 'friends_plugin' ) ) { return ''; } return wp_nonce_url( self_admin_url( 'admin.php?page=unfriend&user=' . $user->user_login ), 'unfriend_' . $user->user_login ); } /** * Redirect to the Friends page */ public function redirect_to_friends_page() { wp_safe_redirect( home_url( '/friends/' ) ); exit; } /** * Check access for the Friends Admin settings page */ public function check_admin_settings() { if ( ! Friends::has_required_privileges() ) { wp_die( esc_html__( 'Sorry, you are not allowed to change the settings.', 'friends' ) ); } } /** * Process the Friends Admin settings page */ public function process_admin_settings() { if ( empty( $_REQUEST ) || ! isset( $_REQUEST['_wpnonce'] ) ) { return; } if ( ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'friends-settings' ) ) { return; } $this->check_admin_settings(); foreach ( array( 'disable_auto_tagging', 'disable_link_previews' ) as $checkbox ) { if ( isset( $_POST[ $checkbox ] ) && boolval( $_POST[ $checkbox ] ) ) { update_option( 'friends_' . $checkbox, true ); } else { delete_option( 'friends_' . $checkbox ); } } if ( current_user_can( 'manage_options' ) ) { if ( isset( $_POST['main_user_id'] ) ) { $main_user_id = absint( $_POST['main_user_id'] ); if ( $main_user_id && user_can( $main_user_id, Friends::REQUIRED_ROLE ) ) { update_option( 'friends_main_user_id', $main_user_id ); } } foreach ( array( 'force_enable_post_formats', 'expose_post_format_feeds', 'exclude_compose_format_from_feed' ) as $checkbox ) { if ( isset( $_POST[ $checkbox ] ) && boolval( $_POST[ $checkbox ] ) ) { update_option( 'friends_' . $checkbox, true ); } else { delete_option( 'friends_' . $checkbox ); } } $post_format_slugs = get_post_format_slugs(); if ( isset( $_POST['friends_compose_post_format'] ) && in_array( sanitize_key( $_POST['friends_compose_post_format'] ), array_merge( array( 'standard' ), $post_format_slugs ), true ) ) { update_option( 'friends_compose_post_format', sanitize_key( $_POST['friends_compose_post_format'] ) ); } else { delete_option( 'friends_compose_post_format' ); } } if ( isset( $_POST['available_emojis'] ) && is_array( $_POST['available_emojis'] ) ) { $available_emojis = array(); foreach ( wp_unslash( $_POST['available_emojis'] ) as $id ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput $id = sanitize_key( $id ); $data = Reactions::get_emoji_data( $id ); if ( $data ) { $available_emojis[ $id ] = $data; } } update_option( 'friends_selected_emojis', $available_emojis ); } else { delete_option( 'friends_selected_emojis' ); } // Global retention. $retention_number_enabled = boolval( isset( $_POST['friends_enable_retention_number'] ) && $_POST['friends_enable_retention_number'] ); update_option( 'friends_enable_retention_number', $retention_number_enabled ); if ( $retention_number_enabled && isset( $_POST['friends_retention_number'] ) ) { update_option( 'friends_retention_number', max( 1, intval( $_POST['friends_retention_number'] ) ) ); } $retention_days_enabled = boolval( isset( $_POST['friends_enable_retention_days'] ) && $_POST['friends_enable_retention_days'] ); update_option( 'friends_enable_retention_days', $retention_days_enabled ); if ( $retention_days_enabled && isset( $_POST['friends_retention_days'] ) ) { update_option( 'friends_retention_days', max( 1, intval( $_POST['friends_retention_days'] ) ) ); } if ( isset( $_POST['retention_delete_reacted'] ) && 1 === intval( $_POST['retention_delete_reacted'] ) ) { delete_option( 'friends_retention_delete_reacted' ); } else { update_option( 'friends_retention_delete_reacted', true ); } if ( isset( $_POST['frontend_default_view'] ) && in_array( wp_unslash( $_POST['frontend_default_view'] ), array( 'collapsed', ) ) ) { update_user_option( get_current_user_id(), 'friends_frontend_default_view', wp_unslash( $_POST['frontend_default_view'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput } else { delete_user_option( get_current_user_id(), 'friends_frontend_default_view' ); } foreach ( array_merge( array( '' ), get_post_format_slugs() ) as $post_type ) { $name = 'friends_frontend_theme'; if ( $post_type ) { $name = 'friends_frontend_theme_' . $post_type; } $theme = 'default'; if ( isset( $_POST[ $name ] ) && in_array( $theme, array_keys( Frontend::get_themes() ) ) ) { $theme = wp_unslash( $_POST[ $name ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput } if ( 'default' === $theme ) { delete_user_option( get_current_user_id(), $name ); } else { update_user_option( get_current_user_id(), $name, $theme ); } } $redirect_args = array( 'updated' => '1' ); if ( isset( $_GET['_wp_http_referer'] ) ) { wp_safe_redirect( wp_get_referer() ); } else { wp_safe_redirect( add_query_arg( $redirect_args, remove_query_arg( array( '_wp_http_referer', '_wpnonce' ) ) ) ); } exit; } /** * Gets the frontend locale. * * @return string The frontend locale. */ public function get_frontend_locale() { $locale = get_option( 'WPLANG' ); return empty( $locale ) ? 'en_US' : $locale; } /** * Get the registry of news entries, newest first. * * Each entry has: version, title, template, and optionally migration_version * for entries that should show migration status. * * @return array */ public static function get_news_entries() { return apply_filters( 'friends_news_entries', array( array( 'version' => '4.3', 'title' => __( '4.3: Link Previews', 'friends' ), 'template' => 'admin/news-4-3', ), array( 'version' => '4.2', 'title' => __( '4.2: Direct Messages', 'friends' ), 'template' => 'admin/news-4-2', ), array( 'version' => '4.1', 'title' => __( '4.1: Add Friend Frontend, Twitter Theme & Browser Extension', 'friends' ), 'template' => 'admin/news-4-1', ), array( 'version' => '4.0', 'title' => __( '4.0: A Major Update', 'friends' ), 'template' => 'admin/welcome-4-0', 'migration_version' => '4.0.0', ), array( 'version' => '3.3', 'title' => __( '3.3: Styling Overhaul', 'friends' ), 'template' => 'admin/news-3-3', ), array( 'version' => '3.0', 'title' => __( '3.0: Followers & Notifications', 'friends' ), 'template' => 'admin/news-3-0', ), array( 'version' => '2.4', 'title' => __( '2.4: Mastodon Compatibility', 'friends' ), 'template' => 'admin/news-2-4', ), array( 'version' => '2.1', 'title' => __( '2.1: Frontend & Plugins', 'friends' ), 'template' => 'admin/news-2-1', ), array( 'version' => '2.0', 'title' => __( '2.0: Revisions & Site Health', 'friends' ), 'template' => 'admin/news-2-0', ), array( 'version' => '0', 'title' => __( 'Welcome to the Friends Plugin!', 'friends' ), 'template' => 'admin/welcome', ), ) ); } /** * Get migration statuses for a specific version. * * @param string $migration_version The version to filter migrations for. * @return array With keys: statuses, all_complete, has_in_progress. */ public static function get_migration_data( $migration_version ) { $all_statuses = Migration::get_all_statuses(); $statuses = array(); $all_complete = true; $has_in_progress = false; foreach ( $all_statuses as $id => $status ) { if ( $status['version'] !== $migration_version ) { continue; } $statuses[ $id ] = $status; if ( empty( $status['completed'] ) ) { $all_complete = false; } if ( ! empty( $status['in_progress'] ) ) { $has_in_progress = true; } } return array( 'statuses' => $statuses, 'all_complete' => $all_complete, 'has_in_progress' => $has_in_progress, ); } /** * Render the Friends Admin home page. * * Shows the welcome page for new users (no subscriptions), * or a news/changelog view for existing users. */ public function render_admin_home() { // Dismiss the update notice permanently when visiting this page. if ( get_option( 'friends_welcome_version' ) ) { delete_option( 'friends_welcome_version' ); } $friends_subscriptions = User_Query::all_associated_users(); $is_new_user = 0 === $friends_subscriptions->get_total(); wp_enqueue_script( 'plugin-install' ); add_thickbox(); wp_enqueue_script( 'updates' ); Friends::template_loader()->get_template_part( 'admin/settings-header', null, array( 'active' => 'friends', ) ); $news_entries = self::get_news_entries(); if ( $is_new_user ) { // New users: welcome entry first, rest after. $news_entries = array_reverse( $news_entries ); } Friends::template_loader()->get_template_part( 'admin/news', null, array( 'entries' => $news_entries, ) ); Friends::template_loader()->get_template_part( 'admin/settings-footer' ); } /** * Process the response after adding a friend/subscription. * * @param User|\WP_Error $friend_user The friend user object. * @param array $vars The form variables. * * @return bool Whether the operation was successful. */ private function process_admin_add_friend_response( $friend_user, $vars ) { if ( is_wp_error( $friend_user ) ) { $this->display_errors( $friend_user ); return false; } if ( ! $friend_user instanceof User ) { ?>

save_feeds( $feed_options ); if ( ! isset( $vars['subscribe'] ) ) { $vars['subscribe'] = array(); } $count = 0; foreach ( $vars['subscribe'] as $feed_url ) { if ( ! isset( $feed_options[ $feed_url ] ) ) { continue; } $new_feed = $friend_user->subscribe( $feed_url, $feed_options[ $feed_url ] ); if ( ! is_wp_error( $new_feed ) ) { do_action( 'friends_user_feed_activated', $new_feed ); ++$count; } } add_filter( 'notify_about_new_friend_post', '__return_false', 999 ); wp_schedule_single_event( time(), 'friends_retrieve_user_feeds', array( $friend_user->ID ) ); $friend_link = '' . esc_html( $friend_user->display_name ) . ''; // translators: %s is a Site URL. $message = sprintf( __( "You're now subscribed to %s.", 'friends' ), $friend_link ); ?>

array( 'href' => array() ) ) ); // translators: %s is the friends page URL. echo ' ', wp_kses( sprintf( __( 'Go to your friends page to view their posts.', 'friends' ), '"' . esc_url( $friend_user->get_local_friends_page_url() ) . '"' ), array( 'a' => array( 'href' => array() ) ) ); echo ' ', esc_html__( 'Fetching feeds...', 'friends' ), ''; ?>

ID ); $friend_url = set_url_scheme( $site->siteurl ); } } if ( ! $friend_user ) { $friend_url = apply_filters( 'friends_rewrite_incoming_url', 'https://' . $friend_url, $friend_url ); } } $friend_user_login = apply_filters( 'friends_suggest_user_login', User::get_user_login_for_url( $friend_url ), $friend_url ); $friend_display_name = apply_filters( 'friends_suggest_display_name', User::get_display_name_for_url( $friend_url ), $friend_url ); $friend_user = get_user_by( 'login', $friend_user_login ); $args = array(); if ( $friend_user ) { $args['friends_multisite_user_login'] = $friend_user_login; $args['friends_multisite_display_name'] = $friend_display_name; } if ( ( isset( $vars['step2'] ) && isset( $vars['feeds'] ) && is_array( $vars['feeds'] ) ) || isset( $vars['step3'] ) ) { $friend_user_login = trim( str_replace( ' ', '-', sanitize_user( $vars['user_login'] ) ), '-' ); $friend_display_name = sanitize_text_field( $vars['display_name'] ); if ( ! $friend_user_login ) { // phpcs:ignore WordPress.WP.I18n.MissingArgDomain $errors->add( 'user_login', __( 'Error: This username is invalid because it uses illegal characters. Please enter a valid username.' ) ); } elseif ( ! is_multisite() && username_exists( $friend_user_login ) ) { // phpcs:ignore WordPress.WP.I18n.MissingArgDomain $errors->add( 'user_login', __( 'Error: This username is already registered. Please choose another one.' ) ); } $feeds = $vars['feeds']; if ( ! $errors->has_errors() ) { $avatar = null; $description = null; foreach ( $feeds as $feed_details ) { if ( ! $avatar && ! empty( $feed_details['avatar'] ) ) { $avatar = $feed_details['avatar']; } if ( ! $description && ! empty( $feed_details['description'] ) ) { $description = wp_encode_emoji( $feed_details['description'] ); } } $friend_user = User::create( $friend_user_login, 'subscription', $friend_url, $friend_display_name, $avatar, $description ); return $this->process_admin_add_friend_response( $friend_user, $vars ); } } else { if ( str_starts_with( $friend_url, home_url() ) ) { return new \WP_Error( 'friend-yourself', __( 'It seems like you sent a friend request to yourself.', 'friends' ) ); } if ( preg_match( '#https://.*?@threads.net#', $friend_url ) ) { return new \WP_Error( 'threads-net', sprintf( // translators: %s is a URL. __( '⚠️ This user has not enabled Fediverse sharing on their Threads.net account.', 'friends' ), 'https://about.fb.com/news/2023/07/introducing-threads-new-app-text-sharing/' ) ); } if ( ! Friends::check_url( $friend_url ) ) { return new \WP_Error( 'invalid-url', __( 'You entered an invalid URL.', 'friends' ) ); } $friend_user = User::get_user( $friend_user_login ); if ( $friend_user && ! is_wp_error( $friend_user ) ) { // translators: %s is the name of a friend / site. return new \WP_Error( 'already-subscribed', sprintf( __( 'You are already subscribed to this site: %s', 'friends' ), '' . esc_html( $friend_user->display_name ) . '' ) ); } $feeds = $this->friends->feed->discover_available_feeds( $friend_url ); if ( is_wp_error( $feeds ) ) { return $feeds; } if ( ! $feeds ) { return new \WP_Error( 'no-feed-found', __( 'No suitable feed was found at the provided address.', 'friends' ) ); } $has_subscribable_feeds = false; $has_threads_net = false; foreach ( $feeds as $url => $feed ) { if ( 0 === strpos( $url, 'https://threads.net/' ) ) { $has_threads_net = true; } if ( isset( $feed['autoselect'] ) && $feed['autoselect'] ) { $has_subscribable_feeds = true; break; } if ( 'unsupported' !== $feed['parser'] ) { $has_subscribable_feeds = true; break; } } if ( ! $has_subscribable_feeds && $has_threads_net ) { $args['feeds_notice'] = sprintf( // translators: %s is a URL. __( '⚠️ This user has not enabled Fediverse sharing on their Threads.net account.', 'friends' ), 'https://about.fb.com/news/2023/07/introducing-threads-new-app-text-sharing/' ); } $better_user_login = User::get_user_login_from_feeds( $feeds ); if ( $better_user_login ) { $friend_user_login = trim( $better_user_login, '-' ); } $better_display_name = User::get_display_name_from_feeds( $feeds ); if ( $better_display_name ) { $friend_display_name = $better_display_name; if ( ! $better_user_login ) { $friend_user_login = trim( strtolower( str_replace( ' ', '-', sanitize_user( $better_display_name ) ) ), '-' ); } } } if ( isset( $vars['quick-subscribe'] ) ) { $vars['feeds'] = $feeds; $vars['subscribe'] = array(); foreach ( $feeds as $feed_url => $details ) { if ( isset( $details['autoselect'] ) && $details['autoselect'] ) { $vars['subscribe'][] = $feed_url; } } $avatar = null; $description = null; foreach ( $feeds as $feed_details ) { if ( ! $avatar && ! empty( $feed_details['avatar'] ) ) { $avatar = $feed_details['avatar']; } if ( ! $description && ! empty( $feed_details['description'] ) ) { $description = $feed_details['description']; } } $friend_user = User::create( $friend_user_login, 'subscription', $friend_url, $friend_display_name, $avatar, $description ); return $this->process_admin_add_friend_response( $friend_user, $vars ); } Friends::template_loader()->get_template_part( 'admin/settings-header', null, array( 'active' => 'add-friend-confirm', 'title' => __( 'Add Friend', 'friends' ), 'menu' => array( '1. ' . __( 'Enter Details', 'friends' ) => array( 'page' => 'add-friend', 'url' => ! empty( $friend_url ) ? $friend_url : false, ), '2. ' . __( 'Confirm', 'friends' ) => 'add-friend-confirm', ), ) ); if ( $errors->has_errors() ) { ?>

get_error_message(), array( 'strong' => array() ) ); ?>

get_template_part( 'admin/select-feeds', null, array_merge( $args, array( 'friend_url' => $friend_url, 'friend_user_login' => $friend_user_login, 'friend_display_name' => $friend_display_name, 'post_formats' => array_merge( array( 'autodetect' => __( 'Autodetect Post Format', 'friends' ) ), get_post_format_strings() ), 'registered_parsers' => $this->friends->feed->get_registered_parsers(), 'feeds' => $feeds, ) ) ); } /** * Render the admin form for following someone. */ public function render_admin_add_friend() { if ( ! Friends::has_required_privileges() ) { wp_die( esc_html__( 'Sorry, you are not allowed to do this.', 'friends' ) ); } if ( ! empty( $_GET['preview'] ) ) { $url = sanitize_text_field( wp_unslash( $_GET['preview'] ) ); ?>

friends->feed->get_registered_parser( sanitize_text_field( wp_unslash( $_GET['parser'] ) ) ); $parser = $this->friends->feed->get_feed_parser( sanitize_text_field( wp_unslash( $_GET['parser'] ) ) ); } if ( ! $parser ) { ?>

friends->feed->preview( $parser, $url, $feed_id ); if ( is_wp_error( $items ) ) { ?>

get_error_message() ); ?>

process_admin_add_friend( $postdata ); } if ( is_wp_error( $response ) ) { ?>

get_error_message(); if ( $response->get_error_data() ) { $message .= ' (' . $response->get_error_data() . ')'; } echo wp_kses( $message, array( 'strong' => array(), 'a' => array( 'href' => array(), 'rel' => array(), 'target' => array(), ), ) ); ?>

'', 'add-friends-placeholder' => apply_filters( 'friends_add_friends_input_placeholder', __( 'Enter URL', 'friends' ) ), ); if ( ! empty( $_REQUEST['url'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $friend_url = sanitize_text_field( wp_unslash( $_REQUEST['url'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $parsed_url = wp_parse_url( $friend_url ); if ( isset( $parsed_url['host'] ) ) { if ( ! isset( $parsed_url['scheme'] ) ) { $args['friend_url'] = apply_filters( 'friends_rewrite_incoming_url', 'https://' . ltrim( $friend_url, '/' ), $friend_url, $parsed_url ); } else { $args['friend_url'] = $friend_url; } } elseif ( class_exists( 'Friends\Feed_Parser_ActivityPub' ) && preg_match( '/^@?' . Feed_Parser_ActivityPub::ACTIVITYPUB_USERNAME_REGEXP . '$/i', $friend_url ) ) { $args['friend_url'] = $friend_url; } } Friends::template_loader()->get_template_part( 'admin/settings-header', null, array( 'active' => 'add-friend', 'title' => __( 'Add Friend', 'friends' ), 'menu' => array( '1. ' . __( 'Enter Details', 'friends' ) => array( 'page' => 'add-friend', 'url' => ! empty( $friend_url ) ? $friend_url : false, ), '2. ' . __( 'Confirm', 'friends' ) => false, ), ) ); Friends::template_loader()->get_template_part( 'admin/add-friend', null, $args ); Friends::template_loader()->get_template_part( 'admin/latest-friends', null, array( 'friend_requests' => User_Query::recent_friends_subscriptions( 25 )->get_results(), ) ); Friends::template_loader()->get_template_part( 'admin/settings-footer', null, $args ); } /** * Display admin notice about a new version. */ public function admin_notice_welcome() { if ( ! current_user_can( 'manage_options' ) ) { return; } if ( isset( $_GET['page'] ) && 'friends' === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } $version = get_option( 'friends_welcome_version' ); $url = admin_url( 'admin.php?page=friends' ); ?>

See what\'s new and check the migration status.', 'friends' ), esc_html( $version ), esc_url( $url ) ), array( 'a' => array( 'href' => array() ) ) ); ?>

get_template_part( 'admin/settings-header', null, array( 'active' => 'friends-settings', ) ); $this->check_admin_settings(); if ( isset( $_GET['updated'] ) && boolval( $_GET['updated'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification ?>

get_template_part( 'admin/settings', null, array_merge( Friends::get_post_stats(), $post_type_themes, array( 'force_enable_post_formats' => get_option( 'friends_force_enable_post_formats' ), 'post_format_strings' => get_post_format_strings(), 'limit_homepage_post_format' => get_option( 'friends_limit_homepage_post_format', false ), 'expose_post_format_feeds' => get_option( 'friends_expose_post_format_feeds' ), 'compose_post_format' => get_option( 'friends_compose_post_format', 'status' ), 'exclude_compose_format_from_feed' => get_option( 'friends_exclude_compose_format_from_feed' ), 'main_user_id' => Friends::get_main_friend_user_id(), 'potential_main_users' => User_Query::all_admin_users(), 'disable_auto_tagging' => get_option( 'friends_disable_auto_tagging' ), 'disable_link_previews' => get_option( 'friends_disable_link_previews' ), 'retention_days' => Friends::get_retention_days(), 'retention_number' => Friends::get_retention_number(), 'retention_days_enabled' => get_option( 'friends_enable_retention_days' ), 'retention_number_enabled' => get_option( 'friends_enable_retention_number' ), 'retention_delete_reacted' => get_option( 'friends_retention_delete_reacted' ), 'frontend_default_view' => get_user_option( 'friends_frontend_default_view', get_current_user_id() ), 'frontend_theme' => get_user_option( 'friends_frontend_theme' ), ) ) ); Friends::template_loader()->get_template_part( 'admin/settings-footer' ); } /** * Process access for the Friends Edit Rules page */ private function check_admin_edit_friend_rules() { if ( ! Friends::is_main_user() ) { wp_die( esc_html__( 'Sorry, you are not allowed to edit the rules.', 'friends' ) ); } if ( ! isset( $_GET['user'] ) ) { wp_die( esc_html__( 'Invalid user.', 'friends' ) ); } if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'edit-friend-rules-' . sanitize_user( wp_unslash( $_GET['user'] ) ) ) ) { wp_die( esc_html__( 'Invalid nonce.', 'friends' ) ); } $friend = User::get_by_username( sanitize_user( wp_unslash( $_GET['user'] ) ) ); if ( ! $friend || is_wp_error( $friend ) ) { wp_die( esc_html__( 'Invalid username.', 'friends' ) ); } if ( ! $friend->has_cap( 'subscription' ) ) { wp_die( esc_html__( 'This is not a user related to this plugin.', 'friends' ) ); } return $friend; } /** * Process the Friends Edit Rules page */ public function process_admin_edit_friend_rules() { $friend = $this->check_admin_edit_friend_rules(); $arg = 'updated'; $arg_value = 1; if ( isset( $_POST['_wpnonce'] ) && ! empty( $_POST['friend-rules-raw'] ) && wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), 'friend-rules-raw-' . $friend->user_login ) ) { $rules = validate_feed_rules( wp_unslash( $_POST['friend-rules-raw'] ) ); if ( false === $rules ) { $arg = 'error'; } else { $friend->update_user_option( 'friends_feed_rules', $rules ); } } 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 ) ) ) { $friend->update_user_option( 'friends_feed_catch_all', validate_feed_catch_all( wp_unslash( $_POST['catch_all'] ) ) ); $friend->update_user_option( 'friends_feed_rules', validate_feed_rules( wp_unslash( $_POST['rules'] ) ) ); } else { return; } if ( isset( $_GET['_wp_http_referer'] ) ) { wp_safe_redirect( wp_get_referer() ); } else { wp_safe_redirect( add_query_arg( $arg, $arg_value, remove_query_arg( '_wp_http_referer' ) ) ); } exit; } /** * Render the Friends Edit Rules page */ public function render_admin_edit_friend_rules() { $friend = $this->check_admin_edit_friend_rules(); $catch_all = $friend->get_feed_catch_all(); $rules = $friend->get_feed_rules(); $this->header_edit_friend( $friend, 'edit-friend-rules' ); if ( isset( $_GET['updated'] ) ) { ?>

'title', 'regex' => '', 'action' => in_array( $catch_all, array( 'trash', 'delete' ), true ) ? 'accept' : 'trash', 'replace' => '', ); if ( isset( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'edit-friend-rules-' . sanitize_user( $friend->user_login ) ) ) { if ( isset( $_GET['post'] ) && intval( $_GET['post'] ) ) { $post = get_post( intval( $_GET['post'] ) ); } else { $post = null; } } $args = array( 'rules' => $rules, 'friend' => $friend, 'catch_all' => $catch_all, 'post' => $post, ); Friends::template_loader()->get_template_part( 'admin/edit-rules', null, $args ); echo '
'; $this->render_preview_friend_rules( $rules, $catch_all, $post ); echo '
'; array_pop( $args['rules'] ); Friends::template_loader()->get_template_part( 'admin/edit-raw-rules', null, $args ); } /** * Respond to the Ajax request to the Friend rules preview */ public function ajax_preview_friend_rules() { if ( ! Friends::has_required_privileges() ) { wp_die( -1 ); } if ( ! isset( $_GET['user'] ) ) { wp_die( esc_html__( 'Invalid user.', 'friends' ) ); } check_ajax_referer( 'edit-friend-rules-' . sanitize_user( wp_unslash( $_GET['user'] ) ) ); if ( isset( $_GET['post'] ) && intval( $_GET['post'] ) ) { $post = get_post( intval( $_GET['post'] ) ); } else { $post = null; } $rules = array(); if ( isset( $_POST['rules'] ) ) { $rules = validate_feed_rules( wp_unslash( $_POST['rules'] ) ); } $catch_all = array(); if ( isset( $_POST['catch_all'] ) ) { $catch_all = validate_feed_rules( wp_unslash( $_POST['catch_all'] ) ); } $this->render_preview_friend_rules( $rules, $catch_all, $post ); wp_die( 1 ); } /** * Respond to the Ajax request to fetch feeds */ public function ajax_fetch_feeds() { if ( ! isset( $_POST['friend'] ) ) { wp_send_json_error( 'missing-parameters' ); } check_ajax_referer( 'fetch-feeds-' . sanitize_user( wp_unslash( $_POST['friend'] ) ) ); $friend_user = User::get_by_username( sanitize_user( wp_unslash( $_POST['friend'] ) ) ); if ( ! $friend_user ) { wp_send_json_error( 'unknown-user' ); } add_filter( 'notify_about_new_friend_post', '__return_false', 999 ); $friend_user->retrieve_posts_from_active_feeds(); wp_send_json_success(); } /** * Render the Friend rules preview * * @param array $rules The rules to apply. * @param string $catch_all The catch all behavior. * @param \WP_Post $post The post. */ public function render_preview_friend_rules( $rules, $catch_all, ?\WP_Post $post = null ) { $friend = $this->check_admin_edit_friend_rules(); $friend_posts = new \WP_Query(); $friend_posts->set( 'post_type', Friends::CPT ); $friend_posts->set( 'post_status', array( 'publish', 'private', 'trash' ) ); $friend_posts->set( 'posts_per_page', 25 ); $friend_posts = $friend->modify_query_by_author( $friend_posts ); $args = array( 'friend' => $friend, 'friend_posts' => $friend_posts, 'feed' => $this->friends->feed, 'post' => $post, ); $friend->set_feed_rules( $rules ); $friend->set_feed_catch_all( $catch_all ); Friends::template_loader()->get_template_part( 'admin/preview-rules', null, $args ); } /** * Process access for the Friends Edit User page */ private function check_admin_edit_friend() { if ( ! friends::has_required_privileges() ) { wp_die( esc_html__( 'Sorry, you are not allowed to edit this user.' ) ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain } if ( ! isset( $_GET['user'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification wp_die( esc_html__( 'Invalid user.', 'friends' ) ); } $friend = User::get_by_username( sanitize_user( wp_unslash( $_GET['user'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification if ( ! $friend || is_wp_error( $friend ) ) { wp_die( esc_html__( 'Invalid username.', 'friends' ) ); } if ( ! $friend->has_cap( 'friends_plugin' ) ) { wp_die( esc_html__( 'This is not a user related to this plugin.', 'friends' ) ); } return $friend; } /** * Process the Friends Edit User page */ public function process_admin_edit_friend() { $friend = $this->check_admin_edit_friend(); $arg = 'updated'; $arg_value = 1; if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), 'edit-friend-' . $friend->user_login ) ) { if ( isset( $_POST['friends_display_name'] ) ) { $friends_display_name = trim( sanitize_text_field( wp_unslash( $_POST['friends_display_name'] ) ) ); if ( $friends_display_name ) { $friend->first_name = $friends_display_name; $friend->display_name = $friends_display_name; } } if ( isset( $_POST['friends_description'] ) ) { $friend->description = trim( sanitize_text_field( wp_unslash( $_POST['friends_description'] ) ) ); } if ( isset( $_POST['user_url'] ) ) { $user_url = sanitize_text_field( wp_unslash( $_POST['user_url'] ) ); if ( filter_var( $user_url, FILTER_VALIDATE_URL ) ) { $friend->user_url = $user_url; } } if ( isset( $_POST['friends_user_login'] ) ) { $new_user_login = User::sanitize_username( sanitize_text_field( wp_unslash( $_POST['friends_user_login'] ) ) ); if ( $new_user_login && $new_user_login !== $friend->user_login ) { $friend->update_user_login( $new_user_login ); } } $friend->save(); } else { return; } do_action( 'friends_edit_friend_after_form_submit', $friend ); $redirect_url = self_admin_url( 'admin.php?page=edit-friend&user=' . $friend->user_login ); wp_safe_redirect( add_query_arg( $arg, rawurlencode( $arg_value ), $redirect_url ) ); exit; } /** * The Friends Edit User header * * @param User $friend The friend. * @param string $active The active menu entry. */ public function header_edit_friend( User $friend, $active ) { $append = '&user=' . sanitize_user( $friend->user_login ); Friends::template_loader()->get_template_part( 'admin/settings-header', null, array( 'active' => $active . $append, 'title' => $friend->user_login, 'menu' => array( __( 'Posts' ) => $friend->get_local_friends_page_url(), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain __( 'Settings' ) => 'edit-friend' . $append, // phpcs:ignore WordPress.WP.I18n.MissingArgDomain __( 'Feeds', 'friends' ) => 'edit-friend-feeds' . $append, __( 'Notifications', 'friends' ) => 'edit-friend-notifications' . $append, __( 'Rules', 'friends' ) => 'edit-friend-rules' . $append . '&_wpnonce=' . wp_create_nonce( 'edit-friend-rules-' . $friend->user_login ), ), ) ); } /** * Render the Friends Edit User page */ public function render_admin_edit_friend() { $friend = $this->check_admin_edit_friend(); $args = array_merge( $friend->get_post_stats(), array( 'friend' => $friend, 'friends_settings_url' => add_query_arg( '_wp_http_referer', remove_query_arg( '_wp_http_referer' ), self_admin_url( 'admin.php?page=friends-settings' ) ), 'registered_parsers' => $this->friends->feed->get_registered_parsers(), ) ); $this->header_edit_friend( $friend, 'edit-friend' ); // phpcs:disable WordPress.Security.NonceVerification if ( isset( $_GET['updated'] ) ) { ?>

get_template_part( 'admin/edit-friend', null, $args ); } public function ajax_refresh_feeds() { check_ajax_referer( 'friends-refresh' ); if ( ! Friends::has_required_privileges() ) { wp_send_json_error( __( 'You do not have permission to do this.', 'friends' ) ); } add_filter( 'notify_about_new_friend_post', '__return_false', 999 ); if ( ! empty( $_POST['user'] ) ) { $friend_user = User::get_by_username( sanitize_user( wp_unslash( $_POST['user'] ) ) ); if ( ! $friend_user || is_wp_error( $friend_user ) || ! $friend_user->can_refresh_feeds() ) { wp_send_json_error( __( 'Invalid user ID.' ) ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain } $friend_user->retrieve_posts_from_active_feeds(); } else { $this->friends->feed->retrieve_friend_posts(); } wp_send_json_success(); } private function normalize_frontend_subscription_url( $url ) { if ( ! is_string( $url ) ) { return ''; } $url = trim( $url ); if ( '' === $url ) { return ''; } $protocol = wp_parse_url( $url, PHP_URL_SCHEME ); if ( ! $protocol ) { return apply_filters( 'friends_rewrite_incoming_url', 'https://' . $url, $url ); } return apply_filters( 'friends_rewrite_incoming_url', $url, $url ); } public function ajax_preview_subscription() { if ( ! isset( $_POST['url'] ) || is_array( $_POST['url'] ) ) { wp_send_json_error( __( 'No URL provided.', 'friends' ) ); } check_ajax_referer( 'friends_add_subscription' ); if ( ! Friends::has_required_privileges() ) { wp_send_json_error( __( 'You do not have permission to do this.', 'friends' ) ); } $incoming_url = trim( wp_unslash( $_POST['url'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( ! class_exists( '\Activitypub\Activitypub' ) && preg_match( '/^@?[A-Za-z0-9_.-]+@(?:[A-Za-z0-9_-]+\.)+[A-Za-z]+$/i', $incoming_url ) ) { wp_send_json_error( __( 'The ActivityPub plugin is required to follow Mastodon handles. Please install and activate it first.', 'friends' ) ); } $url = $this->normalize_frontend_subscription_url( $incoming_url ); if ( '' === $url ) { wp_send_json_error( __( 'No URL provided.', 'friends' ) ); } if ( str_starts_with( $url, home_url() ) ) { wp_send_json_error( __( 'It seems like you sent a friend request to yourself.', 'friends' ) ); } if ( ! Friends::check_url( $url ) ) { wp_send_json_error( __( 'You entered an invalid URL.', 'friends' ) ); } $user_login = apply_filters( 'friends_suggest_user_login', User::get_user_login_for_url( $url ), $url ); $display_name = apply_filters( 'friends_suggest_display_name', User::get_display_name_for_url( $url ), $url ); $feeds = $this->friends->feed->discover_available_feeds( $url ); if ( is_wp_error( $feeds ) ) { wp_send_json_error( $feeds->get_error_message() ); } if ( empty( $feeds ) ) { wp_send_json_error( __( 'No suitable feed was found at the provided address.', 'friends' ) ); } $better_user_login = User::get_user_login_from_feeds( $feeds ); if ( $better_user_login ) { $user_login = trim( $better_user_login, '-' ); } $better_display_name = User::get_display_name_from_feeds( $feeds ); if ( $better_display_name ) { $display_name = $better_display_name; if ( ! $better_user_login ) { $user_login = trim( User::sanitize_username( $better_display_name ), '-' ); } } $friend_user = User::get_user( $user_login ); if ( ! $friend_user || is_wp_error( $friend_user ) ) { $friend_user = Subscription::get_by_username( $user_login ); } if ( $friend_user && ! is_wp_error( $friend_user ) ) { // translators: %s is the name of a friend / site. wp_send_json_error( sprintf( __( 'You are already subscribed to this site: %s', 'friends' ), $friend_user->display_name ) ); } $avatar = null; $description = null; foreach ( $feeds as $feed_details ) { if ( ! $avatar && ! empty( $feed_details['avatar'] ) ) { $avatar = $feed_details['avatar']; } if ( ! $description && ! empty( $feed_details['description'] ) ) { $description = $feed_details['description']; } } wp_send_json_success( array( 'feeds' => $feeds, 'display_name' => $display_name ? $display_name : '', 'user_login' => $user_login ? $user_login : '', 'avatar' => $avatar, 'description' => $description, 'url' => $url, ) ); } public function ajax_preview_subscription_feed() { check_ajax_referer( 'friends_add_subscription' ); if ( ! Friends::has_required_privileges() ) { wp_send_json_error( __( 'You do not have permission to do this.', 'friends' ) ); } $url = isset( $_POST['url'] ) && ! is_array( $_POST['url'] ) ? $this->normalize_frontend_subscription_url( wp_unslash( $_POST['url'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( '' === $url || ! Friends::check_url( $url ) ) { wp_send_json_error( __( 'You entered an invalid URL.', 'friends' ) ); } $parser = isset( $_POST['parser'] ) && ! is_array( $_POST['parser'] ) ? sanitize_key( wp_unslash( $_POST['parser'] ) ) : ''; if ( ! $parser ) { wp_send_json_error( __( 'An invalid parser was supplied.', 'friends' ) ); } $items = $this->friends->feed->preview( $parser, $url ); if ( is_wp_error( $items ) ) { wp_send_json_error( $items->get_error_message() ); } $preview_items = array(); foreach ( array_slice( $items, 0, 5 ) as $item ) { $title = $item->title; if ( 'status' === $item->post_format || ! $title ) { $title = wp_strip_all_tags( $item->content ); } $preview_items[] = array( 'title' => wp_trim_words( wp_strip_all_tags( $title ), 16 ), 'excerpt' => wp_trim_words( wp_strip_all_tags( $item->content ), 40 ), 'permalink' => $item->permalink, 'date' => $item->date, 'author' => $item->author, 'post_format' => $item->post_format, ); } wp_send_json_success( array( 'items' => $preview_items, ) ); } public function ajax_subscribe_frontend() { check_ajax_referer( 'friends_add_subscription' ); if ( ! Friends::has_required_privileges() ) { wp_send_json_error( __( 'You do not have permission to do this.', 'friends' ) ); } $url = isset( $_POST['url'] ) && ! is_array( $_POST['url'] ) ? $this->normalize_frontend_subscription_url( wp_unslash( $_POST['url'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $display_name = isset( $_POST['display_name'] ) && ! is_array( $_POST['display_name'] ) ? sanitize_text_field( wp_unslash( $_POST['display_name'] ) ) : ''; $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 $feeds = isset( $_POST['feeds'] ) && is_array( $_POST['feeds'] ) ? wp_unslash( $_POST['feeds'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( empty( $url ) || empty( $feeds ) ) { wp_send_json_error( __( 'Missing required data.', 'friends' ) ); } if ( ! Friends::check_url( $url ) ) { wp_send_json_error( __( 'You entered an invalid URL.', 'friends' ) ); } $user_login = trim( $user_login, '-' ); if ( ! $user_login ) { wp_send_json_error( __( 'Please enter a valid username.', 'friends' ) ); } if ( ! $display_name ) { $display_name = User::get_display_name_for_url( $url ); } if ( ! is_multisite() && username_exists( $user_login ) ) { wp_send_json_error( __( 'This username is already registered. Please choose another one.' ) ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain } $avatar = null; $description = null; $feed_options = array(); $subscribe = array(); $post_formats = array_merge( array( 'autodetect' => true ), array_fill_keys( array_keys( get_post_format_strings() ), true ) ); foreach ( $feeds as $feed ) { if ( ! is_array( $feed ) ) { continue; } $feed_url = ''; if ( ! empty( $feed['url'] ) && is_scalar( $feed['url'] ) ) { $feed_url = esc_url_raw( trim( $feed['url'] ) ); } if ( ! $feed_url || ! Friends::check_url( $feed_url ) ) { continue; } $parser = isset( $feed['parser'] ) && is_scalar( $feed['parser'] ) ? sanitize_key( $feed['parser'] ) : 'simplepie'; if ( ! $parser || 'unsupported' === $parser ) { continue; } $post_format = isset( $feed['post-format'] ) && is_scalar( $feed['post-format'] ) ? sanitize_key( $feed['post-format'] ) : 'standard'; if ( ! isset( $post_formats[ $post_format ] ) ) { $post_format = 'standard'; } $mime_type = isset( $feed['mime-type'] ) && is_scalar( $feed['mime-type'] ) ? sanitize_text_field( $feed['mime-type'] ) : ''; if ( ! $mime_type && ! empty( $feed['type'] ) && is_scalar( $feed['type'] ) ) { $mime_type = sanitize_text_field( $feed['type'] ); } $feed_options[ $feed_url ] = array( 'url' => $feed_url, 'parser' => $parser, 'post-format' => $post_format, 'title' => isset( $feed['title'] ) && is_scalar( $feed['title'] ) ? sanitize_text_field( $feed['title'] ) : $feed_url, ); if ( $mime_type ) { $feed_options[ $feed_url ]['mime-type'] = $mime_type; } $is_selected = isset( $feed['selected'] ) && in_array( $feed['selected'], array( true, 'true', '1', 1, 'on' ), true ); if ( $is_selected ) { $subscribe[] = $feed_url; } if ( ! $avatar && ! empty( $feed['avatar'] ) && is_scalar( $feed['avatar'] ) ) { $avatar = esc_url_raw( $feed['avatar'] ); } if ( ! $description && ! empty( $feed['description'] ) && is_scalar( $feed['description'] ) ) { $description = wp_encode_emoji( sanitize_textarea_field( $feed['description'] ) ); } } if ( empty( $feed_options ) ) { wp_send_json_error( __( 'No suitable feed was found at the provided address.', 'friends' ) ); } if ( empty( $subscribe ) ) { wp_send_json_error( __( 'Please select at least one feed.', 'friends' ) ); } $friend_user = User::get_user( $user_login ); if ( ! $friend_user || is_wp_error( $friend_user ) ) { $friend_user = Subscription::get_by_username( $user_login ); } if ( $friend_user && ! is_wp_error( $friend_user ) ) { // translators: %s is the name of a friend / site. wp_send_json_error( sprintf( __( 'You are already subscribed to this site: %s', 'friends' ), $friend_user->display_name ) ); } $friend_user = User::create( $user_login, 'subscription', $url, $display_name, $avatar, $description ); if ( is_wp_error( $friend_user ) ) { wp_send_json_error( $friend_user->get_error_message() ); } $saved_feeds = $friend_user->save_feeds( $feed_options ); if ( is_wp_error( $saved_feeds ) ) { wp_send_json_error( $saved_feeds->get_error_message() ); } foreach ( $subscribe as $feed_url ) { if ( ! isset( $feed_options[ $feed_url ] ) ) { continue; } $new_feed = $friend_user->subscribe( $feed_url, $feed_options[ $feed_url ] ); if ( ! is_wp_error( $new_feed ) ) { do_action( 'friends_user_feed_activated', $new_feed ); } } add_filter( 'notify_about_new_friend_post', '__return_false', 999 ); wp_schedule_single_event( time(), 'friends_retrieve_user_feeds', array( $friend_user->ID ) ); wp_send_json_success( array( 'message' => sprintf( // translators: %s is the name of a friend. __( 'You are now following %s.', 'friends' ), $display_name ), 'url' => $friend_user->get_local_friends_page_url(), ) ); } public function ajax_set_avatar() { if ( ! isset( $_POST['user'] ) ) { wp_send_json_error( __( 'No user specified.', 'friends' ) ); } check_ajax_referer( 'set-avatar-' . sanitize_user( wp_unslash( $_POST['user'] ) ) ); if ( ! current_user_can( Friends::REQUIRED_ROLE ) ) { wp_send_json_error(); exit; } if ( empty( $_POST['avatar'] ) ) { wp_send_json_error(); exit; } $avatar = check_url( wp_unslash( $_POST['avatar'] ) ); if ( empty( $avatar ) ) { wp_send_json_error(); exit; } $friend = User::get_by_username( sanitize_user( wp_unslash( $_POST['user'] ) ) ); if ( ! $friend || is_wp_error( $friend ) ) { wp_send_json_error( __( 'Invalid user.', 'friends' ) ); exit; } // Use WordPress functions to check the image dimensions. $size = \wp_getimagesize( $avatar ); if ( ! $size ) { wp_send_json_error( __( 'Image is in an unknown format.', 'friends' ) ); exit; } // Needs to be square and not larger than 512x512. if ( $size[0] !== $size[1] || $size[0] > 512 ) { wp_send_json_error( __( 'Image must be square and not larger than 512x512.', 'friends' ) ); exit; } $url = $friend->update_user_icon_url( $avatar ); if ( ! $url || is_wp_error( $url ) ) { wp_send_json_error( $url ); exit; } wp_send_json_success( array( 'url' => $url, ) ); } /** * Process the Friends Edit Notifications page */ public function process_admin_edit_friend_notifications() { $friend = $this->check_admin_edit_friend(); $arg = 'updated'; $arg_value = 1; if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), 'edit-friend-notifications-' . $friend->user_login ) ) { if ( ! get_user_option( 'friends_no_new_post_notification' ) ) { if ( isset( $_POST['friends_new_post_notification'] ) && boolval( $_POST['friends_new_post_notification'] ) ) { delete_user_option( get_current_user_id(), 'friends_no_new_post_notification_' . $friend->user_login ); } else { update_user_option( get_current_user_id(), 'friends_no_new_post_notification_' . $friend->user_login, 1 ); } } if ( ! get_user_option( 'friends_no_keyword_notification' ) ) { if ( isset( $_POST['friends_keyword_notification'] ) && boolval( $_POST['friends_keyword_notification'] ) ) { delete_user_option( get_current_user_id(), 'friends_no_keyword_notification_' . $friend->user_login ); } else { update_user_option( get_current_user_id(), 'friends_no_keyword_notification_' . $friend->user_login, 1 ); } } do_action( 'friends_edit_friend_notifications_after_form_submit', $friend ); } else { return; } if ( isset( $_GET['_wp_http_referer'] ) ) { wp_safe_redirect( wp_get_referer() ); } else { wp_safe_redirect( add_query_arg( $arg, $arg_value, remove_query_arg( array( '_wp_http_referer', '_wpnonce' ) ) ) ); } exit; } /** * Render the Friends Edit Notifications page */ public function render_admin_edit_friend_notifications() { $friend = $this->check_admin_edit_friend(); $post_stats = $friend->get_post_stats(); $this->header_edit_friend( $friend, 'edit-friend-notifications' ); // phpcs:disable WordPress.Security.NonceVerification if ( isset( $_GET['updated'] ) ) { ?>

get_template_part( 'admin/edit-notifications', null, array( 'friend' => $friend, ) ); } /** * Process the Friends Edit Feeds page */ public function process_admin_edit_friend_feeds() { $friend = $this->check_admin_edit_friend(); $arg = 'updated'; $arg_value = 1; if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), 'edit-friend-feeds-' . $friend->user_login ) ) { $hide_from_friends_page = get_user_option( 'friends_hide_from_friends_page' ); if ( ! $hide_from_friends_page ) { $hide_from_friends_page = array(); } if ( ! isset( $_POST['show_on_friends_page'] ) || ! boolval( $_POST['show_on_friends_page'] ) ) { if ( ! in_array( $friend->user_login, $hide_from_friends_page ) ) { $hide_from_friends_page[] = $friend->user_login; update_user_option( get_current_user_id(), 'friends_hide_from_friends_page', $hide_from_friends_page ); } } elseif ( in_array( $friend->user_login, $hide_from_friends_page ) ) { $hide_from_friends_page = array_values( array_diff( $hide_from_friends_page, array( $friend->user_login ) ) ); update_user_option( get_current_user_id(), 'friends_hide_from_friends_page', $hide_from_friends_page ); } if ( $friend->set_retention_number_enabled( boolval( filter_input( INPUT_POST, 'friends_enable_retention_number', FILTER_SANITIZE_NUMBER_INT ) ) ) && isset( $_POST['friends_retention_number'] ) ) { $friend->set_retention_number( filter_input( INPUT_POST, 'friends_retention_number', FILTER_SANITIZE_NUMBER_INT ) ); } if ( $friend->set_retention_days_enabled( boolval( filter_input( INPUT_POST, 'friends_enable_retention_days', FILTER_SANITIZE_NUMBER_INT ) ) ) && isset( $_POST['friends_retention_days'] ) ) { $friend->set_retention_days( filter_input( INPUT_POST, 'friends_retention_days', FILTER_SANITIZE_NUMBER_INT ) ); } $hide_from_friends_page = get_user_option( 'friends_hide_from_friends_page' ); if ( ! $hide_from_friends_page ) { $hide_from_friends_page = array(); } $show_on_dashboard = filter_input( INPUT_POST, 'show_on_dashboard', FILTER_VALIDATE_BOOLEAN ); $already_on_dashboard = false; $widgets = get_user_option( 'friends_dashboard_widgets', get_current_user_id() ); if ( ! $widgets ) { $widgets = array(); } foreach ( $widgets as $k => $widget ) { if ( ! empty( $widget['friend'] ) && $widget['friend'] === $friend->user_login ) { $already_on_dashboard = true; if ( ! $show_on_dashboard ) { unset( $widgets[ $k ] ); update_user_option( get_current_user_id(), 'friends_dashboard_widgets', $widgets ); } break; } } if ( $show_on_dashboard && ! $already_on_dashboard ) { $widgets[] = array( 'friend' => $friend->user_login ); update_user_option( get_current_user_id(), 'friends_dashboard_widgets', $widgets ); } if ( isset( $_POST['feeds'] ) ) { // Sanitized below. $feeds = wp_unslash( $_POST['feeds'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $existing_feeds = $friend->get_feeds(); if ( isset( $feeds['new'] ) ) { if ( ! isset( $feeds['new']['url'] ) || '' === trim( $feeds['new']['url'] ) ) { unset( $feeds['new'] ); } else { foreach ( $existing_feeds as $term_id => $user_feed ) { if ( $user_feed->get_url() === trim( $feeds['new']['url'] ) ) { if ( isset( $feeds[ $term_id ] ) ) { // Let a newly entered feed overrule an existing one. $feeds[ $term_id ] = array_merge( $feeds[ $term_id ], $feeds['new'] ); $feeds[ $term_id ]['active'] = 1; } unset( $feeds['new'] ); break; } } } } foreach ( $feeds as $term_id => $feed ) { if ( 'new' === $term_id ) { if ( ! isset( $feed['url'] ) || '' === trim( $feed['url'] ) ) { continue; } $feed['active'] = true; $protocol = wp_parse_url( $feed['url'], PHP_URL_SCHEME ); if ( ! $protocol ) { $feed['url'] = apply_filters( 'friends_rewrite_incoming_url', 'https://' . $feed['url'], $feed['url'] ); } $new_feed = $friend->subscribe( $feed['url'], $feed ); if ( is_wp_error( $new_feed ) ) { do_action( 'friends_process_feed_item_submit_error', $new_feed, $feed ); continue; } do_action( 'friends_user_feed_activated', $new_feed ); do_action( 'friends_process_feed_item_submit', $new_feed, $feed ); continue; } if ( ! isset( $existing_feeds[ $term_id ] ) ) { continue; } $user_feed = $existing_feeds[ $term_id ]; unset( $existing_feeds[ $term_id ] ); $protocol = wp_parse_url( $feed['url'], PHP_URL_SCHEME ); if ( ! $protocol ) { $feed['url'] = apply_filters( 'friends_rewrite_incoming_url', 'https://' . $feed['url'], $feed['url'] ); } if ( $user_feed->get_url() !== $feed['url'] ) { do_action( 'friends_user_feed_deactivated', $user_feed ); if ( ! isset( $feed['mime-type'] ) ) { $feed['mime-type'] = $user_feed->get_mime_type(); } if ( $feed['active'] ) { $new_feed = $friend->subscribe( $feed['url'], $feed ); if ( ! is_wp_error( $new_feed ) ) { do_action( 'friends_user_feed_activated', $new_feed ); } } else { $new_feed = $friend->save_feed( $feed['url'], $feed ); } // Since the URL has changed, the above will create a new feed, therefore we need to delete the old one. $user_feed->delete(); if ( is_wp_error( $new_feed ) ) { do_action( 'friends_process_feed_item_submit_error', $new_feed, $feed ); continue; } do_action( 'friends_process_feed_item_submit', $new_feed, $feed ); continue; } if ( $user_feed->get_title() !== $feed['title'] ) { $user_feed->update_metadata( 'title', $feed['title'] ); } if ( $user_feed->get_parser() !== $feed['parser'] ) { $user_feed->update_metadata( 'parser', $feed['parser'] ); } if ( $user_feed->get_post_format() !== $feed['post-format'] ) { $user_feed->update_metadata( 'post-format', $feed['post-format'] ); } if ( isset( $feed['mime-type'] ) && $user_feed->get_mime_type() !== $feed['mime-type'] ) { $user_feed->update_metadata( 'mime-type', $feed['mime-type'] ); } $was_active = $user_feed->is_active(); $is_active = isset( $feed['active'] ) && $feed['active']; $user_feed->update_metadata( 'active', $is_active ); if ( $was_active !== $is_active ) { if ( $is_active ) { do_action( 'friends_user_feed_activated', $user_feed ); } else { do_action( 'friends_user_feed_deactivated', $user_feed ); } } do_action( 'friends_process_feed_item_submit', $user_feed, $feed ); } // Delete remaining existing feeds since they were not submitted. foreach ( $existing_feeds as $term_id => $user_feed ) { do_action( 'friends_user_feed_deactivated', $user_feed ); $user_feed->delete(); } } do_action( 'friends_edit_feeds_after_form_submit', $friend ); } else { return; } if ( isset( $_GET['_wp_http_referer'] ) ) { wp_safe_redirect( wp_get_referer() ); } else { wp_safe_redirect( add_query_arg( $arg, $arg_value, remove_query_arg( array( '_wp_http_referer', '_wpnonce' ) ) ) ); } exit; } /** * Render the Friends Edit Feeds page */ public function render_admin_edit_friend_feeds() { $friend = $this->check_admin_edit_friend(); $already_on_dashboard = false; $widgets = get_user_option( 'friends_dashboard_widgets', get_current_user_id() ); if ( ! $widgets ) { $widgets = array(); } foreach ( $widgets as $widget ) { if ( ! empty( $widget['friend'] ) && $widget['friend'] === $friend->user_login ) { $already_on_dashboard = true; break; } } $args = array_merge( $friend->get_post_stats(), array( 'friend' => $friend, 'rules' => $friend->get_feed_rules(), 'hide_from_friends_page' => get_user_option( 'friends_hide_from_friends_page' ), 'post_formats' => array_merge( array( 'autodetect' => __( 'Autodetect Post Format', 'friends' ) ), get_post_format_strings() ), 'friends_settings_url' => add_query_arg( '_wp_http_referer', remove_query_arg( '_wp_http_referer' ), self_admin_url( 'admin.php?page=friends-settings' ) ), 'registered_parsers' => $this->friends->feed->get_registered_parsers(), 'global_retention_days' => Friends::get_retention_days(), 'global_retention_number' => Friends::get_retention_number(), 'global_retention_days_enabled' => get_option( 'friends_enable_retention_days' ), 'global_retention_number_enabled' => get_option( 'friends_enable_retention_number' ), 'show_on_dashboard' => $already_on_dashboard, ) ); if ( ! $args['hide_from_friends_page'] ) { $args['hide_from_friends_page'] = array(); } $this->header_edit_friend( $friend, 'edit-friend-feeds' ); // phpcs:disable WordPress.Security.NonceVerification if ( isset( $_GET['updated'] ) ) { ?>

get_template_part( 'admin/edit-feeds', null, $args ); } /** * Process the Unfriend page */ public function process_admin_unfriend() { $friend = $this->check_admin_edit_friend(); $arg = 'deleted'; $arg_value = $friend->user_login; if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), 'unfriend-' . $friend->user_login ) ) { $friend->delete(); } else { return; } if ( isset( $_GET['_wp_http_referer'] ) ) { wp_safe_redirect( wp_get_referer() ); } else { wp_safe_redirect( add_query_arg( $arg, $arg_value, home_url( '/friends/following/' ) ) ); } exit; } /** * Render the Unfriend page */ public function render_admin_unfriend() { $friend = $this->check_admin_edit_friend(); $post_stats = $friend->get_post_stats(); $args = array( 'friend' => $friend, 'friend_posts' => $post_stats['post_count'], 'total_size' => $post_stats['total_size'], ); Friends::template_loader()->get_template_part( 'admin/unfriend', null, $args ); } /** * Display error messages. * * @param object $errors The errors. */ private function display_errors( $errors ) { if ( ! is_wp_error( $errors ) ) { return; } ?>

get_error_message() ); ?>

get_error_data(); if ( isset( $error_data->error ) ) { $error = unserialize( $error_data->error ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize if ( is_wp_error( $error ) ) { ?>
						
					
check_admin_settings(); if ( ! empty( $_POST['notification_keywords'] ) && is_array( $_POST['notification_keywords'] ) ) { $keywords = array(); foreach ( wp_unslash( $_POST['notification_keywords'] ) as $i => $keyword ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( trim( $keyword ) ) { $keywords[] = array( 'enabled' => isset( $_POST['notification_keywords_enabled'][ $i ] ) && boolval( $_POST['notification_keywords_enabled'][ $i ] ), 'keyword' => sanitize_text_field( $keyword ), ); } } update_option( 'friends_notification_keywords', $keywords ); } if ( isset( $_POST['keyword_notification_override'] ) && boolval( $_POST['keyword_notification_override'] ) ) { delete_user_option( get_current_user_id(), 'friends_keyword_notification_override_disabled' ); } else { update_user_option( get_current_user_id(), 'friends_keyword_notification_override_disabled', 1 ); } if ( isset( $_POST['new_post_notification'] ) && boolval( $_POST['new_post_notification'] ) ) { delete_user_option( get_current_user_id(), 'friends_no_new_post_notification' ); } else { update_user_option( get_current_user_id(), 'friends_no_new_post_notification', 1 ); } if ( isset( $_POST['friend_follower_notification'] ) && boolval( $_POST['friend_follower_notification'] ) ) { delete_user_option( get_current_user_id(), 'friends_no_friend_follower_notification' ); } else { update_user_option( get_current_user_id(), 'friends_no_friend_follower_notification', 1 ); } foreach ( get_post_format_slugs() as $post_format ) { if ( isset( $_POST[ 'new_post_format_notification_' . $post_format ] ) && boolval( $_POST[ 'new_post_format_notification_' . $post_format ] ) ) { delete_user_option( get_current_user_id(), 'friends_no_new_post_format_notification_' . $post_format ); } else { update_user_option( get_current_user_id(), 'friends_no_new_post_format_notification_' . $post_format, 1 ); } } foreach ( array_keys( $this->friends->feed->get_registered_parsers() ) as $parser ) { if ( isset( $_POST[ 'new_post_by_parser_notification_' . $parser ] ) && boolval( $_POST[ 'new_post_by_parser_notification_' . $parser ] ) ) { delete_user_option( get_current_user_id(), 'friends_no_new_post_by_parser_notification_' . $parser ); } else { update_user_option( get_current_user_id(), 'friends_no_new_post_by_parser_notification_' . $parser, 1 ); } } if ( empty( $_POST['friend_listed'] ) ) { return; } // This is an array, it is checked before use below. $friend_usernames = wp_unslash( $_POST['friend_listed'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $current_user_id = get_current_user_id(); $hide_from_friends_page = array(); foreach ( $friend_usernames as $friend_username ) { $friend_user = User::get_by_username( $friend_username ); if ( ! $friend_user ) { continue; } $friend_username = $friend_user->user_login; if ( ! isset( $_POST['show_on_friends_page'][ $friend_username ] ) ) { $hide_from_friends_page[] = $friend_username; } $no_new_post_notification = ! isset( $_POST['new_friend_post_notification'][ $friend_username ] ) || '0' === $_POST['new_friend_post_notification'][ $friend_username ]; if ( get_user_option( 'friends_no_new_post_notification_' . $friend_username ) !== $no_new_post_notification ) { update_user_option( $current_user_id, 'friends_no_new_post_notification_' . $friend_username, $no_new_post_notification ); } $no_keyword_notification = ! isset( $_POST['keyword_notification'][ $friend_username ] ); if ( get_user_option( 'friends_no_keyword_notification_' . $friend_username ) !== $no_keyword_notification ) { update_user_option( $current_user_id, 'friends_no_keyword_notification_' . $friend_username, $no_keyword_notification ); } } update_user_option( $current_user_id, 'friends_hide_from_friends_page', $hide_from_friends_page ); do_action( 'friends_notification_manager_after_form_submit', $friend_usernames ); if ( isset( $_GET['_wp_http_referer'] ) ) { wp_safe_redirect( wp_get_referer() ); } else { wp_safe_redirect( add_query_arg( 'updated', '1', remove_query_arg( array( '_wp_http_referer', '_wpnonce' ) ) ) ); } exit; } /** * Render the admin notification manager. */ public function render_admin_notification_manager() { Friends::template_loader()->get_template_part( 'admin/settings-header', null, array( 'active' => 'friends-notification-manager', 'title' => __( 'Friends', 'friends' ), ) ); $this->check_admin_settings(); $friend_users = User_Query::all_subscriptions(); $hide_from_friends_page = get_user_option( 'friends_hide_from_friends_page' ); if ( ! $hide_from_friends_page ) { $hide_from_friends_page = array(); } $args = array( 'friend_users' => $friend_users->get_results(), 'friends_settings_url' => add_query_arg( '_wp_http_referer', remove_query_arg( '_wp_http_referer' ), self_admin_url( 'admin.php?page=friends-settings' ) ), 'hide_from_friends_page' => $hide_from_friends_page, 'keyword_override_disabled' => get_user_option( 'friends_keyword_notification_override_disabled' ), 'no_new_post_notification' => get_user_option( 'friends_no_new_post_notification' ), 'no_keyword_notification' => get_user_option( 'friends_no_keyword_notification' ), 'notification_keywords' => Feed::get_all_notification_keywords(), 'active_keywords' => Feed::get_active_notification_keywords(), 'feed_parsers' => $this->friends->feed->get_registered_parsers(), ); if ( class_exists( '\Activitypub\Notification' ) ) { $args['no_friend_follower_notification'] = get_user_option( 'friends_no_friend_follower_notification' ); } Friends::template_loader()->get_template_part( 'admin/notification-manager', null, $args ); Friends::template_loader()->get_template_part( 'admin/settings-footer' ); } public function render_admin_import_export() { Friends::template_loader()->get_template_part( 'admin/settings-header', null, array( 'active' => 'friends-import-export', 'title' => __( 'Friends', 'friends' ), ) ); $this->check_admin_settings(); ?>

get_template_part( 'admin/import-export', null, array( 'private_rss_key' => get_option( 'friends_private_rss_key' ), ) ); Friends::template_loader()->get_template_part( 'admin/settings-footer' ); } public function process_admin_import_export() { if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'friends-settings' ) ) { return; } if ( ! Friends::has_required_privileges() ) { return; } if ( isset( $_FILES['opml']['tmp_name'] ) ) { $opml = file_get_contents( $_FILES['opml']['tmp_name'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents $feeds = Import::opml( $opml ); $users_created = count( $feeds ); $feeds_imported = 0; foreach ( $feeds as $user => $user_feeds ) { $feeds_imported += count( $user_feeds ); } ?>

check_admin_duplicate_remover(); // Nonce verification done in check_admin_duplicate_remover. // phpcs:disable WordPress.Security.NonceVerification.Missing // We iterate over this array and then we sanitize _id. // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( empty( $_POST['deleteduplicate'] ) || ! is_array( $_POST['deleteduplicate'] ) ) { return; } $deleted = 0; foreach ( array_keys( wp_unslash( $_POST['deleteduplicate'] ) ) as $_id ) { if ( ! is_numeric( $_id ) ) { continue; } if ( wp_delete_post( intval( $_id ) ) ) { ++$deleted; } } // phpcs:enable WordPress.Security.NonceVerification.Missing // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( $deleted ) { wp_safe_redirect( add_query_arg( 'deleted', $deleted ) ); exit; } } public function check_admin_duplicate_remover() { if ( ! Friends::is_main_user() ) { wp_die( esc_html__( 'Sorry, you are not allowed to edit the rules.', 'friends' ) ); } if ( ! isset( $_GET['user'] ) ) { wp_die( esc_html__( 'Invalid user.', 'friends' ) ); } if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'duplicate-remover-' . sanitize_user( wp_unslash( $_GET['user'] ) ) ) ) { wp_die( esc_html__( 'Invalid nonce.', 'friends' ) ); } $friend = User::get_by_username( sanitize_user( wp_unslash( $_GET['user'] ) ) ); if ( ! $friend || is_wp_error( $friend ) ) { wp_die( esc_html__( 'Invalid username.', 'friends' ) ); } if ( ! $friend->has_cap( 'subscription' ) ) { wp_die( esc_html__( 'This is not a user related to this plugin.', 'friends' ) ); } return $friend; } /** * Render the duplicates remover */ public function render_admin_duplicate_remover() { $friend = $this->check_admin_duplicate_remover(); $this->header_edit_friend( $friend, 'duplicate-remover' ); // phpcs:disable WordPress.Security.NonceVerification if ( isset( $_GET['deleted'] ) ) { ?>

set( 'post_type', Friends::CPT ); $friend_posts->set( 'post_status', array( 'publish', 'private', 'trash' ) ); $friend_posts->set( 'posts_per_page', 100 ); $friend_posts = $friend->modify_query_by_author( $friend_posts ); $uniques = array(); foreach ( $friend_posts->get_posts() as $_post ) { $permalink = get_permalink( $_post ); if ( ! isset( $uniques[ $permalink ] ) ) { $uniques[ $permalink ] = $_post->ID; } } $args = array( 'friend' => $friend, 'friend_posts' => $friend_posts, 'uniques' => array_flip( $uniques ), 'feed' => $this->friends->feed, ); Friends::template_loader()->get_template_part( 'admin/duplicates', null, $args ); } public static function get_browser_api_key_user( $key ) { $key = (string) $key; if ( ! $key ) { return false; } $parts = explode( '-', $key, 3 ); if ( 3 !== count( $parts ) ) { return false; } $user_id = (int) $parts[1]; if ( ! $user_id ) { return false; } $desired_key = get_user_option( 'friends_browser_api_key', $user_id ); if ( ! $desired_key || ! hash_equals( (string) $desired_key, (string) $key ) ) { return false; } $user = get_user_by( 'ID', $user_id ); if ( ! $user ) { return false; } return $user; } public static function check_browser_api_key( $key ) { return false !== self::get_browser_api_key_user( $key ); } public static function revoke_browser_api_key( $user_id = false ) { if ( ! $user_id ) { $user_id = get_current_user_id(); } delete_user_option( $user_id, 'friends_browser_api_key' ); } public static function get_browser_api_key( $user_id = false ) { if ( ! $user_id ) { $user_id = get_current_user_id(); } $key = get_user_option( 'friends_browser_api_key', $user_id ); if ( ! $key ) { $key = 'friends-' . $user_id . '-' . wp_generate_password( 32, false ); update_user_option( $user_id, 'friends_browser_api_key', $key ); } return $key; } public function render_browser_extension() { add_filter( 'friends_admin_tabs', function ( $menu ) { $menu[ __( 'Browser Extension', 'friends' ) ] = 'friends-browser-extension'; return $menu; } ); Friends::template_loader()->get_template_part( 'admin/settings-header', null, array( 'active' => 'friends-browser-extension', ) ); $this->check_admin_settings(); $browser_api_key = self::get_browser_api_key(); if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), 'friends-browser-extension' ) ) { if ( isset( $_POST['revoke-api-key'] ) ) { self::revoke_browser_api_key(); $browser_api_key = self::get_browser_api_key(); } } Friends::template_loader()->get_template_part( 'admin/browser-extension', null, array( 'browser-api-key' => $browser_api_key, ) ); Friends::template_loader()->get_template_part( 'admin/settings-footer' ); } public function render_friends_logs() { add_filter( 'friends_admin_tabs', function ( $menu ) { $menu[ __( 'Logs', 'friends' ) ] = 'friends-logs'; return $menu; } ); Friends::template_loader()->get_template_part( 'admin/settings-header', null, array( 'active' => 'friends-logs', ) ); $this->check_admin_settings(); Friends::template_loader()->get_template_part( 'admin/logs', null, array( 'logs' => Logging::get_logs(), ) ); Friends::template_loader()->get_template_part( 'admin/settings-footer' ); } /** * Gets the roles associated with the Friends plugin. * * @return array The associated roles. */ public static function get_associated_roles() { $roles = new \WP_Roles(); $friend_roles = array(); foreach ( $roles->roles as $role => $data ) { if ( isset( $data['capabilities']['friends_plugin'] ) ) { $friend_roles[ $role ] = $data['name']; } } return $friend_roles; } public static function get_users_url() { return home_url( '/friends/following/' ); } /** * Override the post title for specific post formats. * * @param string $title The title. * @param int $post_id The post id. * * @return string The potentially overriden title. */ public function override_post_format_title( $title, $post_id = null ) { if ( $post_id && empty( $title ) && is_admin() && function_exists( 'get_current_screen' ) ) { $screen = get_current_screen(); if ( $screen && 'edit-post' === $screen->id ) { if ( 'status' === get_post_format() ) { $post = get_post( $post_id ); return wp_trim_words( wp_strip_all_tags( $post->post_content ) ); } } } return $title; } /** * Get the unread badge HTML * * @return string The unread badge HTML. */ public function get_unread_badge() { $unread_count = apply_filters( 'friends_unread_count', 0 ); if ( 0 === intval( $unread_count ) ) { return ''; } if ( get_user_option( 'friends_unobtrusive_badge' ) ) { return ' (' . $unread_count . ')'; } $unread_badge = '
'; // translators: %s is the number of unread items. $unread_badge .= sprintf( _n( '%s unread item', '%s unread items', $unread_count, 'friends' ), $unread_count ); $unread_badge .= '
'; return $unread_badge; } /** * Add a Friends menu to the admin bar * * @param \WP_Admin_Bar $wp_menu The admin bar to modify. */ public function admin_bar_friends_menu( \WP_Admin_Bar $wp_menu ) { if ( ! Friends::has_required_privileges() ) { return; } $my_url = home_url(); $my_admin_url = site_url(); $unread = $this->get_unread_badge(); $wp_menu->add_node( array( 'id' => 'friends-menu', 'parent' => '', 'title' => ' ' . esc_html( __( 'Friends', 'friends' ) ) . $unread . '', 'href' => $my_url . '/friends/', ) ); do_action( 'friends_own_site_menu_top', $wp_menu, $my_url, $my_admin_url ); do_action( 'friends_current_site_menu_top', $wp_menu, $my_url, $my_admin_url ); $wp_menu->add_menu( array( 'id' => 'your-feed', 'parent' => 'friends-menu', 'title' => esc_html__( 'Main Feed', 'friends' ), 'href' => home_url( '/friends/' ), ) ); $wp_menu->add_menu( array( 'id' => 'add-friend', 'parent' => 'friends-menu', 'title' => esc_html__( 'Add a friend', 'friends' ), 'href' => home_url( '/friends/add-friend' ), ) ); $wp_menu->add_menu( array( 'id' => 'friends', 'parent' => 'friends-menu', 'title' => esc_html__( 'Settings' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain 'href' => home_url( '/friends/settings/' ), ) ); } /** * Add Friend entries to the New Content admin section * * @param \WP_Admin_Bar $wp_menu The admin bar to modify. */ public function admin_bar_new_content( \WP_Admin_Bar $wp_menu ) { if ( Friends::has_required_privileges() ) { $wp_menu->add_menu( array( 'id' => 'new-friend-request', 'parent' => 'new-content', 'title' => esc_html__( 'Friend', 'friends' ), 'href' => self_admin_url( 'admin.php?page=add-friend' ), ) ); $wp_menu->add_menu( array( 'id' => 'new-subscription', 'parent' => 'new-content', 'title' => esc_html__( 'Subscription', 'friends' ), 'href' => self_admin_url( 'admin.php?page=add-friend' ), ) ); } } /** * Show friends admin bar item on mobile. */ public function admin_bar_mobile() { if ( ! is_user_logged_in() ) { return; } $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"; ?> has_cap( 'subscription' ) ) { $only_friends_affiliated = false; break; } } if ( $only_friends_affiliated ) { ?> get_active_feeds() as $feed ) { do_action( 'friends_user_feed_deactivated', $feed ); $feed->delete(); } // Delete the rest. foreach ( $friend_user->get_feeds() as $feed ) { $feed->delete(); } foreach ( $friend_user->get_all_post_ids() as $post_id ) { wp_delete_post( $post_id ); } } /** * Display the Bookmarklets at the Tools section of wp-admin */ public function toolbox_bookmarklets() { ?>

 · 

get_total(); $friend_post_count = wp_count_posts( Friends::CPT ); $friend_post_count = $friend_post_count->publish + $friend_post_count->private; if ( $subscription_count ) { // translators: %s is the number of subscriptions. $items[] = '' . sprintf( _n( '%s Subscription', '%s Subscriptions', $subscription_count, 'friends' ), $subscription_count ) . ''; } if ( $friend_post_count ) { // translators: %s is the number of friend posts. $items[] = '' . sprintf( _n( '%s Post by Friends', '%s Posts by Friends', $friend_post_count, 'friends' ), number_format_i18n( $friend_post_count ) ) . ''; } return $items; } public function add_dashboard_widgets() { if ( ! Friends::has_required_privileges() ) { return; } $user_id = get_current_user_id(); $widgets = get_user_option( 'friends_dashboard_widgets', $user_id ); if ( ! $widgets ) { $widgets = array( array() ); update_user_option( $user_id, 'friends_dashboard_widgets', $widgets ); } foreach ( $widgets as $i => $widget ) { if ( ! is_array( $widget ) ) { continue; } $title = __( 'Latest Posts', 'friends' ); if ( isset( $widget['format'] ) ) { $title = get_post_format_string( sanitize_key( $widget['format'] ) ); } if ( ! empty( $widget['friend'] ) ) { $user = User::get_by_username( $widget['friend'] ); $title = ' by ' . $user->display_name; } $title = sprintf( // translators: %s is an author name or "Latest Posts". __( 'Friends: %s', 'friends' ), $title ); wp_add_dashboard_widget( 'friends_dashboard_widget' . $i, $title, array( $this, 'render_dashboard_widget' ), array( $this, 'render_dashboard_widget_controls' ), $widget, 'side', 'high' ); } } public function add_new_dashboard_widget( $friend = null, $format = null ) { $user_id = get_current_user_id(); $widgets = get_user_option( 'friends_dashboard_widgets', $user_id ); if ( ! $widgets ) { $widgets = array(); } $widget = array(); if ( $friend ) { $widget['friend'] = $friend; } if ( $format ) { $widget['format'] = $format; } $widgets[] = $widget; update_user_option( $user_id, 'friends_dashboard_widgets', $widgets ); } public function render_dashboard_widget_controls( $id, $widget = false ) { if ( empty( $id ) && $widget ) { $id = intval( str_replace( 'friends_dashboard_widget', '', $widget['id'] ) ); } $user_id = get_current_user_id(); $widgets = get_user_option( 'friends_dashboard_widgets', $user_id ); if ( ! $widgets ) { $widgets = array( array() ); } // phpcs:disable WordPress.Security.NonceVerification if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] && isset( $_POST['widget_id'] ) ) { $id = intval( str_replace( 'friends_dashboard_widget', '', sanitize_text_field( wp_unslash( $_POST['widget_id'] ) ) ) ); if ( isset( $_POST['add-new'] ) ) { $id = count( $widgets ); $widgets[ $id ] = array(); } if ( ! empty( $_POST['friend'] ) ) { $widgets[ $id ]['friend'] = sanitize_text_field( wp_unslash( $_POST['friend'] ) ); } else { unset( $widgets[ $id ]['friend'] ); } if ( ! empty( $_POST['format'] ) ) { $widgets[ $id ]['format'] = sanitize_text_field( wp_unslash( $_POST['format'] ) ); } else { unset( $widgets[ $id ]['format'] ); } if ( isset( $_POST['delete'] ) ) { unset( $widgets[ $id ] ); } update_user_option( $user_id, 'friends_dashboard_widgets', $widgets ); } // phpcs:enable WordPress.Security.NonceVerification $args = array(); if ( isset( $widgets[ $id ] ) ) { $args = $widgets[ $id ]; } echo '

'; echo ''; echo '

'; echo '

'; echo ''; echo '

'; echo '

'; echo ' '; echo ' '; echo '

'; } public function render_dashboard_widget( $args, $widget ) { $args = $widget['args']; echo '
'; } public function ajax_friends_dashboard() { check_ajax_referer( 'friends-dashboard' ); $query_args = array(); $args = array(); if ( isset( $_POST['friend'] ) ) { $friend = User::get_by_username( sanitize_text_field( wp_unslash( $_POST['friend'] ) ) ); if ( $friend ) { $args['friend_user'] = $friend; $query_args = $friend->modify_get_posts_args_by_author( $query_args ); } } if ( isset( $_POST['format'] ) ) { $post_formats = get_post_format_slugs(); $format = sanitize_text_field( wp_unslash( $_POST['format'] ) ); if ( isset( $post_formats[ $format ] ) ) { $args['post_format'] = $format; if ( 'standard' !== $format ) { $query_args['tax_query'] = array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query array( 'taxonomy' => 'post_format', 'field' => 'slug', 'terms' => array( 'post-format-' . $format ), ), ); } else { $query_args['tax_query'] = array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query array( 'taxonomy' => 'post_format', 'operator' => 'NOT EXISTS', ), ); } } } $any_friends = User_Query::all_associated_users(); ob_start(); if ( 0 === $any_friends->get_total() && empty( $query_args ) ) { Friends::template_loader()->get_template_part( 'admin/dashboard-widget-welcome', null, array() ); } else { $query_args['post_type'] = apply_filters( 'friends_frontend_post_types', array( 'post' ) ); $args['posts'] = get_posts( $query_args ); Friends::template_loader()->get_template_part( 'admin/dashboard-widget', null, $args ); } $data = ob_get_contents(); ob_end_clean(); wp_send_json_success( $data ); } public function site_status_test_php_modules( $modules ) { $modules['mbstring']['required'] = true; return $modules; } public function admin_friend_posts_query( $query ) { global $wp_query, $wp, $authordata; if ( $wp_query !== $query || ! is_admin() ) { return $query; } if ( ! isset( $query->query['post_type'] ) || ! in_array( $query->query['post_type'], apply_filters( 'friends_frontend_post_types', array( 'post' ) ), true ) ) { return $query; } if ( empty( $query->query['author'] ) ) { return $query; } $author = User::get_user_by_id( $query->query['author'] ); if ( ! $author ) { return $query; } $query->query_vars['author'] = ''; $query = $author->modify_query_by_author( $query ); return $query; } /** * Render an "ActivityPub plugin not active" notice for activitypub-parser feeds * when the ActivityPub plugin is not loaded (so Feed_Parser_ActivityPub never fires). * * @param User_Feed $feed The feed. * @param int $term_id The term ID. * @param string $parser The parser slug. */ public function maybe_render_activitypub_inactive_notice( $feed, $term_id, $parser ) { if ( 'activitypub' !== $parser ) { return; } if ( class_exists( '\Activitypub\Activitypub' ) ) { return; } ?>