Working subscription approval!

This commit is contained in:
Brion Vibber 2011-03-28 17:06:02 -07:00
parent a70e68e09c
commit e5b5c256a3
13 changed files with 714 additions and 7 deletions

View File

@ -157,7 +157,7 @@ class ApprovegroupAction extends Action
$this->request->abort(); $this->request->abort();
} }
} catch (Exception $e) { } catch (Exception $e) {
common_log(LOG_ERROR, "Exception canceling group sub: " . $e->getMessage()); common_log(LOG_ERR, "Exception canceling group sub: " . $e->getMessage());
// TRANS: Server error displayed when cancelling a queued group join request fails. // TRANS: Server error displayed when cancelling a queued group join request fails.
// TRANS: %1$s is the leaving user's nickname, $2$s is the group nickname for which the leave failed. // TRANS: %1$s is the leaving user's nickname, $2$s is the group nickname for which the leave failed.
$this->serverError(sprintf(_('Could not cancel request for user %1$s to join group %2$s.'), $this->serverError(sprintf(_('Could not cancel request for user %1$s to join group %2$s.'),

145
actions/approvesub.php Normal file
View File

@ -0,0 +1,145 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Leave a group
*
* PHP version 5
*
* LICENCE: This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Group
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2008-2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
/**
* Leave a group
*
* This is the action for leaving a group. It works more or less like the subscribe action
* for users.
*
* @category Group
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class ApprovesubAction extends Action
{
var $profile = null;
/**
* Prepare to run
*/
function prepare($args)
{
parent::prepare($args);
$cur = common_current_user();
if (empty($cur)) {
// TRANS: Client error displayed trying to approve group membership while not logged in.
$this->clientError(_('Must be logged in.'), 403);
return false;
}
if ($this->arg('profile_id')) {
$this->profile = Profile::staticGet('id', $this->arg('profile_id'));
} else {
// TRANS: Client error displayed trying to approve subscriptionswithout specifying a profile to approve.
$this->clientError(_('Must specify a profile.'));
return false;
}
$this->request = Subscription_queue::pkeyGet(array('subscriber' => $this->profile->id,
'subscribed' => $cur->id));
if (empty($this->request)) {
// TRANS: Client error displayed trying to approve subscription for a non-existing request.
$this->clientError(sprintf(_('%s is not in the moderation queue for your subscriptions.'), $this->profile->nickname), 403);
}
$this->approve = (bool)$this->arg('approve');
$this->cancel = (bool)$this->arg('cancel');
if (!$this->approve && !$this->cancel) {
// TRANS: Client error displayed trying to approve/deny subscription.
$this->clientError(_('Internal error: received neither cancel nor abort.'));
}
if ($this->approve && $this->cancel) {
// TRANS: Client error displayed trying to approve/deny subscription
$this->clientError(_('Internal error: received both cancel and abort.'));
}
return true;
}
/**
* Handle the request
*
* On POST, add the current user to the group
*
* @param array $args unused
*
* @return void
*/
function handle($args)
{
parent::handle($args);
$cur = common_current_user();
try {
if ($this->approve) {
$this->request->complete();
} elseif ($this->cancel) {
$this->request->abort();
}
} catch (Exception $e) {
common_log(LOG_ERR, "Exception canceling sub: " . $e->getMessage());
// TRANS: Server error displayed when cancelling a queued subscription request fails.
// TRANS: %1$s is the leaving user's nickname, $2$s is the nickname for which the leave failed.
$this->serverError(sprintf(_('Could not cancel or approve request for user %1$s to join group %2$s.'),
$this->profile->nickname, $cur->nickname));
return;
}
if ($this->boolean('ajax')) {
$this->startHTML('text/xml;charset=utf-8');
$this->elementStart('head');
// TRANS: Title for subscription approval ajax return
// TRANS: %1$s is the approved user's nickname
$this->element('title', null, sprintf(_m('TITLE','%1$s\'s request'),
$this->profile->nickname));
$this->elementEnd('head');
$this->elementStart('body');
if ($this->approve) {
// TRANS: Message on page for user after approving a subscription request.
$this->element('p', 'success', _('Subscription approved.'));
} elseif ($this->cancel) {
// TRANS: Message on page for user after rejecting a subscription request.
$this->element('p', 'success', _('Subscription canceled.'));
}
$this->elementEnd('body');
$this->elementEnd('html');
} else {
common_redirect(common_local_url('subqueue', array('nickname' =>
$cur->nickname)),
303);
}
}
}

View File

@ -141,7 +141,7 @@ class CancelgroupAction extends Action
try { try {
$this->request->abort(); $this->request->abort();
} catch (Exception $e) { } catch (Exception $e) {
common_log(LOG_ERROR, "Exception canceling group sub: " . $e->getMessage()); common_log(LOG_ERR, "Exception canceling group sub: " . $e->getMessage());
// TRANS: Server error displayed when cancelling a queued group join request fails. // TRANS: Server error displayed when cancelling a queued group join request fails.
// TRANS: %1$s is the leaving user's nickname, $2$s is the group nickname for which the leave failed. // TRANS: %1$s is the leaving user's nickname, $2$s is the group nickname for which the leave failed.
$this->serverError(sprintf(_('Could not cancel request for user %1$s to join group %2$s.'), $this->serverError(sprintf(_('Could not cancel request for user %1$s to join group %2$s.'),

View File

@ -0,0 +1,119 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Leave a group
*
* PHP version 5
*
* LICENCE: This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Group
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2008-2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
/**
* Leave a group
*
* This is the action for leaving a group. It works more or less like the subscribe action
* for users.
*
* @category Group
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class CancelsubscriptionAction extends Action
{
function handle($args)
{
parent::handle($args);
if ($this->boolean('ajax')) {
StatusNet::setApi(true);
}
if (!common_logged_in()) {
$this->clientError(_('Not logged in.'));
return;
}
$user = common_current_user();
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
common_redirect(common_local_url('subscriptions',
array('nickname' => $user->nickname)));
return;
}
/* Use a session token for CSRF protection. */
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->clientError(_('There was a problem with your session token. ' .
'Try again, please.'));
return;
}
$other_id = $this->arg('unsubscribeto');
if (!$other_id) {
$this->clientError(_('No profile ID in request.'));
return;
}
$other = Profile::staticGet('id', $other_id);
if (!$other) {
$this->clientError(_('No profile with that ID.'));
return;
}
$this->request = Subscription_queue::pkeyGet(array('subscriber' => $user->id,
'subscribed' => $other->id));
if (empty($this->request)) {
// TRANS: Client error displayed when trying to approve a non-existing group join request.
// TRANS: %s is a user nickname.
$this->clientError(sprintf(_('%s is not in the moderation queue for this group.'), $this->profile->nickname), 403);
}
$this->request->abort();
if ($this->boolean('ajax')) {
$this->startHTML('text/xml;charset=utf-8');
$this->elementStart('head');
$this->element('title', null, _('Unsubscribed'));
$this->elementEnd('head');
$this->elementStart('body');
$subscribe = new SubscribeForm($this, $other);
$subscribe->show();
$this->elementEnd('body');
$this->elementEnd('html');
} else {
common_redirect(common_local_url('subscriptions',
array('nickname' => $user->nickname)),
303);
}
}
}

View File

@ -156,7 +156,7 @@ class GroupqueueAction extends GroupDesignAction
$members->free(); $members->free();
$this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE, $this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE,
$this->page, 'groupmembers', $this->page, 'groupqueue',
array('nickname' => $this->group->nickname)); array('nickname' => $this->group->nickname));
} }
} }

142
actions/subqueue.php Normal file
View File

@ -0,0 +1,142 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* List of group members
*
* PHP version 5
*
* LICENCE: This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Group
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2008-2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once(INSTALLDIR.'/lib/profilelist.php');
/**
* List of group members
*
* @category Group
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class SubqueueAction extends GalleryAction
{
var $page = null;
function isReadOnly($args)
{
return true;
}
// @todo FIXME: most of this belongs in a base class, sounds common to most group actions?
function prepare($args)
{
parent::prepare($args);
$cur = common_current_user();
if (!$cur || $cur->id != $this->profile->id) {
// TRANS: Client error displayed when trying to approve group applicants without being a group administrator.
$this->clientError(_('You may only approve your own pending subscriptions.'));
return false;
}
return true;
}
function title()
{
if ($this->page == 1) {
// TRANS: Title of the first page showing pending subscribers still awaiting approval.
// TRANS: %s is the name of the user.
return sprintf(_('%s subscribers awaiting approval'),
$this->profile->nickname);
} else {
// TRANS: Title of all but the first page showing pending subscribersmembers still awaiting approval.
// TRANS: %1$s is the name of the user, %2$d is the page number of the members list.
return sprintf(_('%1$s subscribers awaiting approval, page %2$d'),
$this->profile->nickname,
$this->page);
}
}
function showPageNotice()
{
$this->element('p', 'instructions',
// TRANS: Page notice for group members page.
_('A list of users awaiting approval to subscribe to you.'));
}
function showContent()
{
$offset = ($this->page-1) * PROFILES_PER_PAGE;
$limit = PROFILES_PER_PAGE + 1;
$cnt = 0;
$members = $this->profile->getRequests($offset, $limit);
if ($members) {
// @fixme change!
$member_list = new SubQueueList($members, $this);
$cnt = $member_list->show();
}
$members->free();
$this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE,
$this->page, 'subqueue',
array('nickname' => $this->profile->nickname)); // urgh
}
}
class SubQueueList extends ProfileList
{
function newListItem($profile)
{
return new SubQueueListItem($profile, $this->action);
}
}
class SubQueueListItem extends ProfileListItem
{
function showActions()
{
$this->startActions();
if (Event::handle('StartProfileListItemActionElements', array($this))) {
$this->showApproveButtons();
Event::handle('EndProfileListItemActionElements', array($this));
}
$this->endActions();
}
function showApproveButtons()
{
$this->out->elementStart('li', 'entity_approval');
$form = new ApproveSubForm($this->out, $this->profile);
$form->show();
$this->out->elementEnd('li');
}
}

View File

@ -356,6 +356,36 @@ class Profile extends Memcached_DataObject
return new ArrayWrapper($profiles); return new ArrayWrapper($profiles);
} }
/**
* Get pending subscribers, who have not yet been approved.
*
* @param int $offset
* @param int $limit
* @return Profile
*/
function getRequests($offset=0, $limit=null)
{
$qry =
'SELECT profile.* ' .
'FROM profile JOIN subscription_queue '.
'ON profile.id = subscription_queue.subscriber ' .
'WHERE subscription_queue.subscribed = %d ' .
'ORDER BY subscription_queue.created DESC ';
if ($limit != null) {
if (common_config('db','type') == 'pgsql') {
$qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
} else {
$qry .= ' LIMIT ' . $offset . ', ' . $limit;
}
}
$members = new Profile();
$members->query(sprintf($qry, $this->id));
return $members;
}
function subscriptionCount() function subscriptionCount()
{ {
$c = Cache::instance(); $c = Cache::instance();

View File

@ -122,7 +122,7 @@ class Subscription extends Memcached_DataObject
Event::handle('EndSubscribe', array($subscriber, $other)); Event::handle('EndSubscribe', array($subscriber, $other));
} }
return true; return $sub;
} }
/** /**

View File

@ -72,7 +72,7 @@ class Subscription_queue extends Managed_DataObject
{ {
$subscriber = Profile::staticGet('id', $this->subscriber); $subscriber = Profile::staticGet('id', $this->subscriber);
$subscribed = Profile::staticGet('id', $this->subscribed); $subscribed = Profile::staticGet('id', $this->subscribed);
$sub = Subscription::start($subscriber, $other, Subscription::FORCE); $sub = Subscription::start($subscriber, $subscribed, Subscription::FORCE);
if ($sub) { if ($sub) {
$this->delete(); $this->delete();
} }
@ -82,7 +82,7 @@ class Subscription_queue extends Managed_DataObject
/** /**
* Cancel an outstanding subscription request to the other profile. * Cancel an outstanding subscription request to the other profile.
*/ */
public function abort($profile) public function abort()
{ {
$subscriber = Profile::staticGet('id', $this->subscriber); $subscriber = Profile::staticGet('id', $this->subscriber);
$subscribed = Profile::staticGet('id', $this->subscribed); $subscribed = Profile::staticGet('id', $this->subscribed);

114
lib/approvesubform.php Normal file
View File

@ -0,0 +1,114 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Form for leaving a group
*
* PHP version 5
*
* LICENCE: This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Form
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Sarven Capadisli <csarven@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/lib/form.php';
/**
* Form for leaving a group
*
* @category Form
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Sarven Capadisli <csarven@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*
* @see UnsubscribeForm
*/
class ApproveSubForm extends Form
{
var $profile = null;
/**
* Constructor
*
* @param HTMLOutputter $out output channel
* @param Profile $profile user whose request to accept or drop
*/
function __construct($out=null, $profile=null)
{
parent::__construct($out);
$this->profile = $profile;
}
/**
* ID of the form
*
* @return string ID of the form
*/
function id()
{
return 'sub-queue-' . $this->profile->id;
}
/**
* class of the form
*
* @return string of the form class
*/
function formClass()
{
return 'form_sub_queue ajax';
}
/**
* Action of the form
*
* @return string URL of the action
*/
function action()
{
$params = array();
if ($this->profile) {
$params['profile_id'] = $this->profile->id;
}
return common_local_url('approvesub',
array(), $params);
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
// TRANS: Submit button text to accept a subscription request on approve sub form.
$this->out->submit('approve', _m('BUTTON','Accept'));
// TRANS: Submit button text to reject a subscription request on approve sub form.
$this->out->submit('cancel', _m('BUTTON','Reject'));
}
}

View File

@ -0,0 +1,129 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Form for leaving a group
*
* PHP version 5
*
* LICENCE: This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Form
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Sarven Capadisli <csarven@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/lib/form.php';
/**
* Form for leaving a group
*
* @category Form
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Sarven Capadisli <csarven@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*
* @see UnsubscribeForm
*/
class CancelSubscriptionForm extends Form
{
/**
* user being subscribed to
*/
var $profile = null;
/**
* Constructor
*
* @param HTMLOutputter $out output channel
* @param Profile $profile being subscribed to
*/
function __construct($out=null, $profile=null)
{
parent::__construct($out);
$this->profile = $profile;
}
/**
* ID of the form
*
* @return string ID of the form
*/
function id()
{
return 'subscription-cancel-' . $this->profile->id;
}
/**
* class of the form
*
* @return string of the form class
*/
function formClass()
{
return 'form_unsubscribe ajax';
}
/**
* Action of the form
*
* @return string URL of the action
*/
function action()
{
return common_local_url('cancelsubscription',
array(),
array('id' => $this->profile->id));
}
/**
* Data elements of the form
*
* @return void
*/
function formData()
{
$this->out->hidden('unsubscribeto-' . $this->profile->id,
$this->profile->id,
'unsubscribeto');
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
$this->out->submit('submit', _('Cancel sub request'));
}
}

View File

@ -199,7 +199,7 @@ class Router
// main stuff is repetitive // main stuff is repetitive
$main = array('login', 'logout', 'register', 'subscribe', $main = array('login', 'logout', 'register', 'subscribe',
'unsubscribe', 'cancelsubscription', 'unsubscribe', 'cancelsubscription', 'approvesub',
'confirmaddress', 'recoverpassword', 'confirmaddress', 'recoverpassword',
'invite', 'favor', 'disfavor', 'sup', 'invite', 'favor', 'disfavor', 'sup',
'block', 'unblock', 'subedit', 'block', 'unblock', 'subedit',
@ -844,6 +844,10 @@ class Router
array('tag' => self::REGEX_TAG)); array('tag' => self::REGEX_TAG));
} }
$m->connect('subscribers/pending',
array('action' => 'subqueue',
'nickname' => $nickname));
foreach (array('rss', 'groups') as $a) { foreach (array('rss', 'groups') as $a) {
$m->connect($a, $m->connect($a,
array('action' => 'user'.$a, array('action' => 'user'.$a,
@ -900,6 +904,9 @@ class Router
array('action' => $a), array('action' => $a),
array('nickname' => Nickname::DISPLAY_FMT)); array('nickname' => Nickname::DISPLAY_FMT));
} }
$m->connect(':nickname/subscribers/pending',
array('action' => 'subqueue'),
array('nickname' => Nickname::DISPLAY_FMT));
foreach (array('subscriptions', 'subscribers') as $a) { foreach (array('subscriptions', 'subscribers') as $a) {
$m->connect(':nickname/'.$a.'/:tag', $m->connect(':nickname/'.$a.'/:tag',

View File

@ -96,6 +96,20 @@ class SubGroupNav extends Menu
$this->user->nickname), $this->user->nickname),
$action == 'subscribers', $action == 'subscribers',
'nav_subscribers'); 'nav_subscribers');
if ($cur && $cur->id == $this->user->id) {
// Possibly site admins should be able to get in here too
$pending = $this->countPendingSubs();
if ($pending || $cur->subscribe_policy == User::SUBSCRIBE_POLICY_MODERATE) {
$this->out->menuItem(common_local_url('subqueue',
array('nickname' =>
$this->user->nickname)),
sprintf(_('Pending (%d)'), $pending),
sprintf(_('Approve pending subscription requests'),
$this->user->nickname),
$action == 'subqueueaction',
'nav_subscribers');
}
}
$this->out->menuItem(common_local_url('usergroups', $this->out->menuItem(common_local_url('usergroups',
array('nickname' => array('nickname' =>
$this->user->nickname)), $this->user->nickname)),
@ -118,4 +132,11 @@ class SubGroupNav extends Menu
$this->out->elementEnd('ul'); $this->out->elementEnd('ul');
} }
function countPendingSubs()
{
$req = new Subscription_queue();
$req->subscribed = $this->user->id;
return $req->count();
}
} }