Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions friends.css
Original file line number Diff line number Diff line change
Expand Up @@ -1861,6 +1861,30 @@ h2#page-title a.dashicons {
}
}

& section.subscriptions .feed-preview {
margin-top: 1em;

& .avatar { border-radius: 50%; vertical-align: middle; margin-right: .5em; }

& .feed-list {
list-style: none;
padding-left: 0;
margin: .5em 0;

& li { padding: 4px 0; }
}
}

& section.subscriptions .subscribe-success {
margin-top: 1em;
display: flex;
align-items: center;
gap: .5em;

& .avatar { border-radius: 50%; }
& a { font-weight: bold; font-size: 1.1em; }
}

& section.subscriptions {
& .subscription-item {
padding: 8px 0;
Expand Down
86 changes: 86 additions & 0 deletions friends.js
Original file line number Diff line number Diff line change
Expand Up @@ -843,4 +843,90 @@
} );
} );

$document.on( 'submit', '#add-subscription-form', function ( e ) {
e.preventDefault();
const $this = $( this );
const url = $this.find( 'input[name="url"]' ).val();
if ( ! url ) {
return;
}
const $preview = $( '#preview-subscription' );
$preview.html( '<p class="loading">' + ( friends.text_loading || 'Loading...' ) + '</p>' );
wp.ajax.send( 'friends-preview-subscription', {
data: {
_ajax_nonce: $this.find( 'input[name=_wpnonce]' ).val(),
url: url,
},
success( r ) {
$preview.data( 'feeds', r.feeds );
let html = '<div class="feed-preview">';
if ( r.avatar ) {
html += '<img src="' + r.avatar + '" class="avatar" width="48" height="48" /> ';
}
html += '<strong>' + ( r.display_name || r.user_login || r.url ) + '</strong>';
if ( r.description ) {
html += '<p>' + r.description + '</p>';
}
html += '<ul class="feed-list">';
const feedUrls = Object.keys( r.feeds );
for ( let i = 0; i < feedUrls.length; i++ ) {
const feedUrl = feedUrls[ i ];
const feed = r.feeds[ feedUrl ];
if ( feed.parser === 'unsupported' ) {
continue;
}
const checked = feed.autoselect ? ' checked' : '';
html += '<li><label><input type="checkbox" name="feed" value="' + feedUrl + '"' + checked + ' /> ';
html += ( feed.title || feedUrl ) + ' <small>(' + ( feed.type || '' ) + ')</small>';
html += '</label></li>';
}
html += '</ul>';
html += '<button class="btn btn-primary subscribe-btn" data-url="' + r.url + '" data-display-name="' + ( r.display_name || '' ) + '" data-nonce="' + $this.find( 'input[name=_wpnonce]' ).val() + '">';
html += ( friends.text_follow || 'Follow' ) + '</button>';
html += '</div>';
$preview.html( html );
$preview.find( '.subscribe-btn' ).focus();
},
error( result ) {
$preview.html( '<p class="error">' + ( result && result.length ? result : ( friends.text_error || 'An error occurred.' ) ) + '</p>' );
},
} );
} );

$document.on( 'click', '.subscribe-btn', function () {
const $this = $( this );
const $preview = $( '#preview-subscription' );
const allFeeds = $preview.data( 'feeds' ) || {};
const feeds = {};
$preview.find( 'input[name="feed"]' ).each( function () {
const $input = $( this );
const feedUrl = $input.val();
feeds[ feedUrl ] = $.extend( {}, allFeeds[ feedUrl ] || {}, {
url: feedUrl,
selected: $input.is( ':checked' ),
} );
} );
wp.ajax.send( 'friends-subscribe-frontend', {
data: {
_ajax_nonce: $this.data( 'nonce' ),
url: $this.data( 'url' ),
display_name: $this.data( 'display-name' ),
feeds: feeds,
},
success( r ) {
let html = '<div class="subscribe-success">';
const avatar = $preview.find( '.avatar' ).attr( 'src' );
if ( avatar ) {
html += '<img src="' + avatar + '" class="avatar" width="48" height="48" /> ';
}
html += '<a href="' + r.url + '">' + r.message + '</a>';
html += '</div>';
$preview.html( html );
},
error( result ) {
$preview.html( '<p class="error">' + result + '</p>' );
},
} );
} );

} )( jQuery, window.wp, window.friends );
133 changes: 133 additions & 0 deletions includes/class-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
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-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' ) );
Expand Down Expand Up @@ -1665,6 +1667,129 @@
wp_send_json_success();
}

