forked from GNUsocial/gnu-social
394 lines
12 KiB
PHP
394 lines
12 KiB
PHP
|
<?php
|
||
|
// This file is part of GNU social - https://www.gnu.org/software/social
|
||
|
//
|
||
|
// GNU social is free software: you can redistribute it and/or modify
|
||
|
// it under the terms of the GNU Affero General Public License as published by
|
||
|
// the Free Software Foundation, either version 3 of the License, or
|
||
|
// (at your option) any later version.
|
||
|
//
|
||
|
// GNU social is distributed in the hope that it will be useful,
|
||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
// GNU Affero General Public License for more details.
|
||
|
//
|
||
|
// You should have received a copy of the GNU Affero General Public License
|
||
|
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
/**
|
||
|
* Remote Follow implementation for GNU social
|
||
|
*
|
||
|
* @package GNUsocial
|
||
|
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
|
||
|
* @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
|
||
|
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
||
|
*/
|
||
|
|
||
|
defined('GNUSOCIAL') || die();
|
||
|
|
||
|
/**
|
||
|
* Remote-follow follow action
|
||
|
*
|
||
|
* @category Plugin
|
||
|
* @package GNUsocial
|
||
|
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
|
||
|
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
||
|
*/
|
||
|
class RemoteFollowSubAction extends Action
|
||
|
{
|
||
|
protected $uri; // acct: or uri of remote entity
|
||
|
protected $profile; // profile of remote entity, if valid
|
||
|
|
||
|
protected function prepare(array $args = [])
|
||
|
{
|
||
|
parent::prepare($args);
|
||
|
|
||
|
if (!common_logged_in()) {
|
||
|
common_set_returnto($_SERVER['REQUEST_URI']);
|
||
|
if (Event::handle('RedirectToLogin', [$this, null])) {
|
||
|
common_redirect(common_local_url('login'), 303);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!$this->profile && $this->arg('profile')) {
|
||
|
$this->uri = $this->trimmed('profile');
|
||
|
|
||
|
$profile = null;
|
||
|
if (!Event::handle('RemoteFollowPullProfile', [$this->uri, &$profile]) && !is_null($profile)) {
|
||
|
$this->profile = $profile;
|
||
|
} else {
|
||
|
// TRANS: Error displayed when there's failure in fetching the remote profile.
|
||
|
$this->error = _m('Sorry, we could not reach that address. ' .
|
||
|
'Please make sure it is a valid address and try again later.');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Handles the submission.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
protected function handle(): void
|
||
|
{
|
||
|
parent::handle();
|
||
|
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||
|
$this->handlePost();
|
||
|
} else {
|
||
|
$this->showForm();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Show the initial form, when we haven't yet been given a valid
|
||
|
* remote profile.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function showInputForm(): void
|
||
|
{
|
||
|
$this->elementStart('form', ['method' => 'post',
|
||
|
'id' => 'form_ostatus_sub',
|
||
|
'class' => 'form_settings',
|
||
|
'action' => $this->selfLink()]);
|
||
|
|
||
|
$this->hidden('token', common_session_token());
|
||
|
|
||
|
$this->elementStart('fieldset', ['id' => 'settings_feeds']);
|
||
|
|
||
|
$this->elementStart('ul', 'form_data');
|
||
|
$this->elementStart('li');
|
||
|
$this->input('profile',
|
||
|
// TRANS: Field label for a field that takes an user address.
|
||
|
_m('Subscribe to'),
|
||
|
$this->uri,
|
||
|
// TRANS: Tooltip for field label "Subscribe to".
|
||
|
_m('User\'s address, like nickname@example.com or http://example.net/nickname.'));
|
||
|
$this->elementEnd('li');
|
||
|
$this->elementEnd('ul');
|
||
|
// TRANS: Button text.
|
||
|
$this->submit('validate', _m('BUTTON','Continue'));
|
||
|
|
||
|
$this->elementEnd('fieldset');
|
||
|
|
||
|
$this->elementEnd('form');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Show the preview-and-confirm form. We've got a valid remote
|
||
|
* profile and are ready to poke it!
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function showPreviewForm(): void
|
||
|
{
|
||
|
if (!$this->preview()) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$this->elementStart('div', 'entity_actions');
|
||
|
$this->elementStart('ul');
|
||
|
$this->elementStart('li', 'entity_subscribe');
|
||
|
$this->elementStart('form', ['method' => 'post',
|
||
|
'id' => 'form_ostatus_sub',
|
||
|
'class' => 'form_remote_authorize',
|
||
|
'action' => $this->selfLink()]);
|
||
|
$this->elementStart('fieldset');
|
||
|
$this->hidden('token', common_session_token());
|
||
|
$this->hidden('profile', $this->uri);
|
||
|
$this->submit('submit',
|
||
|
// TRANS: Button text.
|
||
|
_m('BUTTON','Confirm'),
|
||
|
'submit',
|
||
|
null,
|
||
|
// TRANS: Tooltip for button "Confirm".
|
||
|
_m('Subscribe to this user'));
|
||
|
$this->elementEnd('fieldset');
|
||
|
$this->elementEnd('form');
|
||
|
$this->elementEnd('li');
|
||
|
$this->elementEnd('ul');
|
||
|
$this->elementEnd('div');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Show a preview for a remote user's profile.
|
||
|
*
|
||
|
* @return bool true if we're ok to try subscribing, false otherwise
|
||
|
*/
|
||
|
public function preview(): bool
|
||
|
{
|
||
|
if ($this->scoped->isSubscribed($this->profile)) {
|
||
|
$this->element('div',
|
||
|
['class' => 'error'],
|
||
|
// TRANS: Extra paragraph in remote profile view when already subscribed.
|
||
|
_m('You are already subscribed to this user.'));
|
||
|
$ok = false;
|
||
|
} else {
|
||
|
$ok = true;
|
||
|
}
|
||
|
|
||
|
$avatarUrl = $this->profile->avatarUrl(AVATAR_PROFILE_SIZE);
|
||
|
|
||
|
$this->showEntity($this->profile,
|
||
|
$this->profile->getUrl(),
|
||
|
$avatarUrl,
|
||
|
$this->profile->getDescription());
|
||
|
return $ok;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Show someone's profile.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function showEntity(Profile $entity, string $profile_url, string $avatar, ?string $note): void
|
||
|
{
|
||
|
$nickname = $entity->getNickname();
|
||
|
$fullname = $entity->getFullname();
|
||
|
$homepage = $entity->getHomepage();
|
||
|
$location = $entity->getLocation();
|
||
|
|
||
|
$this->elementStart('div', 'entity_profile vcard');
|
||
|
$this->element('img', ['src' => $avatar,
|
||
|
'class' => 'photo avatar entity_depiction',
|
||
|
'width' => AVATAR_PROFILE_SIZE,
|
||
|
'height' => AVATAR_PROFILE_SIZE,
|
||
|
'alt' => $nickname]);
|
||
|
|
||
|
$hasFN = ($fullname !== '') ? 'nickname' : 'fn nickname entity_nickname';
|
||
|
$this->elementStart('a', ['href' => $profile_url,
|
||
|
'class' => 'url '.$hasFN]);
|
||
|
$this->text($nickname);
|
||
|
$this->elementEnd('a');
|
||
|
|
||
|
if (!is_null($fullname)) {
|
||
|
$this->elementStart('div', 'fn entity_fn');
|
||
|
$this->text($fullname);
|
||
|
$this->elementEnd('div');
|
||
|
}
|
||
|
|
||
|
if (!is_null($location)) {
|
||
|
$this->elementStart('div', 'label entity_location');
|
||
|
$this->text($location);
|
||
|
$this->elementEnd('div');
|
||
|
}
|
||
|
|
||
|
if (!is_null($homepage)) {
|
||
|
$this->elementStart('a', ['href' => $homepage,
|
||
|
'class' => 'url entity_url']);
|
||
|
$this->text($homepage);
|
||
|
$this->elementEnd('a');
|
||
|
}
|
||
|
|
||
|
if (!is_null($note)) {
|
||
|
$this->elementStart('div', 'note entity_note');
|
||
|
$this->text($note);
|
||
|
$this->elementEnd('div');
|
||
|
}
|
||
|
$this->elementEnd('div');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Redirect on successful remote follow
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function success(): void
|
||
|
{
|
||
|
$url = common_local_url('subscriptions', ['nickname' => $this->scoped->getNickname()]);
|
||
|
common_redirect($url, 303);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Attempt to finalize subscription.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function follow(): void
|
||
|
{
|
||
|
if ($this->scoped->isSubscribed($this->profile)) {
|
||
|
// TRANS: Remote subscription dialog error.
|
||
|
$this->showForm(_m('Already subscribed!'));
|
||
|
} elseif (Subscription::start($this->scoped, $this->profile)) {
|
||
|
$this->success();
|
||
|
} else {
|
||
|
// TRANS: Remote subscription dialog error.
|
||
|
$this->showForm(_m('Remote subscription failed!'));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Handle posts to this form
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function handlePost(): void
|
||
|
{
|
||
|
// CSRF protection
|
||
|
$token = $this->trimmed('token');
|
||
|
if (!$token || $token != common_session_token()) {
|
||
|
// TRANS: Client error displayed when the session token does not match or is not given.
|
||
|
$this->showForm(_m('There was a problem with your session token. '.
|
||
|
'Try again, please.'));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ($this->profile && $this->arg('submit')) {
|
||
|
$this->follow();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$this->showForm();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Show the appropriate form based on our input state.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function showForm(?string $err = null): void
|
||
|
{
|
||
|
if ($err) {
|
||
|
$this->error = $err;
|
||
|
}
|
||
|
|
||
|
if ($this->boolean('ajax')) {
|
||
|
$this->startHTML('text/xml;charset=utf-8');
|
||
|
$this->elementStart('head');
|
||
|
// TRANS: Form title.
|
||
|
$this->element('title', null, _m('Subscribe to user'));
|
||
|
$this->elementEnd('head');
|
||
|
$this->elementStart('body');
|
||
|
$this->showContent();
|
||
|
$this->elementEnd('body');
|
||
|
$this->endHTML();
|
||
|
} else {
|
||
|
$this->showPage();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Title of the page
|
||
|
*
|
||
|
* @return string title of the page
|
||
|
*/
|
||
|
public function title(): string
|
||
|
{
|
||
|
// TRANS: Page title for remote subscription form.
|
||
|
return !empty($this->uri) ? _m('Confirm') : _m('Remote subscription');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Instructions for use
|
||
|
*
|
||
|
* @return string instructions for use
|
||
|
*/
|
||
|
public function getInstructions(): string
|
||
|
{
|
||
|
// TRANS: Instructions.
|
||
|
return _m('You can subscribe to users from other supported sites. Paste their address or profile URI below:');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Show page notice.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function showPageNotice(): void
|
||
|
{
|
||
|
if (!empty($this->error)) {
|
||
|
$this->element('p', 'error', $this->error);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Content area of the page
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function showContent(): void
|
||
|
{
|
||
|
if ($this->profile) {
|
||
|
$this->showPreviewForm();
|
||
|
} else {
|
||
|
$this->showInputForm();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Show javascript headers
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function showScripts(): void
|
||
|
{
|
||
|
parent::showScripts();
|
||
|
$this->autofocus('profile');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return url for this action
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
function selfLink(): string
|
||
|
{
|
||
|
return common_local_url('RemoteFollowSub');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Disable the send-notice form at the top of the page.
|
||
|
* This is really just a hack for the broken CSS in the Cloudy theme,
|
||
|
* I think; copying from other non-notice-navigation pages that do this
|
||
|
* as well. There will be plenty of others also broken.
|
||
|
*
|
||
|
* @fixme fix the cloudy theme
|
||
|
* @fixme do this in a more general way
|
||
|
*/
|
||
|
public function showNoticeForm(): void
|
||
|
{
|
||
|
// nop
|
||
|
}
|
||
|
}
|