From 01ecca5e60aa6912f29a03e8d811f6ebb15ace1e Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 21 Mar 2011 11:18:38 -0700 Subject: [PATCH 1/8] remove type hinting -- fails when ArrayWrapper gets passed in some profile list pages --- plugins/ModPlus/ModPlusPlugin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/ModPlus/ModPlusPlugin.php b/plugins/ModPlus/ModPlusPlugin.php index f8351f0988..ed478c27b8 100644 --- a/plugins/ModPlus/ModPlusPlugin.php +++ b/plugins/ModPlus/ModPlusPlugin.php @@ -129,9 +129,9 @@ class ModPlusPlugin extends Plugin * Currently only adds output for remote profiles, nothing for local users. * * @param HTMLOutputter $out - * @param Profile $profile + * @param Profile $profile (may also be an ArrayWrapper... sigh) */ - protected function showProfileOptions(HTMLOutputter $out, Profile $profile) + protected function showProfileOptions(HTMLOutputter $out, $profile) { $isRemote = !(User::staticGet('id', $profile->id)); if ($isRemote) { From 0bec9cfdbc8edb05677ad9997058265dd32921b4 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 21 Mar 2011 13:30:40 -0700 Subject: [PATCH 2/8] Add request_queue table and user_group.join_policy column, for upcoming join & subscription moderation. UI for setting the join policy is in, but not yet used. --- actions/editgroup.php | 2 ++ actions/newgroup.php | 2 ++ classes/Request_queue.php | 50 +++++++++++++++++++++++++++++++++++++++ classes/User_group.php | 6 +++++ classes/statusnet.ini | 1 + db/core.php | 3 +++ lib/groupeditform.php | 9 +++++++ 7 files changed, 73 insertions(+) create mode 100644 classes/Request_queue.php diff --git a/actions/editgroup.php b/actions/editgroup.php index 08a75da12c..f5bada04fc 100644 --- a/actions/editgroup.php +++ b/actions/editgroup.php @@ -185,6 +185,7 @@ class EditgroupAction extends GroupDesignAction $description = $this->trimmed('description'); $location = $this->trimmed('location'); $aliasstring = $this->trimmed('aliases'); + $join_policy = intval($this->arg('join_policy')); if ($this->nicknameExists($nickname)) { // TRANS: Group edit form validation error. @@ -265,6 +266,7 @@ class EditgroupAction extends GroupDesignAction $this->group->description = $description; $this->group->location = $location; $this->group->mainpage = common_local_url('showgroup', array('nickname' => $nickname)); + $this->group->join_policy = $join_policy; $result = $this->group->update($orig); diff --git a/actions/newgroup.php b/actions/newgroup.php index 9682b875cb..540a42b9ba 100644 --- a/actions/newgroup.php +++ b/actions/newgroup.php @@ -131,6 +131,7 @@ class NewgroupAction extends Action $description = $this->trimmed('description'); $location = $this->trimmed('location'); $aliasstring = $this->trimmed('aliases'); + $join_policy = intval($this->arg('join_policy')); if ($this->nicknameExists($nickname)) { // TRANS: Group create form validation error. @@ -215,6 +216,7 @@ class NewgroupAction extends Action 'location' => $location, 'aliases' => $aliases, 'userid' => $cur->id, + 'join_policy' => $join_policy, 'local' => true)); $this->group = $group; diff --git a/classes/Request_queue.php b/classes/Request_queue.php new file mode 100644 index 0000000000..3d094b1dfc --- /dev/null +++ b/classes/Request_queue.php @@ -0,0 +1,50 @@ + 'Holder for subscription & group join requests awaiting moderation.', + 'fields' => array( + 'subscriber' => array('type' => 'int', 'not null' => true, 'description' => 'remote or local profile making the request'), + 'subscribed' => array('type' => 'int', 'description' => 'remote or local profile to subscribe to, if any'), + 'group_id' => array('type' => 'int', 'description' => 'remote or local group to join, if any'), + 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'), + ), + 'unique key' => array( + 'request_queue_subscriber_subscribed_group_id' => array('subscriber', 'subscribed', 'group_id'), + ), + 'indexes' => array( + 'request_queue_subscriber_created_idx' => array('subscriber', 'created'), + 'request_queue_subscribed_created_idx' => array('subscriber', 'created'), + 'request_queue_group_id_created_idx' => array('group_id', 'created'), + ), + 'foreign keys' => array( + 'request_queue_subscriber_fkey' => array('profile', array('subscriber' => 'id')), + 'request_queue_subscribed_fkey' => array('profile', array('subscribed' => 'id')), + 'request_queue_group_id_fkey' => array('user_group', array('group_id' => 'id')), + ) + ); + } +} diff --git a/classes/User_group.php b/classes/User_group.php index 5a9991fe9e..6ed51b506b 100644 --- a/classes/User_group.php +++ b/classes/User_group.php @@ -24,6 +24,7 @@ class User_group extends Memcached_DataObject public $modified; // timestamp not_null default_CURRENT_TIMESTAMP public $uri; // varchar(255) unique_key public $mainpage; // varchar(255) + public $join_policy; // tinyint /* Static get */ function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('User_group',$k,$v); } @@ -511,6 +512,11 @@ class User_group extends Memcached_DataObject $group->uri = $uri; $group->mainpage = $mainpage; $group->created = common_sql_now(); + if (isset($fields['join_policy'])) { + $group->join_policy = intval($fields['join_policy']); + } else { + $group->join_policy = 0; + } if (Event::handle('StartGroupSave', array(&$group))) { diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 338e5c5aea..f648fb3fbf 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -621,6 +621,7 @@ created = 142 modified = 384 uri = 2 mainpage = 2 +join_policy = 1 [user_group__keys] id = N diff --git a/db/core.php b/db/core.php index 16a59462d4..4aa1911d46 100644 --- a/db/core.php +++ b/db/core.php @@ -649,6 +649,7 @@ $schema['user_group'] = array( 'uri' => array('type' => 'varchar', 'length' => 255, 'description' => 'universal identifier'), 'mainpage' => array('type' => 'varchar', 'length' => 255, 'description' => 'page for group info to link to'), + 'join_policy' => array('type' => 'int', 'size' => 'tiny', 'description' => '0=open; 1=requires admin approval'), ), 'primary key' => array('id'), 'unique keys' => array( @@ -1025,3 +1026,5 @@ $schema['schema_version'] = array( ), 'primary key' => array('table_name'), ); + +$schema['request_queue'] = Request_queue::schemaDef(); diff --git a/lib/groupeditform.php b/lib/groupeditform.php index 3a2cf6bf4a..75f1344c11 100644 --- a/lib/groupeditform.php +++ b/lib/groupeditform.php @@ -186,6 +186,15 @@ class GroupEditForm extends Form common_config('group', 'maxaliases')));; $this->out->elementEnd('li'); } + $this->out->elementStart('li'); + $this->out->dropdown('join_policy', + _('Membership policy'), + array(0 => _('Open to all'), + 1 => _('Admin must approve all members')), + _('Whether admin approval is required to join this group.'), + false, + (empty($this->group->join_policy)) ? 0 : $this->group->join_policy); + $this->out->elementEnd('li'); Event::handle('EndGroupEditFormData', array($this)); } $this->out->elementEnd('ul'); From 541dfa04fe98d0995679d44fdc5d0c5dc8ceca77 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 21 Mar 2011 14:35:29 -0700 Subject: [PATCH 3/8] Switch things from calling Group_member::join & leave & calling events manually to running through Profile::joinGroup() && Profile::leaveGroup(), with the events encapsulated. --- EVENTS.txt | 8 +++---- actions/apigroupjoin.php | 5 +--- actions/apigroupleave.php | 5 +--- actions/atompubmembershipfeed.php | 5 +--- actions/atompubshowmembership.php | 5 +--- actions/joingroup.php | 5 +--- actions/leavegroup.php | 5 +--- classes/Group_member.php | 1 + classes/Profile.php | 30 ++++++++++++++++++++++++ classes/User.php | 27 +++++++++++++++++++++ lib/activityimporter.php | 5 +--- lib/activitymover.php | 2 +- lib/command.php | 10 ++------ plugins/ForceGroup/ForceGroupPlugin.php | 5 +--- plugins/OStatus/OStatusPlugin.php | 2 +- plugins/OStatus/actions/groupsalmon.php | 15 ++---------- plugins/OStatus/actions/ostatusgroup.php | 16 ++++--------- 17 files changed, 81 insertions(+), 70 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index 54d06655ee..1494a9c890 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -742,19 +742,19 @@ EndUnsubscribe: when an unsubscribe is done StartJoinGroup: when a user is joining a group - $group: the group being joined -- $user: the user joining +- $profile: the local or remote user joining EndJoinGroup: when a user finishes joining a group - $group: the group being joined -- $user: the user joining +- $profile: the local or remote user joining StartLeaveGroup: when a user is leaving a group - $group: the group being left -- $user: the user leaving +- $profile: the local or remote user leaving EndLeaveGroup: when a user has left a group - $group: the group being left -- $user: the user leaving +- $profile: the local or remote user leaving StartShowContentLicense: Showing the default license for content - $action: the current action diff --git a/actions/apigroupjoin.php b/actions/apigroupjoin.php index 2e35cb87de..7124a4b0f0 100644 --- a/actions/apigroupjoin.php +++ b/actions/apigroupjoin.php @@ -126,10 +126,7 @@ class ApiGroupJoinAction extends ApiAuthAction } try { - if (Event::handle('StartJoinGroup', array($this->group, $this->user))) { - Group_member::join($this->group->id, $this->user->id); - Event::handle('EndJoinGroup', array($this->group, $this->user)); - } + $this->user->joinGroup($this->group); } catch (Exception $e) { // TRANS: Server error displayed when joining a group failed in the database. // TRANS: %1$s is the joining user's nickname, $2$s is the group nickname for which the join failed. diff --git a/actions/apigroupleave.php b/actions/apigroupleave.php index 083ebd890f..35a4e04d78 100644 --- a/actions/apigroupleave.php +++ b/actions/apigroupleave.php @@ -117,10 +117,7 @@ class ApiGroupLeaveAction extends ApiAuthAction } try { - if (Event::handle('StartLeaveGroup', array($this->group,$this->user))) { - Group_member::leave($this->group->id, $this->user->id); - Event::handle('EndLeaveGroup', array($this->group, $this->user)); - } + $this->user->leaveGroup($this->group); } catch (Exception $e) { // TRANS: Server error displayed when leaving a group failed in the database. // TRANS: %1$s is the leaving user's nickname, $2$s is the group nickname for which the leave failed. diff --git a/actions/atompubmembershipfeed.php b/actions/atompubmembershipfeed.php index b52583314d..f7882d97d3 100644 --- a/actions/atompubmembershipfeed.php +++ b/actions/atompubmembershipfeed.php @@ -275,10 +275,7 @@ class AtompubmembershipfeedAction extends ApiAuthAction throw new ClientException(_('Blocked by admin.')); } - if (Event::handle('StartJoinGroup', array($group, $this->auth_user))) { - $membership = Group_member::join($group->id, $this->auth_user->id); - Event::handle('EndJoinGroup', array($group, $this->auth_user)); - } + $this->auth_user->joinGroup($group); Event::handle('EndAtomPubNewActivity', array($activity, $membership)); } diff --git a/actions/atompubshowmembership.php b/actions/atompubshowmembership.php index 098cca8b3e..8bf62912f5 100644 --- a/actions/atompubshowmembership.php +++ b/actions/atompubshowmembership.php @@ -151,10 +151,7 @@ class AtompubshowmembershipAction extends ApiAuthAction " membership."), 403); } - if (Event::handle('StartLeaveGroup', array($this->_group, $this->auth_user))) { - Group_member::leave($this->_group->id, $this->auth_user->id); - Event::handle('EndLeaveGroup', array($this->_group, $this->auth_user)); - } + $this->auth_user->leaveGroup($this->_group); return; } diff --git a/actions/joingroup.php b/actions/joingroup.php index 4c45ca8b9d..8675dbaed3 100644 --- a/actions/joingroup.php +++ b/actions/joingroup.php @@ -129,10 +129,7 @@ class JoingroupAction extends Action $cur = common_current_user(); try { - if (Event::handle('StartJoinGroup', array($this->group, $cur))) { - Group_member::join($this->group->id, $cur->id); - Event::handle('EndJoinGroup', array($this->group, $cur)); - } + $cur->joinGroup($this->group); } catch (Exception $e) { // TRANS: Server error displayed when joining a group failed in the database. // TRANS: %1$s is the joining user's nickname, $2$s is the group nickname for which the join failed. diff --git a/actions/leavegroup.php b/actions/leavegroup.php index f5d1ccd08c..9e560b9717 100644 --- a/actions/leavegroup.php +++ b/actions/leavegroup.php @@ -123,10 +123,7 @@ class LeavegroupAction extends Action $cur = common_current_user(); try { - if (Event::handle('StartLeaveGroup', array($this->group, $cur))) { - Group_member::leave($this->group->id, $cur->id); - Event::handle('EndLeaveGroup', array($this->group, $cur)); - } + $cur->leaveGroup($this->group); } catch (Exception $e) { // TRANS: Server error displayed when leaving a group failed in the database. // TRANS: %1$s is the leaving user's nickname, $2$s is the group nickname for which the leave failed. diff --git a/classes/Group_member.php b/classes/Group_member.php index 2cf31cf123..30b79bb931 100644 --- a/classes/Group_member.php +++ b/classes/Group_member.php @@ -28,6 +28,7 @@ class Group_member extends Memcached_DataObject /** * Method to add a user to a group. + * In most cases, you should call Profile->joinGroup() instead. * * @param integer $group_id Group to add to * @param integer $profile_id Profile being added diff --git a/classes/Profile.php b/classes/Profile.php index 88edf5cbb3..a80f121fc4 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -339,6 +339,36 @@ class Profile extends Memcached_DataObject return $groups; } + /** + * Request to join the given group. + * May throw exceptions on failure. + * + * @param User_group $group + * @return Group_member + */ + function joinGroup(User_group $group) + { + $ok = null; + if (Event::handle('StartJoinGroup', array($group, $this))) { + $ok = Group_member::join($group->id, $this->id); + Event::handle('EndJoinGroup', array($group, $this)); + } + return $ok; + } + + /** + * Leave a group that this profile is a member of. + * + * @param User_group $group + */ + function leaveGroup(User_group $group) + { + if (Event::handle('StartLeaveGroup', array($this->group, $this))) { + Group_member::leave($this->group->id, $this->id); + Event::handle('EndLeaveGroup', array($this->group, $this)); + } + } + function avatarUrl($size=AVATAR_PROFILE_SIZE) { $avatar = $this->getAvatar($size); diff --git a/classes/User.php b/classes/User.php index 970e167a3b..31b132d0f3 100644 --- a/classes/User.php +++ b/classes/User.php @@ -68,6 +68,9 @@ class User extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE + /** + * @return Profile + */ function getProfile() { $profile = Profile::staticGet('id', $this->id); @@ -596,6 +599,30 @@ class User extends Memcached_DataObject return $profile->getGroups($offset, $limit); } + /** + * Request to join the given group. + * May throw exceptions on failure. + * + * @param User_group $group + * @return Group_member + */ + function joinGroup(User_group $group) + { + $profile = $this->getProfile(); + return $profile->joinGroup($group); + } + + /** + * Leave a group that this user is a member of. + * + * @param User_group $group + */ + function leaveGroup(User_group $group) + { + $profile = $this->getProfile(); + return $profile->leaveGroup($group); + } + function getSubscriptions($offset=0, $limit=null) { $profile = $this->getProfile(); diff --git a/lib/activityimporter.php b/lib/activityimporter.php index aa9b95e084..270b285a26 100644 --- a/lib/activityimporter.php +++ b/lib/activityimporter.php @@ -163,10 +163,7 @@ class ActivityImporter extends QueueHandler throw new ClientException(_("User is already a member of this group.")); } - if (Event::handle('StartJoinGroup', array($group, $user))) { - Group_member::join($group->id, $user->id); - Event::handle('EndJoinGroup', array($group, $user)); - } + $user->joinGroup($group); } // XXX: largely cadged from Ostatus_profile::processNote() diff --git a/lib/activitymover.php b/lib/activitymover.php index 495d7b4caa..b308ad5624 100644 --- a/lib/activitymover.php +++ b/lib/activitymover.php @@ -116,7 +116,7 @@ class ActivityMover extends QueueHandler $sink->postActivity($act); $group = User_group::staticGet('uri', $act->objects[0]->id); if (!empty($group)) { - Group_member::leave($group->id, $user->id); + $user->leaveGroup($group); } break; case ActivityVerb::FOLLOW: diff --git a/lib/command.php b/lib/command.php index 03baa8212d..5b9964c5b1 100644 --- a/lib/command.php +++ b/lib/command.php @@ -352,10 +352,7 @@ class JoinCommand extends Command } try { - if (Event::handle('StartJoinGroup', array($group, $cur))) { - Group_member::join($group->id, $cur->id); - Event::handle('EndJoinGroup', array($group, $cur)); - } + $cur->joinGroup($group); } catch (Exception $e) { // TRANS: Message given having failed to add a user to a group. // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group. @@ -400,10 +397,7 @@ class DropCommand extends Command } try { - if (Event::handle('StartLeaveGroup', array($group, $cur))) { - Group_member::leave($group->id, $cur->id); - Event::handle('EndLeaveGroup', array($group, $cur)); - } + $cur->leaveGroup($group); } catch (Exception $e) { // TRANS: Message given having failed to remove a user from a group. // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group. diff --git a/plugins/ForceGroup/ForceGroupPlugin.php b/plugins/ForceGroup/ForceGroupPlugin.php index fb98644846..5925dcaef0 100644 --- a/plugins/ForceGroup/ForceGroupPlugin.php +++ b/plugins/ForceGroup/ForceGroupPlugin.php @@ -68,10 +68,7 @@ class ForceGroupPlugin extends Plugin $group = User_group::getForNickname($nickname); if ($group && !$profile->isMember($group)) { try { - if (Event::handle('StartJoinGroup', array($group, $user))) { - Group_member::join($group->id, $user->id); - Event::handle('EndJoinGroup', array($group, $user)); - } + $profile->joinGroup($group); } catch (Exception $e) { // TRANS: Server exception. // TRANS: %1$s is a user nickname, %2$s is a group nickname. diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 738481149c..e75130b9e9 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -675,7 +675,7 @@ class OStatusPlugin extends Plugin * it'll be left with a stray membership record. * * @param User_group $group - * @param User $user + * @param Profile $user * * @return mixed hook return value */ diff --git a/plugins/OStatus/actions/groupsalmon.php b/plugins/OStatus/actions/groupsalmon.php index 024f0cc217..a9838f6e1b 100644 --- a/plugins/OStatus/actions/groupsalmon.php +++ b/plugins/OStatus/actions/groupsalmon.php @@ -149,14 +149,7 @@ class GroupsalmonAction extends SalmonAction } try { - // @fixme that event currently passes a user from main UI - // Event should probably move into Group_member::join - // and take a Profile object. - // - //if (Event::handle('StartJoinGroup', array($this->group, $profile))) { - Group_member::join($this->group->id, $profile->id); - //Event::handle('EndJoinGroup', array($this->group, $profile)); - //} + $profile->joinGroup($this->group); } catch (Exception $e) { // TRANS: Server error. %1$s is a profile URI, %2$s is a group nickname. $this->serverError(sprintf(_m('Could not join remote user %1$s to group %2$s.'), @@ -181,11 +174,7 @@ class GroupsalmonAction extends SalmonAction $profile = $oprofile->localProfile(); try { - // @fixme event needs to be refactored as above - //if (Event::handle('StartLeaveGroup', array($this->group, $profile))) { - Group_member::leave($this->group->id, $profile->id); - //Event::handle('EndLeaveGroup', array($this->group, $profile)); - //} + $profile->leaveGroup($this->group); } catch (Exception $e) { // TRANS: Server error. %1$s is a profile URI, %2$s is a group nickname. $this->serverError(sprintf(_m('Could not remove remote user %1$s from group %2$s.'), diff --git a/plugins/OStatus/actions/ostatusgroup.php b/plugins/OStatus/actions/ostatusgroup.php index 24fbaac9ca..245c56a68e 100644 --- a/plugins/OStatus/actions/ostatusgroup.php +++ b/plugins/OStatus/actions/ostatusgroup.php @@ -141,18 +141,12 @@ class OStatusGroupAction extends OStatusSubAction return; } - if (Event::handle('StartJoinGroup', array($group, $user))) { - $ok = Group_member::join($this->oprofile->group_id, $user->id); - if ($ok) { - Event::handle('EndJoinGroup', array($group, $user)); - $this->success(); - } else { - // TRANS: OStatus remote group subscription dialog error. - $this->showForm(_m('Remote group join failed!')); - } - } else { + try { + $user->joinGroup($group); + } catch (Exception $e) { // TRANS: OStatus remote group subscription dialog error. - $this->showForm(_m('Remote group join aborted!')); + $this->showForm(_m('Remote group join failed!')); + return; } } From a54eb0941e4f8c9948149c9a7046fe200d7e47f2 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 21 Mar 2011 15:04:32 -0700 Subject: [PATCH 4/8] Tweaking request_queue -> group_join_queue, easier to deal with the indexes and keys and caching this way. --- classes/Group_join_queue.php | 54 ++++++++++++++++++++++++++++++++++ classes/Managed_DataObject.php | 1 + classes/Profile.php | 18 +++++++----- classes/Request_queue.php | 50 ------------------------------- classes/User_group.php | 3 ++ db/core.php | 2 +- lib/groupeditform.php | 6 ++-- 7 files changed, 73 insertions(+), 61 deletions(-) create mode 100644 classes/Group_join_queue.php delete mode 100644 classes/Request_queue.php diff --git a/classes/Group_join_queue.php b/classes/Group_join_queue.php new file mode 100644 index 0000000000..d8deb253b1 --- /dev/null +++ b/classes/Group_join_queue.php @@ -0,0 +1,54 @@ + 'Holder for group join requests awaiting moderation.', + 'fields' => array( + 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'remote or local profile making the request'), + 'group_id' => array('type' => 'int', 'description' => 'remote or local group to join, if any'), + 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'), + ), + 'primary key' => array('profile_id', 'group_id'), + 'indexes' => array( + 'group_join_queue_profile_id_created_idx' => array('profile_id', 'created'), + 'group_join_queue_group_id_created_idx' => array('group_id', 'created'), + ), + 'foreign keys' => array( + 'group_join_queue_profile_id_fkey' => array('profile', array('profile_id' => 'id')), + 'group_join_queue_group_id_fkey' => array('user_group', array('group_id' => 'id')), + ) + ); + } + + public static function saveNew(Profile $profile, User_group $group) + { + $rq = new Group_join_queue(); + $rq->profile_id = $profile->id; + $rq->group_id = $group->id; + $rq->created = common_sql_now(); + $rq->insert(); + return $rq; + } +} diff --git a/classes/Managed_DataObject.php b/classes/Managed_DataObject.php index 7990d7f408..7263b3e320 100644 --- a/classes/Managed_DataObject.php +++ b/classes/Managed_DataObject.php @@ -93,6 +93,7 @@ abstract class Managed_DataObject extends Memcached_DataObject function keyTypes() { $table = call_user_func(array(get_class($this), 'schemaDef')); + $keys = array(); if (!empty($table['unique keys'])) { foreach ($table['unique keys'] as $idx => $fields) { diff --git a/classes/Profile.php b/classes/Profile.php index a80f121fc4..d4b288fa7a 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -344,14 +344,18 @@ class Profile extends Memcached_DataObject * May throw exceptions on failure. * * @param User_group $group - * @return Group_member + * @return mixed: Group_member on success, Group_join_queue if pending approval, null on some cancels? */ function joinGroup(User_group $group) { $ok = null; - if (Event::handle('StartJoinGroup', array($group, $this))) { - $ok = Group_member::join($group->id, $this->id); - Event::handle('EndJoinGroup', array($group, $this)); + if ($group->join_policy == User_group::JOIN_POLICY_MODERATE) { + $ok = Group_join_queue::saveNew($this, $group); + } else { + if (Event::handle('StartJoinGroup', array($group, $this))) { + $ok = Group_member::join($group->id, $this->id); + Event::handle('EndJoinGroup', array($group, $this)); + } } return $ok; } @@ -363,9 +367,9 @@ class Profile extends Memcached_DataObject */ function leaveGroup(User_group $group) { - if (Event::handle('StartLeaveGroup', array($this->group, $this))) { - Group_member::leave($this->group->id, $this->id); - Event::handle('EndLeaveGroup', array($this->group, $this)); + if (Event::handle('StartLeaveGroup', array($group, $this))) { + Group_member::leave($group->id, $this->id); + Event::handle('EndLeaveGroup', array($group, $this)); } } diff --git a/classes/Request_queue.php b/classes/Request_queue.php deleted file mode 100644 index 3d094b1dfc..0000000000 --- a/classes/Request_queue.php +++ /dev/null @@ -1,50 +0,0 @@ - 'Holder for subscription & group join requests awaiting moderation.', - 'fields' => array( - 'subscriber' => array('type' => 'int', 'not null' => true, 'description' => 'remote or local profile making the request'), - 'subscribed' => array('type' => 'int', 'description' => 'remote or local profile to subscribe to, if any'), - 'group_id' => array('type' => 'int', 'description' => 'remote or local group to join, if any'), - 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'), - ), - 'unique key' => array( - 'request_queue_subscriber_subscribed_group_id' => array('subscriber', 'subscribed', 'group_id'), - ), - 'indexes' => array( - 'request_queue_subscriber_created_idx' => array('subscriber', 'created'), - 'request_queue_subscribed_created_idx' => array('subscriber', 'created'), - 'request_queue_group_id_created_idx' => array('group_id', 'created'), - ), - 'foreign keys' => array( - 'request_queue_subscriber_fkey' => array('profile', array('subscriber' => 'id')), - 'request_queue_subscribed_fkey' => array('profile', array('subscribed' => 'id')), - 'request_queue_group_id_fkey' => array('user_group', array('group_id' => 'id')), - ) - ); - } -} diff --git a/classes/User_group.php b/classes/User_group.php index 6ed51b506b..11efe8d3af 100644 --- a/classes/User_group.php +++ b/classes/User_group.php @@ -5,6 +5,9 @@ class User_group extends Memcached_DataObject { + const JOIN_POLICY_OPEN = 0; + const JOIN_POLICY_MODERATE = 1; + ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ diff --git a/db/core.php b/db/core.php index 4aa1911d46..928186d94d 100644 --- a/db/core.php +++ b/db/core.php @@ -1027,4 +1027,4 @@ $schema['schema_version'] = array( 'primary key' => array('table_name'), ); -$schema['request_queue'] = Request_queue::schemaDef(); +$schema['group_join_queue'] = Group_join_queue::schemaDef(); diff --git a/lib/groupeditform.php b/lib/groupeditform.php index 75f1344c11..0e9c41055e 100644 --- a/lib/groupeditform.php +++ b/lib/groupeditform.php @@ -189,11 +189,11 @@ class GroupEditForm extends Form $this->out->elementStart('li'); $this->out->dropdown('join_policy', _('Membership policy'), - array(0 => _('Open to all'), - 1 => _('Admin must approve all members')), + array(User_group::JOIN_POLICY_OPEN => _('Open to all'), + User_group::JOIN_POLICY_MODERATE => _('Admin must approve all members')), _('Whether admin approval is required to join this group.'), false, - (empty($this->group->join_policy)) ? 0 : $this->group->join_policy); + (empty($this->group->join_policy)) ? User_group::JOIN_POLICY_OPEN : $this->group->join_policy); $this->out->elementEnd('li'); Event::handle('EndGroupEditFormData', array($this)); } From 471a4805871c44ad8770342ca8ca05536068dc85 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 21 Mar 2011 16:26:41 -0700 Subject: [PATCH 5/8] Logic to have group joins turn into pending joins automatically when group is set to mod; allow users to cancel their pending group requests. --- actions/cancelgroup.php | 154 +++++++++++++++++++++++++++++++++++ actions/joingroup.php | 14 +++- classes/Group_join_queue.php | 4 + classes/Profile.php | 24 ++++++ lib/cancelgroupform.php | 116 ++++++++++++++++++++++++++ lib/groupprofileblock.php | 8 +- lib/router.php | 2 +- 7 files changed, 316 insertions(+), 6 deletions(-) create mode 100644 actions/cancelgroup.php create mode 100644 lib/cancelgroupform.php diff --git a/actions/cancelgroup.php b/actions/cancelgroup.php new file mode 100644 index 0000000000..089b4d751e --- /dev/null +++ b/actions/cancelgroup.php @@ -0,0 +1,154 @@ +. + * + * @category Group + * @package StatusNet + * @author Evan Prodromou + * @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 + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ +class CancelgroupAction extends Action +{ + var $group = null; + + /** + * Prepare to run + */ + function prepare($args) + { + parent::prepare($args); + + if (!common_logged_in()) { + // TRANS: Client error displayed when trying to leave a group while not logged in. + $this->clientError(_('You must be logged in to leave a group.')); + return false; + } + + $nickname_arg = $this->trimmed('nickname'); + $id = intval($this->arg('id')); + if ($id) { + $this->group = User_group::staticGet('id', $id); + } else if ($nickname_arg) { + $nickname = common_canonical_nickname($nickname_arg); + + // Permanent redirect on non-canonical nickname + + if ($nickname_arg != $nickname) { + $args = array('nickname' => $nickname); + common_redirect(common_local_url('leavegroup', $args), 301); + return false; + } + + $local = Local_group::staticGet('nickname', $nickname); + + if (!$local) { + // TRANS: Client error displayed when trying to leave a non-local group. + $this->clientError(_('No such group.'), 404); + return false; + } + + $this->group = User_group::staticGet('id', $local->group_id); + } else { + // TRANS: Client error displayed when trying to leave a group without providing a group name or group ID. + $this->clientError(_('No nickname or ID.'), 404); + return false; + } + + if (!$this->group) { + // TRANS: Client error displayed when trying to leave a non-existing group. + $this->clientError(_('No such group.'), 404); + return false; + } + + $cur = common_current_user(); + $this->profile = $cur->getProfile(); + + $this->request = Group_join_queue::pkeyGet(array('profile_id' => $this->profile->id, + 'group_id' => $this->group->id)); + + if (empty($this->request)) { + $this->clientError(_('You are not in the moderation queue for this group.'), 403); + } + 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); + + try { + $this->profile->cancelJoinGroup($this->group); + } catch (Exception $e) { + common_log(LOG_ERROR, "Exception canceling group sub: " . $e->getMessage()); + // 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. + $this->serverError(sprintf(_('Could not cancel request for user %1$s to join group %2$s.'), + $this->profile->nickname, $this->group->nickname)); + return; + } + + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + // TRANS: Title for leave group page after leaving. + $this->element('title', null, sprintf(_m('TITLE','%1$s left group %2$s'), + $this->profile->nickname, + $this->group->nickname)); + $this->elementEnd('head'); + $this->elementStart('body'); + $jf = new JoinForm($this, $this->group); + $jf->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + common_redirect(common_local_url('groupmembers', array('nickname' => + $this->group->nickname)), + 303); + } + } +} diff --git a/actions/joingroup.php b/actions/joingroup.php index 8675dbaed3..f302b39e79 100644 --- a/actions/joingroup.php +++ b/actions/joingroup.php @@ -129,7 +129,7 @@ class JoingroupAction extends Action $cur = common_current_user(); try { - $cur->joinGroup($this->group); + $result = $cur->joinGroup($this->group); } catch (Exception $e) { // TRANS: Server error displayed when joining a group failed in the database. // TRANS: %1$s is the joining user's nickname, $2$s is the group nickname for which the join failed. @@ -147,8 +147,16 @@ class JoingroupAction extends Action $this->group->nickname)); $this->elementEnd('head'); $this->elementStart('body'); - $lf = new LeaveForm($this, $this->group); - $lf->show(); + + if ($result instanceof Group_member) { + $form = new LeaveForm($this, $this->group); + } else if ($result instanceof Group_join_queue) { + $form = new CancelGroupForm($this, $this->group); + } else { + // wtf? + throw new Exception(_m("Unknown error joining group.")); + } + $form->show(); $this->elementEnd('body'); $this->elementEnd('html'); } else { diff --git a/classes/Group_join_queue.php b/classes/Group_join_queue.php index d8deb253b1..ee47b4932d 100644 --- a/classes/Group_join_queue.php +++ b/classes/Group_join_queue.php @@ -18,6 +18,10 @@ class Group_join_queue extends Managed_DataObject function staticGet($k,$v=null) { return Memcached_DataObject::staticGet('Group_join_queue',$k,$v); } + /* Pkey get */ + function pkeyGet($k) + { return Memcached_DataObject::pkeyGet('Group_join_queue',$k); } + /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE diff --git a/classes/Profile.php b/classes/Profile.php index d4b288fa7a..57522d28dc 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -313,6 +313,13 @@ class Profile extends Memcached_DataObject } } + function isPendingMember($group) + { + $request = Group_join_queue::pkeyGet(array('profile_id' => $this->id, + 'group_id' => $group->id)); + return !empty($request); + } + function getGroups($offset=0, $limit=null) { $qry = @@ -360,6 +367,23 @@ class Profile extends Memcached_DataObject return $ok; } + /** + * Cancel a pending group join... + * + * @param User_group $group + */ + function cancelJoinGroup(User_group $group) + { + $request = Group_join_queue::pkeyGet(array('profile_id' => $this->id, + 'group_id' => $group->id)); + if ($request) { + if (Event::handle('StartCancelJoinGroup', array($group, $this))) { + $request->delete(); + Event::handle('EndCancelJoinGroup', array($group, $this)); + } + } + } + /** * Leave a group that this profile is a member of. * diff --git a/lib/cancelgroupform.php b/lib/cancelgroupform.php new file mode 100644 index 0000000000..e71144f0ed --- /dev/null +++ b/lib/cancelgroupform.php @@ -0,0 +1,116 @@ +. + * + * @category Form + * @package StatusNet + * @author Evan Prodromou + * @author Sarven Capadisli + * @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 + * @author Sarven Capadisli + * @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 CancelGroupForm extends Form +{ + /** + * group for user to leave + */ + + var $group = null; + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param group $group group to leave + */ + + function __construct($out=null, $group=null) + { + parent::__construct($out); + + $this->group = $group; + } + + /** + * ID of the form + * + * @return string ID of the form + */ + + function id() + { + return 'group-cancel-' . $this->group->id; + } + + /** + * class of the form + * + * @return string of the form class + */ + + function formClass() + { + return 'form_group_leave ajax'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('cancelgroup', + array('id' => $this->group->id)); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', _('Cancel join request')); + } +} diff --git a/lib/groupprofileblock.php b/lib/groupprofileblock.php index 9df541e343..819c0fbdcc 100644 --- a/lib/groupprofileblock.php +++ b/lib/groupprofileblock.php @@ -97,10 +97,14 @@ class GroupProfileBlock extends ProfileBlock $this->out->elementStart('li', 'entity_subscribe'); if (Event::handle('StartGroupSubscribe', array($this, $this->group))) { if ($cur) { - if ($cur->isMember($this->group)) { + $profile = $cur->getProfile(); + if ($profile->isMember($this->group)) { $lf = new LeaveForm($this->out, $this->group); $lf->show(); - } else if (!Group_block::isBlocked($this->group, $cur->getProfile())) { + } else if ($profile->isPendingMember($this->group)) { + $cf = new CancelGroupForm($this->out, $this->group); + $cf->show(); + } else if (!Group_block::isBlocked($this->group, $profile)) { $jf = new JoinForm($this->out, $this->group); $jf->show(); } diff --git a/lib/router.php b/lib/router.php index efbd2c6cdd..d6f5a37b06 100644 --- a/lib/router.php +++ b/lib/router.php @@ -366,7 +366,7 @@ class Router $m->connect('group/new', array('action' => 'newgroup')); - foreach (array('edit', 'join', 'leave', 'delete') as $v) { + foreach (array('edit', 'join', 'leave', 'delete', 'cancel') as $v) { $m->connect('group/:nickname/'.$v, array('action' => $v.'group'), array('nickname' => Nickname::DISPLAY_FMT)); From 6bdb1053ad006e7b3313098cc7e3722616d49588 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 21 Mar 2011 16:40:10 -0700 Subject: [PATCH 6/8] Pending members queue list -- doesn't yet allow approval. --- actions/groupqueue.php | 529 +++++++++++++++++++++++++++++++++++++++++ classes/User_group.php | 30 +++ lib/router.php | 4 + 3 files changed, 563 insertions(+) create mode 100644 actions/groupqueue.php diff --git a/actions/groupqueue.php b/actions/groupqueue.php new file mode 100644 index 0000000000..aa745f7384 --- /dev/null +++ b/actions/groupqueue.php @@ -0,0 +1,529 @@ +. + * + * @category Group + * @package StatusNet + * @author Evan Prodromou + * @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'); +require_once INSTALLDIR.'/lib/publicgroupnav.php'; + +/** + * List of group members + * + * @category Group + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ +class GroupqueueAction extends GroupDesignAction +{ + var $page = null; + + function isReadOnly($args) + { + return true; + } + + // fixme most of this belongs in a base class, sounds common to most group actions? + function prepare($args) + { + parent::prepare($args); + $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; + + $nickname_arg = $this->arg('nickname'); + $nickname = common_canonical_nickname($nickname_arg); + + // Permanent redirect on non-canonical nickname + + if ($nickname_arg != $nickname) { + $args = array('nickname' => $nickname); + if ($this->page != 1) { + $args['page'] = $this->page; + } + common_redirect(common_local_url('groupqueue', $args), 301); + return false; + } + + if (!$nickname) { + // TRANS: Client error displayed when trying to view group members without providing a group nickname. + $this->clientError(_('No nickname.'), 404); + return false; + } + + $local = Local_group::staticGet('nickname', $nickname); + + if (!$local) { + // TRANS: Client error displayed when trying to view group members for a non-existing group. + $this->clientError(_('No such group.'), 404); + return false; + } + + $this->group = User_group::staticGet('id', $local->group_id); + + if (!$this->group) { + // TRANS: Client error displayed when trying to view group members for an object that is not a group. + $this->clientError(_('No such group.'), 404); + return false; + } + + return true; + } + + function title() + { + if ($this->page == 1) { + // TRANS: Title of the page showing pending group members still awaiting approval to join the group. + // TRANS: %s is the name of the group. + return sprintf(_('%s group members awaiting approval'), + $this->group->nickname); + } else { + // TRANS: Title of the page showing pending group members still awaiting approval to join the group. + // TRANS: %1$s is the name of the group, %2$d is the page number of the members list. + return sprintf(_('%1$s group members awaiting approval, page %2$d'), + $this->group->nickname, + $this->page); + } + } + + function handle($args) + { + parent::handle($args); + $this->showPage(); + } + + function showPageNotice() + { + $this->element('p', 'instructions', + // TRANS: Page notice for group members page. + _('A list of users awaiting approval to join this group.')); + } + + function showObjectNav() + { + $nav = new GroupNav($this, $this->group); + $nav->show(); + } + + function showContent() + { + $offset = ($this->page-1) * PROFILES_PER_PAGE; + $limit = PROFILES_PER_PAGE + 1; + + $cnt = 0; + + $members = $this->group->getRequests($offset, $limit); + + if ($members) { + // @fixme change! + $member_list = new GroupMemberList($members, $this->group, $this); + $cnt = $member_list->show(); + } + + $members->free(); + + $this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE, + $this->page, 'groupmembers', + array('nickname' => $this->group->nickname)); + } +} + +class GroupMemberList extends ProfileList +{ + var $group = null; + + function __construct($profile, $group, $action) + { + parent::__construct($profile, $action); + + $this->group = $group; + } + + function newListItem($profile) + { + return new GroupMemberListItem($profile, $this->group, $this->action); + } +} + +class GroupMemberListItem extends ProfileListItem +{ + var $group = null; + + function __construct($profile, $group, $action) + { + parent::__construct($profile, $action); + + $this->group = $group; + } + + function showFullName() + { + parent::showFullName(); + if ($this->profile->isAdmin($this->group)) { + $this->out->text(' '); // for separating the classes. + // TRANS: Indicator in group members list that this user is a group administrator. + $this->out->element('span', 'role', _('Admin')); + } + } + + function showActions() + { + $this->startActions(); + if (Event::handle('StartProfileListItemActionElements', array($this))) { + $this->showSubscribeButton(); + $this->showMakeAdminForm(); + $this->showGroupBlockForm(); + Event::handle('EndProfileListItemActionElements', array($this)); + } + $this->endActions(); + } + + function showMakeAdminForm() + { + $user = common_current_user(); + + if (!empty($user) && + $user->id != $this->profile->id && + ($user->isAdmin($this->group) || $user->hasRight(Right::MAKEGROUPADMIN)) && + !$this->profile->isAdmin($this->group)) { + $this->out->elementStart('li', 'entity_make_admin'); + $maf = new MakeAdminForm($this->out, $this->profile, $this->group, + $this->returnToArgs()); + $maf->show(); + $this->out->elementEnd('li'); + } + + } + + function showGroupBlockForm() + { + $user = common_current_user(); + + if (!empty($user) && $user->id != $this->profile->id && $user->isAdmin($this->group)) { + $this->out->elementStart('li', 'entity_block'); + $bf = new GroupBlockForm($this->out, $this->profile, $this->group, + $this->returnToArgs()); + $bf->show(); + $this->out->elementEnd('li'); + } + } + + function linkAttributes() + { + $aAttrs = parent::linkAttributes(); + + if (common_config('nofollow', 'members')) { + $aAttrs['rel'] .= ' nofollow'; + } + + return $aAttrs; + } + + function homepageAttributes() + { + $aAttrs = parent::linkAttributes(); + + if (common_config('nofollow', 'members')) { + $aAttrs['rel'] = 'nofollow'; + } + + return $aAttrs; + } + + /** + * Fetch necessary return-to arguments for the profile forms + * to return to this list when they're done. + * + * @return array + */ + protected function returnToArgs() + { + $args = array('action' => 'groupmembers', + 'nickname' => $this->group->nickname); + $page = $this->out->arg('page'); + if ($page) { + $args['param-page'] = $page; + } + return $args; + } +} + +/** + * Form for blocking a user from a group + * + * @category Form + * @package StatusNet + * @author Evan Prodromou + * @author Sarven Capadisli + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see BlockForm + */ +class GroupBlockForm extends Form +{ + /** + * Profile of user to block + */ + + var $profile = null; + + /** + * Group to block the user from + */ + + var $group = null; + + /** + * Return-to args + */ + + var $args = null; + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param Profile $profile profile of user to block + * @param User_group $group group to block user from + * @param array $args return-to args + */ + function __construct($out=null, $profile=null, $group=null, $args=null) + { + parent::__construct($out); + + $this->profile = $profile; + $this->group = $group; + $this->args = $args; + } + + /** + * ID of the form + * + * @return int ID of the form + */ + function id() + { + // This should be unique for the page. + return 'block-' . $this->profile->id; + } + + /** + * class of the form + * + * @return string class of the form + */ + function formClass() + { + return 'form_group_block'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + function action() + { + return common_local_url('groupblock'); + } + + /** + * Legend of the Form + * + * @return void + */ + function formLegend() + { + // TRANS: Form legend for form to block user from a group. + $this->out->element('legend', null, _('Block user from group')); + } + + /** + * Data elements of the form + * + * @return void + */ + function formData() + { + $this->out->hidden('blockto-' . $this->profile->id, + $this->profile->id, + 'blockto'); + $this->out->hidden('blockgroup-' . $this->group->id, + $this->group->id, + 'blockgroup'); + if ($this->args) { + foreach ($this->args as $k => $v) { + $this->out->hidden('returnto-' . $k, $v); + } + } + } + + /** + * Action elements + * + * @return void + */ + function formActions() + { + $this->out->submit( + 'submit', + // TRANS: Button text for the form that will block a user from a group. + _m('BUTTON','Block'), + 'submit', + null, + // TRANS: Submit button title. + _m('TOOLTIP', 'Block this user')); + } +} + +/** + * Form for making a user an admin for a group + * + * @category Form + * @package StatusNet + * @author Evan Prodromou + * @author Sarven Capadisli + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ +class MakeAdminForm extends Form +{ + /** + * Profile of user to block + */ + var $profile = null; + + /** + * Group to block the user from + */ + var $group = null; + + /** + * Return-to args + */ + var $args = null; + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param Profile $profile profile of user to block + * @param User_group $group group to block user from + * @param array $args return-to args + */ + function __construct($out=null, $profile=null, $group=null, $args=null) + { + parent::__construct($out); + + $this->profile = $profile; + $this->group = $group; + $this->args = $args; + } + + /** + * ID of the form + * + * @return int ID of the form + */ + function id() + { + // This should be unique for the page. + return 'makeadmin-' . $this->profile->id; + } + + /** + * class of the form + * + * @return string class of the form + */ + function formClass() + { + return 'form_make_admin'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + function action() + { + return common_local_url('makeadmin', array('nickname' => $this->group->nickname)); + } + + /** + * Legend of the Form + * + * @return void + */ + function formLegend() + { + // TRANS: Form legend for form to make a user a group admin. + $this->out->element('legend', null, _('Make user an admin of the group')); + } + + /** + * Data elements of the form + * + * @return void + */ + function formData() + { + $this->out->hidden('profileid-' . $this->profile->id, + $this->profile->id, + 'profileid'); + $this->out->hidden('groupid-' . $this->group->id, + $this->group->id, + 'groupid'); + if ($this->args) { + foreach ($this->args as $k => $v) { + $this->out->hidden('returnto-' . $k, $v); + } + } + } + + /** + * Action elements + * + * @return void + */ + function formActions() + { + $this->out->submit( + 'submit', + // TRANS: Button text for the form that will make a user administrator. + _m('BUTTON','Make Admin'), + 'submit', + null, + // TRANS: Submit button title. + _m('TOOLTIP','Make this user an admin')); + } +} diff --git a/classes/User_group.php b/classes/User_group.php index 11efe8d3af..c044594f1f 100644 --- a/classes/User_group.php +++ b/classes/User_group.php @@ -153,6 +153,36 @@ class User_group extends Memcached_DataObject return $members; } + /** + * Get pending members, 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 group_join_queue '. + 'ON profile.id = group_join_queue.profile_id ' . + 'WHERE group_join_queue.group_id = %d ' . + 'ORDER BY group_join_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 getMemberCount() { // XXX: WORM cache this diff --git a/lib/router.php b/lib/router.php index d6f5a37b06..fcc915b107 100644 --- a/lib/router.php +++ b/lib/router.php @@ -393,6 +393,10 @@ class Router array('action' => 'makeadmin'), array('nickname' => Nickname::DISPLAY_FMT)); + $m->connect('group/:nickname/members/pending', + array('action' => 'groupqueue'), + array('nickname' => Nickname::DISPLAY_FMT)); + $m->connect('group/:id/id', array('action' => 'groupbyid'), array('id' => '[0-9]+')); From 942887ca8ce29f2cbb487d884a328d7fbbba2566 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 21 Mar 2011 17:17:18 -0700 Subject: [PATCH 7/8] Split up some list/form classes, and get the 'approve' and 'cancel' links on group member queue working. --- actions/approvegroup.php | 168 ++++++++++++++++ actions/cancelgroup.php | 17 +- actions/groupmembers.php | 373 ------------------------------------ actions/groupqueue.php | 372 ++--------------------------------- classes/Profile.php | 22 +++ lib/approvegroupform.php | 122 ++++++++++++ lib/cancelgroupform.php | 10 +- lib/groupblockform.php | 130 +++++++++++++ lib/groupmemberlist.php | 18 ++ lib/groupmemberlistitem.php | 105 ++++++++++ lib/makeadminform.php | 125 ++++++++++++ lib/router.php | 2 +- 12 files changed, 735 insertions(+), 729 deletions(-) create mode 100644 actions/approvegroup.php create mode 100644 lib/approvegroupform.php create mode 100644 lib/groupblockform.php create mode 100644 lib/groupmemberlist.php create mode 100644 lib/groupmemberlistitem.php create mode 100644 lib/makeadminform.php diff --git a/actions/approvegroup.php b/actions/approvegroup.php new file mode 100644 index 0000000000..c52e0e3c46 --- /dev/null +++ b/actions/approvegroup.php @@ -0,0 +1,168 @@ +. + * + * @category Group + * @package StatusNet + * @author Evan Prodromou + * @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 + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ +class ApprovegroupAction extends Action +{ + var $group = null; + + /** + * Prepare to run + */ + function prepare($args) + { + parent::prepare($args); + + if (!common_logged_in()) { + // TRANS: Client error displayed when trying to leave a group while not logged in. + $this->clientError(_('You must be logged in to leave a group.')); + return false; + } + + $nickname_arg = $this->trimmed('nickname'); + $id = intval($this->arg('id')); + if ($id) { + $this->group = User_group::staticGet('id', $id); + } else if ($nickname_arg) { + $nickname = common_canonical_nickname($nickname_arg); + + // Permanent redirect on non-canonical nickname + + if ($nickname_arg != $nickname) { + $args = array('nickname' => $nickname); + common_redirect(common_local_url('leavegroup', $args), 301); + return false; + } + + $local = Local_group::staticGet('nickname', $nickname); + + if (!$local) { + // TRANS: Client error displayed when trying to leave a non-local group. + $this->clientError(_('No such group.'), 404); + return false; + } + + $this->group = User_group::staticGet('id', $local->group_id); + } else { + // TRANS: Client error displayed when trying to leave a group without providing a group name or group ID. + $this->clientError(_('No nickname or ID.'), 404); + return false; + } + + if (!$this->group) { + // TRANS: Client error displayed when trying to leave a non-existing group. + $this->clientError(_('No such group.'), 404); + return false; + } + + $cur = common_current_user(); + if (empty($cur)) { + $this->clientError(_('Must be logged in.'), 403); + return false; + } + if ($this->arg('profile_id')) { + if ($cur->isAdmin($this->group)) { + $this->profile = Profile::staticGet('id', $this->arg('profile_id')); + } else { + $this->clientError(_('Only group admin can approve or cancel join requests.'), 403); + return false; + } + } else { + $this->clientError(_('Must specify a profile.')); + return false; + } + + $this->request = Group_join_queue::pkeyGet(array('profile_id' => $this->profile->id, + 'group_id' => $this->group->id)); + + if (empty($this->request)) { + $this->clientError(sprintf(_('%s is not in the moderation queue for this group.'), $this->profile->nickname), 403); + } + 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); + + try { + $this->profile->completeJoinGroup($this->group); + } catch (Exception $e) { + common_log(LOG_ERROR, "Exception canceling group sub: " . $e->getMessage()); + // 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. + $this->serverError(sprintf(_('Could not cancel request for user %1$s to join group %2$s.'), + $this->profile->nickname, $this->group->nickname)); + return; + } + + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + // TRANS: Title for leave group page after leaving. + $this->element('title', null, sprintf(_m('TITLE','%1$s left group %2$s'), + $this->profile->nickname, + $this->group->nickname)); + $this->elementEnd('head'); + $this->elementStart('body'); + $jf = new JoinForm($this, $this->group); + $jf->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + common_redirect(common_local_url('groupmembers', array('nickname' => + $this->group->nickname)), + 303); + } + } +} diff --git a/actions/cancelgroup.php b/actions/cancelgroup.php index 089b4d751e..68d7f39139 100644 --- a/actions/cancelgroup.php +++ b/actions/cancelgroup.php @@ -97,13 +97,26 @@ class CancelgroupAction extends Action } $cur = common_current_user(); - $this->profile = $cur->getProfile(); + if (empty($cur)) { + $this->clientError(_('Must be logged in.'), 403); + return false; + } + if ($this->arg('profile_id')) { + if ($cur->isAdmin($this->group)) { + $this->profile = Profile::staticGet('id', $this->arg('profile_id')); + } else { + $this->clientError(_('Only group admin can approve or cancel join requests.'), 403); + return false; + } + } else { + $this->profile = $cur->getProfile(); + } $this->request = Group_join_queue::pkeyGet(array('profile_id' => $this->profile->id, 'group_id' => $this->group->id)); if (empty($this->request)) { - $this->clientError(_('You are not in the moderation queue for this group.'), 403); + $this->clientError(sprintf(_('%s is not in the moderation queue for this group.'), $this->profile->nickname), 403); } return true; } diff --git a/actions/groupmembers.php b/actions/groupmembers.php index e280fd1fd1..2bb35ceddc 100644 --- a/actions/groupmembers.php +++ b/actions/groupmembers.php @@ -152,376 +152,3 @@ class GroupmembersAction extends GroupDesignAction array('nickname' => $this->group->nickname)); } } - -class GroupMemberList extends ProfileList -{ - var $group = null; - - function __construct($profile, $group, $action) - { - parent::__construct($profile, $action); - - $this->group = $group; - } - - function newListItem($profile) - { - return new GroupMemberListItem($profile, $this->group, $this->action); - } -} - -class GroupMemberListItem extends ProfileListItem -{ - var $group = null; - - function __construct($profile, $group, $action) - { - parent::__construct($profile, $action); - - $this->group = $group; - } - - function showFullName() - { - parent::showFullName(); - if ($this->profile->isAdmin($this->group)) { - $this->out->text(' '); // for separating the classes. - // TRANS: Indicator in group members list that this user is a group administrator. - $this->out->element('span', 'role', _('Admin')); - } - } - - function showActions() - { - $this->startActions(); - if (Event::handle('StartProfileListItemActionElements', array($this))) { - $this->showSubscribeButton(); - $this->showMakeAdminForm(); - $this->showGroupBlockForm(); - Event::handle('EndProfileListItemActionElements', array($this)); - } - $this->endActions(); - } - - function showMakeAdminForm() - { - $user = common_current_user(); - - if (!empty($user) && - $user->id != $this->profile->id && - ($user->isAdmin($this->group) || $user->hasRight(Right::MAKEGROUPADMIN)) && - !$this->profile->isAdmin($this->group)) { - $this->out->elementStart('li', 'entity_make_admin'); - $maf = new MakeAdminForm($this->out, $this->profile, $this->group, - $this->returnToArgs()); - $maf->show(); - $this->out->elementEnd('li'); - } - - } - - function showGroupBlockForm() - { - $user = common_current_user(); - - if (!empty($user) && $user->id != $this->profile->id && $user->isAdmin($this->group)) { - $this->out->elementStart('li', 'entity_block'); - $bf = new GroupBlockForm($this->out, $this->profile, $this->group, - $this->returnToArgs()); - $bf->show(); - $this->out->elementEnd('li'); - } - } - - function linkAttributes() - { - $aAttrs = parent::linkAttributes(); - - if (common_config('nofollow', 'members')) { - $aAttrs['rel'] .= ' nofollow'; - } - - return $aAttrs; - } - - function homepageAttributes() - { - $aAttrs = parent::linkAttributes(); - - if (common_config('nofollow', 'members')) { - $aAttrs['rel'] = 'nofollow'; - } - - return $aAttrs; - } - - /** - * Fetch necessary return-to arguments for the profile forms - * to return to this list when they're done. - * - * @return array - */ - protected function returnToArgs() - { - $args = array('action' => 'groupmembers', - 'nickname' => $this->group->nickname); - $page = $this->out->arg('page'); - if ($page) { - $args['param-page'] = $page; - } - return $args; - } -} - -/** - * Form for blocking a user from a group - * - * @category Form - * @package StatusNet - * @author Evan Prodromou - * @author Sarven Capadisli - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - * - * @see BlockForm - */ -class GroupBlockForm extends Form -{ - /** - * Profile of user to block - */ - - var $profile = null; - - /** - * Group to block the user from - */ - - var $group = null; - - /** - * Return-to args - */ - - var $args = null; - - /** - * Constructor - * - * @param HTMLOutputter $out output channel - * @param Profile $profile profile of user to block - * @param User_group $group group to block user from - * @param array $args return-to args - */ - function __construct($out=null, $profile=null, $group=null, $args=null) - { - parent::__construct($out); - - $this->profile = $profile; - $this->group = $group; - $this->args = $args; - } - - /** - * ID of the form - * - * @return int ID of the form - */ - function id() - { - // This should be unique for the page. - return 'block-' . $this->profile->id; - } - - /** - * class of the form - * - * @return string class of the form - */ - function formClass() - { - return 'form_group_block'; - } - - /** - * Action of the form - * - * @return string URL of the action - */ - function action() - { - return common_local_url('groupblock'); - } - - /** - * Legend of the Form - * - * @return void - */ - function formLegend() - { - // TRANS: Form legend for form to block user from a group. - $this->out->element('legend', null, _('Block user from group')); - } - - /** - * Data elements of the form - * - * @return void - */ - function formData() - { - $this->out->hidden('blockto-' . $this->profile->id, - $this->profile->id, - 'blockto'); - $this->out->hidden('blockgroup-' . $this->group->id, - $this->group->id, - 'blockgroup'); - if ($this->args) { - foreach ($this->args as $k => $v) { - $this->out->hidden('returnto-' . $k, $v); - } - } - } - - /** - * Action elements - * - * @return void - */ - function formActions() - { - $this->out->submit( - 'submit', - // TRANS: Button text for the form that will block a user from a group. - _m('BUTTON','Block'), - 'submit', - null, - // TRANS: Submit button title. - _m('TOOLTIP', 'Block this user')); - } -} - -/** - * Form for making a user an admin for a group - * - * @category Form - * @package StatusNet - * @author Evan Prodromou - * @author Sarven Capadisli - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ -class MakeAdminForm extends Form -{ - /** - * Profile of user to block - */ - var $profile = null; - - /** - * Group to block the user from - */ - var $group = null; - - /** - * Return-to args - */ - var $args = null; - - /** - * Constructor - * - * @param HTMLOutputter $out output channel - * @param Profile $profile profile of user to block - * @param User_group $group group to block user from - * @param array $args return-to args - */ - function __construct($out=null, $profile=null, $group=null, $args=null) - { - parent::__construct($out); - - $this->profile = $profile; - $this->group = $group; - $this->args = $args; - } - - /** - * ID of the form - * - * @return int ID of the form - */ - function id() - { - // This should be unique for the page. - return 'makeadmin-' . $this->profile->id; - } - - /** - * class of the form - * - * @return string class of the form - */ - function formClass() - { - return 'form_make_admin'; - } - - /** - * Action of the form - * - * @return string URL of the action - */ - function action() - { - return common_local_url('makeadmin', array('nickname' => $this->group->nickname)); - } - - /** - * Legend of the Form - * - * @return void - */ - function formLegend() - { - // TRANS: Form legend for form to make a user a group admin. - $this->out->element('legend', null, _('Make user an admin of the group')); - } - - /** - * Data elements of the form - * - * @return void - */ - function formData() - { - $this->out->hidden('profileid-' . $this->profile->id, - $this->profile->id, - 'profileid'); - $this->out->hidden('groupid-' . $this->group->id, - $this->group->id, - 'groupid'); - if ($this->args) { - foreach ($this->args as $k => $v) { - $this->out->hidden('returnto-' . $k, $v); - } - } - } - - /** - * Action elements - * - * @return void - */ - function formActions() - { - $this->out->submit( - 'submit', - // TRANS: Button text for the form that will make a user administrator. - _m('BUTTON','Make Admin'), - 'submit', - null, - // TRANS: Submit button title. - _m('TOOLTIP','Make this user an admin')); - } -} diff --git a/actions/groupqueue.php b/actions/groupqueue.php index aa745f7384..687fa0b973 100644 --- a/actions/groupqueue.php +++ b/actions/groupqueue.php @@ -94,6 +94,11 @@ class GroupqueueAction extends GroupDesignAction return false; } + $cur = common_current_user(); + if (!$cur || !$cur->isAdmin($this->group)) { + $this->clientError(_('Only the group admin may approve users.')); + return false; + } return true; } @@ -143,7 +148,7 @@ class GroupqueueAction extends GroupDesignAction if ($members) { // @fixme change! - $member_list = new GroupMemberList($members, $this->group, $this); + $member_list = new GroupQueueList($members, $this->group, $this); $cnt = $member_list->show(); } @@ -155,375 +160,40 @@ class GroupqueueAction extends GroupDesignAction } } -class GroupMemberList extends ProfileList +class GroupQueueList extends GroupMemberList { - var $group = null; - - function __construct($profile, $group, $action) - { - parent::__construct($profile, $action); - - $this->group = $group; - } - function newListItem($profile) { - return new GroupMemberListItem($profile, $this->group, $this->action); + return new GroupQueueListItem($profile, $this->group, $this->action); } } -class GroupMemberListItem extends ProfileListItem +class GroupQueueListItem extends GroupMemberListItem { - var $group = null; - - function __construct($profile, $group, $action) - { - parent::__construct($profile, $action); - - $this->group = $group; - } - - function showFullName() - { - parent::showFullName(); - if ($this->profile->isAdmin($this->group)) { - $this->out->text(' '); // for separating the classes. - // TRANS: Indicator in group members list that this user is a group administrator. - $this->out->element('span', 'role', _('Admin')); - } - } - function showActions() { $this->startActions(); if (Event::handle('StartProfileListItemActionElements', array($this))) { - $this->showSubscribeButton(); - $this->showMakeAdminForm(); - $this->showGroupBlockForm(); + $this->showApproveButton(); + $this->showCancelButton(); Event::handle('EndProfileListItemActionElements', array($this)); } $this->endActions(); } - function showMakeAdminForm() + function showApproveButton() { - $user = common_current_user(); - - if (!empty($user) && - $user->id != $this->profile->id && - ($user->isAdmin($this->group) || $user->hasRight(Right::MAKEGROUPADMIN)) && - !$this->profile->isAdmin($this->group)) { - $this->out->elementStart('li', 'entity_make_admin'); - $maf = new MakeAdminForm($this->out, $this->profile, $this->group, - $this->returnToArgs()); - $maf->show(); - $this->out->elementEnd('li'); - } - + $this->out->elementStart('li', 'entity_join'); + $form = new ApproveGroupForm($this->out, $this->group, $this->profile); + $form->show(); + $this->out->elementEnd('li'); } - function showGroupBlockForm() + function showCancelButton() { - $user = common_current_user(); - - if (!empty($user) && $user->id != $this->profile->id && $user->isAdmin($this->group)) { - $this->out->elementStart('li', 'entity_block'); - $bf = new GroupBlockForm($this->out, $this->profile, $this->group, - $this->returnToArgs()); - $bf->show(); - $this->out->elementEnd('li'); - } - } - - function linkAttributes() - { - $aAttrs = parent::linkAttributes(); - - if (common_config('nofollow', 'members')) { - $aAttrs['rel'] .= ' nofollow'; - } - - return $aAttrs; - } - - function homepageAttributes() - { - $aAttrs = parent::linkAttributes(); - - if (common_config('nofollow', 'members')) { - $aAttrs['rel'] = 'nofollow'; - } - - return $aAttrs; - } - - /** - * Fetch necessary return-to arguments for the profile forms - * to return to this list when they're done. - * - * @return array - */ - protected function returnToArgs() - { - $args = array('action' => 'groupmembers', - 'nickname' => $this->group->nickname); - $page = $this->out->arg('page'); - if ($page) { - $args['param-page'] = $page; - } - return $args; - } -} - -/** - * Form for blocking a user from a group - * - * @category Form - * @package StatusNet - * @author Evan Prodromou - * @author Sarven Capadisli - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - * - * @see BlockForm - */ -class GroupBlockForm extends Form -{ - /** - * Profile of user to block - */ - - var $profile = null; - - /** - * Group to block the user from - */ - - var $group = null; - - /** - * Return-to args - */ - - var $args = null; - - /** - * Constructor - * - * @param HTMLOutputter $out output channel - * @param Profile $profile profile of user to block - * @param User_group $group group to block user from - * @param array $args return-to args - */ - function __construct($out=null, $profile=null, $group=null, $args=null) - { - parent::__construct($out); - - $this->profile = $profile; - $this->group = $group; - $this->args = $args; - } - - /** - * ID of the form - * - * @return int ID of the form - */ - function id() - { - // This should be unique for the page. - return 'block-' . $this->profile->id; - } - - /** - * class of the form - * - * @return string class of the form - */ - function formClass() - { - return 'form_group_block'; - } - - /** - * Action of the form - * - * @return string URL of the action - */ - function action() - { - return common_local_url('groupblock'); - } - - /** - * Legend of the Form - * - * @return void - */ - function formLegend() - { - // TRANS: Form legend for form to block user from a group. - $this->out->element('legend', null, _('Block user from group')); - } - - /** - * Data elements of the form - * - * @return void - */ - function formData() - { - $this->out->hidden('blockto-' . $this->profile->id, - $this->profile->id, - 'blockto'); - $this->out->hidden('blockgroup-' . $this->group->id, - $this->group->id, - 'blockgroup'); - if ($this->args) { - foreach ($this->args as $k => $v) { - $this->out->hidden('returnto-' . $k, $v); - } - } - } - - /** - * Action elements - * - * @return void - */ - function formActions() - { - $this->out->submit( - 'submit', - // TRANS: Button text for the form that will block a user from a group. - _m('BUTTON','Block'), - 'submit', - null, - // TRANS: Submit button title. - _m('TOOLTIP', 'Block this user')); - } -} - -/** - * Form for making a user an admin for a group - * - * @category Form - * @package StatusNet - * @author Evan Prodromou - * @author Sarven Capadisli - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ -class MakeAdminForm extends Form -{ - /** - * Profile of user to block - */ - var $profile = null; - - /** - * Group to block the user from - */ - var $group = null; - - /** - * Return-to args - */ - var $args = null; - - /** - * Constructor - * - * @param HTMLOutputter $out output channel - * @param Profile $profile profile of user to block - * @param User_group $group group to block user from - * @param array $args return-to args - */ - function __construct($out=null, $profile=null, $group=null, $args=null) - { - parent::__construct($out); - - $this->profile = $profile; - $this->group = $group; - $this->args = $args; - } - - /** - * ID of the form - * - * @return int ID of the form - */ - function id() - { - // This should be unique for the page. - return 'makeadmin-' . $this->profile->id; - } - - /** - * class of the form - * - * @return string class of the form - */ - function formClass() - { - return 'form_make_admin'; - } - - /** - * Action of the form - * - * @return string URL of the action - */ - function action() - { - return common_local_url('makeadmin', array('nickname' => $this->group->nickname)); - } - - /** - * Legend of the Form - * - * @return void - */ - function formLegend() - { - // TRANS: Form legend for form to make a user a group admin. - $this->out->element('legend', null, _('Make user an admin of the group')); - } - - /** - * Data elements of the form - * - * @return void - */ - function formData() - { - $this->out->hidden('profileid-' . $this->profile->id, - $this->profile->id, - 'profileid'); - $this->out->hidden('groupid-' . $this->group->id, - $this->group->id, - 'groupid'); - if ($this->args) { - foreach ($this->args as $k => $v) { - $this->out->hidden('returnto-' . $k, $v); - } - } - } - - /** - * Action elements - * - * @return void - */ - function formActions() - { - $this->out->submit( - 'submit', - // TRANS: Button text for the form that will make a user administrator. - _m('BUTTON','Make Admin'), - 'submit', - null, - // TRANS: Submit button title. - _m('TOOLTIP','Make this user an admin')); + $this->out->elementStart('li', 'entity_leave'); + $bf = new CancelGroupForm($this->out, $this->group, $this->profile); + $bf->show(); + $this->out->elementEnd('li'); } } diff --git a/classes/Profile.php b/classes/Profile.php index 57522d28dc..126bc25a66 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -384,6 +384,28 @@ class Profile extends Memcached_DataObject } } + /** + * Complete a pending group join on our end... + * + * @param User_group $group + */ + function completeJoinGroup(User_group $group) + { + $ok = null; + $request = Group_join_queue::pkeyGet(array('profile_id' => $this->id, + 'group_id' => $group->id)); + if ($request) { + if (Event::handle('StartJoinGroup', array($group, $this))) { + $ok = Group_member::join($group->id, $this->id); + $request->delete(); + Event::handle('EndJoinGroup', array($group, $this)); + } + } else { + throw new Exception(_m('Invalid group join approval: not pending.')); + } + return $ok; + } + /** * Leave a group that this profile is a member of. * diff --git a/lib/approvegroupform.php b/lib/approvegroupform.php new file mode 100644 index 0000000000..265dc9ba71 --- /dev/null +++ b/lib/approvegroupform.php @@ -0,0 +1,122 @@ +. + * + * @category Form + * @package StatusNet + * @author Evan Prodromou + * @author Sarven Capadisli + * @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 + * @author Sarven Capadisli + * @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 ApproveGroupForm extends Form +{ + /** + * group for user to leave + */ + + var $group = null; + var $profile = null; + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param group $group group to leave + */ + + function __construct($out=null, $group=null, $profile=null) + { + parent::__construct($out); + + $this->group = $group; + $this->profile = $profile; + } + + /** + * ID of the form + * + * @return string ID of the form + */ + + function id() + { + return 'group-cancel-' . $this->group->id; + } + + /** + * class of the form + * + * @return string of the form class + */ + + function formClass() + { + return 'form_group_join 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('approvegroup', + array('id' => $this->group->id), $params); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', _('Approve')); + } +} diff --git a/lib/cancelgroupform.php b/lib/cancelgroupform.php index e71144f0ed..f945a847f7 100644 --- a/lib/cancelgroupform.php +++ b/lib/cancelgroupform.php @@ -54,6 +54,7 @@ class CancelGroupForm extends Form */ var $group = null; + var $profile = null; /** * Constructor @@ -62,11 +63,12 @@ class CancelGroupForm extends Form * @param group $group group to leave */ - function __construct($out=null, $group=null) + function __construct($out=null, $group=null, $profile=null) { parent::__construct($out); $this->group = $group; + $this->profile = $profile; } /** @@ -99,8 +101,12 @@ class CancelGroupForm extends Form function action() { + $params = array(); + if ($this->profile) { + $params['profile_id'] = $this->profile->id; + } return common_local_url('cancelgroup', - array('id' => $this->group->id)); + array('id' => $this->group->id), $params); } /** diff --git a/lib/groupblockform.php b/lib/groupblockform.php new file mode 100644 index 0000000000..279ddf66fb --- /dev/null +++ b/lib/groupblockform.php @@ -0,0 +1,130 @@ + + * @author Sarven Capadisli + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see BlockForm + */ +class GroupBlockForm extends Form +{ + /** + * Profile of user to block + */ + + var $profile = null; + + /** + * Group to block the user from + */ + + var $group = null; + + /** + * Return-to args + */ + + var $args = null; + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param Profile $profile profile of user to block + * @param User_group $group group to block user from + * @param array $args return-to args + */ + function __construct($out=null, $profile=null, $group=null, $args=null) + { + parent::__construct($out); + + $this->profile = $profile; + $this->group = $group; + $this->args = $args; + } + + /** + * ID of the form + * + * @return int ID of the form + */ + function id() + { + // This should be unique for the page. + return 'block-' . $this->profile->id; + } + + /** + * class of the form + * + * @return string class of the form + */ + function formClass() + { + return 'form_group_block'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + function action() + { + return common_local_url('groupblock'); + } + + /** + * Legend of the Form + * + * @return void + */ + function formLegend() + { + // TRANS: Form legend for form to block user from a group. + $this->out->element('legend', null, _('Block user from group')); + } + + /** + * Data elements of the form + * + * @return void + */ + function formData() + { + $this->out->hidden('blockto-' . $this->profile->id, + $this->profile->id, + 'blockto'); + $this->out->hidden('blockgroup-' . $this->group->id, + $this->group->id, + 'blockgroup'); + if ($this->args) { + foreach ($this->args as $k => $v) { + $this->out->hidden('returnto-' . $k, $v); + } + } + } + + /** + * Action elements + * + * @return void + */ + function formActions() + { + $this->out->submit( + 'submit', + // TRANS: Button text for the form that will block a user from a group. + _m('BUTTON','Block'), + 'submit', + null, + // TRANS: Submit button title. + _m('TOOLTIP', 'Block this user')); + } +} diff --git a/lib/groupmemberlist.php b/lib/groupmemberlist.php new file mode 100644 index 0000000000..92dc4029c6 --- /dev/null +++ b/lib/groupmemberlist.php @@ -0,0 +1,18 @@ +group = $group; + } + + function newListItem($profile) + { + return new GroupMemberListItem($profile, $this->group, $this->action); + } +} diff --git a/lib/groupmemberlistitem.php b/lib/groupmemberlistitem.php new file mode 100644 index 0000000000..82fdc4ef2f --- /dev/null +++ b/lib/groupmemberlistitem.php @@ -0,0 +1,105 @@ +group = $group; + } + + function showFullName() + { + parent::showFullName(); + if ($this->profile->isAdmin($this->group)) { + $this->out->text(' '); // for separating the classes. + // TRANS: Indicator in group members list that this user is a group administrator. + $this->out->element('span', 'role', _('Admin')); + } + } + + function showActions() + { + $this->startActions(); + if (Event::handle('StartProfileListItemActionElements', array($this))) { + $this->showSubscribeButton(); + $this->showMakeAdminForm(); + $this->showGroupBlockForm(); + Event::handle('EndProfileListItemActionElements', array($this)); + } + $this->endActions(); + } + + function showMakeAdminForm() + { + $user = common_current_user(); + + if (!empty($user) && + $user->id != $this->profile->id && + ($user->isAdmin($this->group) || $user->hasRight(Right::MAKEGROUPADMIN)) && + !$this->profile->isAdmin($this->group)) { + $this->out->elementStart('li', 'entity_make_admin'); + $maf = new MakeAdminForm($this->out, $this->profile, $this->group, + $this->returnToArgs()); + $maf->show(); + $this->out->elementEnd('li'); + } + + } + + function showGroupBlockForm() + { + $user = common_current_user(); + + if (!empty($user) && $user->id != $this->profile->id && $user->isAdmin($this->group)) { + $this->out->elementStart('li', 'entity_block'); + $bf = new GroupBlockForm($this->out, $this->profile, $this->group, + $this->returnToArgs()); + $bf->show(); + $this->out->elementEnd('li'); + } + } + + function linkAttributes() + { + $aAttrs = parent::linkAttributes(); + + if (common_config('nofollow', 'members')) { + $aAttrs['rel'] .= ' nofollow'; + } + + return $aAttrs; + } + + function homepageAttributes() + { + $aAttrs = parent::linkAttributes(); + + if (common_config('nofollow', 'members')) { + $aAttrs['rel'] = 'nofollow'; + } + + return $aAttrs; + } + + /** + * Fetch necessary return-to arguments for the profile forms + * to return to this list when they're done. + * + * @return array + */ + protected function returnToArgs() + { + $args = array('action' => 'groupmembers', + 'nickname' => $this->group->nickname); + $page = $this->out->arg('page'); + if ($page) { + $args['param-page'] = $page; + } + return $args; + } +} + diff --git a/lib/makeadminform.php b/lib/makeadminform.php new file mode 100644 index 0000000000..de245f3d66 --- /dev/null +++ b/lib/makeadminform.php @@ -0,0 +1,125 @@ + + * @author Sarven Capadisli + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ +class MakeAdminForm extends Form +{ + /** + * Profile of user to block + */ + var $profile = null; + + /** + * Group to block the user from + */ + var $group = null; + + /** + * Return-to args + */ + var $args = null; + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param Profile $profile profile of user to block + * @param User_group $group group to block user from + * @param array $args return-to args + */ + function __construct($out=null, $profile=null, $group=null, $args=null) + { + parent::__construct($out); + + $this->profile = $profile; + $this->group = $group; + $this->args = $args; + } + + /** + * ID of the form + * + * @return int ID of the form + */ + function id() + { + // This should be unique for the page. + return 'makeadmin-' . $this->profile->id; + } + + /** + * class of the form + * + * @return string class of the form + */ + function formClass() + { + return 'form_make_admin'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + function action() + { + return common_local_url('makeadmin', array('nickname' => $this->group->nickname)); + } + + /** + * Legend of the Form + * + * @return void + */ + function formLegend() + { + // TRANS: Form legend for form to make a user a group admin. + $this->out->element('legend', null, _('Make user an admin of the group')); + } + + /** + * Data elements of the form + * + * @return void + */ + function formData() + { + $this->out->hidden('profileid-' . $this->profile->id, + $this->profile->id, + 'profileid'); + $this->out->hidden('groupid-' . $this->group->id, + $this->group->id, + 'groupid'); + if ($this->args) { + foreach ($this->args as $k => $v) { + $this->out->hidden('returnto-' . $k, $v); + } + } + } + + /** + * Action elements + * + * @return void + */ + function formActions() + { + $this->out->submit( + 'submit', + // TRANS: Button text for the form that will make a user administrator. + _m('BUTTON','Make Admin'), + 'submit', + null, + // TRANS: Submit button title. + _m('TOOLTIP','Make this user an admin')); + } +} diff --git a/lib/router.php b/lib/router.php index fcc915b107..fa9fe9aee1 100644 --- a/lib/router.php +++ b/lib/router.php @@ -366,7 +366,7 @@ class Router $m->connect('group/new', array('action' => 'newgroup')); - foreach (array('edit', 'join', 'leave', 'delete', 'cancel') as $v) { + foreach (array('edit', 'join', 'leave', 'delete', 'cancel', 'approve') as $v) { $m->connect('group/:nickname/'.$v, array('action' => $v.'group'), array('nickname' => Nickname::DISPLAY_FMT)); From 61960d36687cba7b4ace801fdd65829314bc0a13 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 21 Mar 2011 17:23:13 -0700 Subject: [PATCH 8/8] Add pending members list to group navigation, if group has joins moderated or if it has pending requests open --- lib/groupnav.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/groupnav.php b/lib/groupnav.php index a2dd6eac00..6a4f5f7e30 100644 --- a/lib/groupnav.php +++ b/lib/groupnav.php @@ -100,6 +100,18 @@ class GroupNav extends Menu $cur = common_current_user(); if ($cur && $cur->isAdmin($this->group)) { + $pending = $this->countPendingMembers(); + if ($pending || $this->group->join_policy == User_group::JOIN_POLICY_MODERATE) { + $this->out->menuItem(common_local_url('groupqueue', array('nickname' => + $nickname)), + // TRANS: Menu item in the group navigation page. Only shown for group administrators. + sprintf(_m('MENU','Pending members (%d)'), $pending), + // TRANS: Tooltip for menu item in the group navigation page. Only shown for group administrators. + // TRANS: %s is the nickname of the group. + sprintf(_m('TOOLTIP','%s pending members'), $nickname), + $action_name == 'groupqueue', + 'nav_group_pending'); + } $this->out->menuItem(common_local_url('blockedfromgroup', array('nickname' => $nickname)), // TRANS: Menu item in the group navigation page. Only shown for group administrators. @@ -141,4 +153,11 @@ class GroupNav extends Menu } $this->out->elementEnd('ul'); } + + function countPendingMembers() + { + $req = new Group_join_queue(); + $req->group_id = $this->group->id; + return $req->count(); + } }