public function ajax_preview_subscription() {
if ( ! isset( $_POST['url'] ) ) {
wp_send_json_error( __( 'No URL provided.', 'friends' ) );
}

check_ajax_referer( 'friends_add_subscription' );

$url = wp_unslash( $_POST['url'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

$protocol = wp_parse_url( $url, PHP_URL_SCHEME );
if ( ! $protocol ) {
$url = apply_filters( 'friends_rewrite_incoming_url', 'https://' . $url, $url );
} else {
$url = apply_filters( 'friends_rewrite_incoming_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' ) );
}

$display_name = User::get_display_name_from_feeds( $feeds );
$user_login = User::get_user_login_from_feeds( $feeds );
$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_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'] ) ? wp_unslash( $_POST['url'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$display_name = isset( $_POST['display_name'] ) ? sanitize_text_field( wp_unslash( $_POST['display_name'] ) ) : '';
$feeds = isset( $_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' ) );
}

$user_login = User::get_user_login_for_url( $url );

$avatar = null;
$description = null;
foreach ( $feeds as $feed ) {
if ( ! $avatar && ! empty( $feed['avatar'] ) ) {
$avatar = $feed['avatar'];
}
if ( ! $description && ! empty( $feed['description'] ) ) {
$description = $feed['description'];
}
}

$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() );
}

$feed_options = array();
$subscribe = array();
foreach ( $feeds as $feed ) {
if ( ! empty( $feed['url'] ) ) {
$feed_options[ $feed['url'] ] = $feed;
if ( ! empty( $feed['selected'] ) ) {
$subscribe[] = $feed['url'];
}
}
}

$friend_user->save_feeds( $feed_options );

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 );
$friend_user->retrieve_posts_from_active_feeds();

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(),

Check warning on line 1788 in includes/class-admin.php

View workflow job for this annotation

GitHub Actions / Basic CS and QA checks

Array double arrow not aligned correctly; expected 5 space(s) between "'url'" and double arrow, but found 1.

Check warning on line 1788 in includes/class-admin.php

View workflow job for this annotation

GitHub Actions / Basic CS and QA checks

Array double arrow not aligned correctly; expected 5 space(s) between "'url'" and double arrow, but found 1.
)
);
}

public function ajax_set_avatar() {
if ( ! isset( $_POST['user'] ) ) {
wp_send_json_error( __( 'No user specified.', 'friends' ) );
Expand Down Expand Up @@ -2637,6 +2762,14 @@
)
);

$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',
Expand Down
7 changes: 7 additions & 0 deletions includes/class-frontend.php
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,9 @@ public function enqueue_scripts() {
// translators: %s is a user handle.
$variables['text_confirm_delete_follower'] = __( 'Do you really want to delete the follower %s?', 'friends' );
$variables['text_new_folder'] = __( 'Folder name:', 'friends' );
$variables['text_loading'] = __( 'Loading...', 'friends' );
$variables['text_follow'] = __( 'Follow', 'friends' );
$variables['text_error'] = __( 'An error occurred.', 'friends' );
wp_localize_script( 'friends', 'friends', $variables );
}

Expand Down Expand Up @@ -1503,6 +1506,10 @@ public function get_static_frontend_template( $path ) {
$wp_query->is_singular = false;

switch ( $path ) {
case 'add-friend':
$path = 'frontend/add-friend';
break;

case 'subscriptions':
wp_safe_redirect( home_url( '/friends/following/' ) );
exit;
Expand Down
59 changes: 59 additions & 0 deletions templates/frontend/add-friend.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php
/**
* This is the Add Friend page
*
* @version 1.0
* @package Friends
*/

$args['title'] = __( 'Add Friend', 'friends' );
$args['no-bottom-margin'] = true;

Friends\Friends::template_loader()->get_template_part( 'frontend/header', null, $args );

?>
<section class="subscriptions">
<div class="card">
<div class="card-body">
<form id="add-subscription-form" action="" method="post">
<?php wp_nonce_field( 'friends_add_subscription' ); ?>
<div class="input-group">
<input type="text" name="url" id="subscription-url" class="form-input" placeholder="<?php esc_attr_e( 'Enter a URL or @user@instance', 'friends' ); ?>" required>
<button type="submit" class="btn btn-primary input-group-btn"><?php esc_html_e( 'Follow', 'friends' ); ?></button>
</div>
</form>
<div id="preview-subscription"></div>
</div>
</div>

<div class="card">
<div class="card-body">
<h5><?php esc_html_e( 'Discover People', 'friends' ); ?></h5>
<p><?php esc_html_e( 'Browse the local timeline of a Mastodon server to find interesting people. Copy their profile URL and paste it in the field above to follow them.', 'friends' ); ?></p>
<ul>
<?php
$instances = apply_filters(
'friends_mastodon_instances',
array(
'mastodon.social',
'mastodon.online',
'fosstodon.org',
'hachyderm.io',
'infosec.exchange',
'indieweb.social',
)
);
foreach ( $instances as $instance ) :
?>
<li><a href="<?php echo esc_url( 'https://' . $instance . '/public/local' ); ?>" target="_blank" rel="noopener noreferrer"><?php echo esc_html( $instance ); ?></a></li>
<?php endforeach; ?>
</ul>
</div>
</div>
</section>
<?php
Friends\Friends::template_loader()->get_template_part(
'frontend/footer',
null,
$args
);
Loading