diff --git a/actions/approvegroup.php b/actions/approvegroup.php index 5039cfae6b..95338a4af3 100644 --- a/actions/approvegroup.php +++ b/actions/approvegroup.php @@ -152,9 +152,9 @@ class ApprovegroupAction extends Action try { if ($this->approve) { - $this->profile->completeJoinGroup($this->group); + $this->request->complete(); } elseif ($this->cancel) { - $this->profile->cancelJoinGroup($this->group); + $this->request->abort(); } } catch (Exception $e) { common_log(LOG_ERROR, "Exception canceling group sub: " . $e->getMessage()); diff --git a/actions/cancelgroup.php b/actions/cancelgroup.php index 57df1a10a7..15eb2b5dc3 100644 --- a/actions/cancelgroup.php +++ b/actions/cancelgroup.php @@ -139,7 +139,7 @@ class CancelgroupAction extends Action parent::handle($args); try { - $this->profile->cancelJoinGroup($this->group); + $this->request->abort(); } 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. diff --git a/actions/profilesettings.php b/actions/profilesettings.php index e1d686ca29..7aa987f3a5 100644 --- a/actions/profilesettings.php +++ b/actions/profilesettings.php @@ -192,6 +192,17 @@ class ProfilesettingsAction extends SettingsAction ($this->arg('autosubscribe')) ? $this->boolean('autosubscribe') : $user->autosubscribe); $this->elementEnd('li'); + $this->elementStart('li'); + $this->dropdown('subscribe_policy', + // TRANS: Dropdown field label on profile settings, for what policies to apply when someone else tries to subscribe to your updates. + _('Subscription policy'), + array(User::SUBSCRIBE_POLICY_OPEN => _('Let anyone follow me'), + User::SUBSCRIBE_POLICY_MODERATE => _('Ask me first')), + // TRANS: Dropdown field title on group edit form. + _('Whether other users need your permission to follow your updates.'), + false, + (empty($user->subscribe_policy)) ? User::SUBSCRIBE_POLICY_OPEN : $user->subscribe_policy); + $this->elementEnd('li'); } $this->elementEnd('ul'); // TRANS: Button to save input in profile settings. @@ -234,6 +245,7 @@ class ProfilesettingsAction extends SettingsAction $bio = $this->trimmed('bio'); $location = $this->trimmed('location'); $autosubscribe = $this->boolean('autosubscribe'); + $subscribe_policy = $this->trimmed('subscribe_policy'); $language = $this->trimmed('language'); $timezone = $this->trimmed('timezone'); $tagstring = $this->trimmed('tags'); @@ -333,11 +345,12 @@ class ProfilesettingsAction extends SettingsAction } // XXX: XOR - if ($user->autosubscribe ^ $autosubscribe) { + if (($user->autosubscribe ^ $autosubscribe) || $user->subscribe_policy != $subscribe_policy) { $original = clone($user); $user->autosubscribe = $autosubscribe; + $user->subscribe_policy = $subscribe_policy; $result = $user->update($original); @@ -345,7 +358,7 @@ class ProfilesettingsAction extends SettingsAction common_log_db_error($user, 'UPDATE', __FILE__); // TRANS: Server error thrown when user profile settings could not be updated to // TRANS: automatically subscribe to any subscriber. - $this->serverError(_('Could not update user for autosubscribe.')); + $this->serverError(_('Could not update user for autosubscribe or subscribe_policy.')); return; } } diff --git a/classes/Group_join_queue.php b/classes/Group_join_queue.php index 48b36cae2d..acf3a13957 100644 --- a/classes/Group_join_queue.php +++ b/classes/Group_join_queue.php @@ -56,6 +56,71 @@ class Group_join_queue extends Managed_DataObject return $rq; } + function getMember() + { + $member = Profile::staticGet('id', $this->profile_id); + + if (empty($member)) { + // TRANS: Exception thrown providing an invalid profile ID. + // TRANS: %s is the invalid profile ID. + throw new Exception(sprintf(_("Profile ID %s is invalid."),$this->profile_id)); + } + + return $member; + } + + function getGroup() + { + $group = User_group::staticGet('id', $this->group_id); + + if (empty($group)) { + // TRANS: Exception thrown providing an invalid group ID. + // TRANS: %s is the invalid group ID. + throw new Exception(sprintf(_("Group ID %s is invalid."),$this->group_id)); + } + + return $group; + } + + /** + * Abort the pending group join... + * + * @param User_group $group + */ + function abort() + { + $profile = $this->getMember(); + $group = $this->getGroup(); + if ($request) { + if (Event::handle('StartCancelJoinGroup', array($profile, $group))) { + $this->delete(); + Event::handle('EndCancelJoinGroup', array($profile, $group)); + } + } + } + + /** + * Complete a pending group join... + * + * @return Group_member object on success + */ + function complete(User_group $group) + { + $join = null; + $profile = $this->getMember(); + $group = $this->getGroup(); + if (Event::handle('StartJoinGroup', array($profile, $group))) { + $join = Group_member::join($group->id, $profile->id); + $this->delete(); + Event::handle('EndJoinGroup', array($profile, $group)); + } + if (!$join) { + throw new Exception('Internal error: group join failed.'); + } + $join->notify(); + return $join; + } + /** * Send notifications via email etc to group administrators about * this exciting new pending moderation queue item! diff --git a/classes/Profile.php b/classes/Profile.php index 9a145a0018..98fe9ede2f 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -297,49 +297,6 @@ class Profile extends Memcached_DataObject return $join; } - /** - * 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)); - } - } - } - - /** - * Complete a pending group join on our end... - * - * @param User_group $group - */ - function completeJoinGroup(User_group $group) - { - $join = null; - $request = Group_join_queue::pkeyGet(array('profile_id' => $this->id, - 'group_id' => $group->id)); - if ($request) { - if (Event::handle('StartJoinGroup', array($group, $this))) { - $join = Group_member::join($group->id, $this->id); - $request->delete(); - Event::handle('EndJoinGroup', array($group, $this)); - } - } else { - // TRANS: Exception thrown trying to approve a non-existing group join request. - throw new Exception(_('Invalid group join approval: not pending.')); - } - if ($join) { - $join->notify(); - } - return $join; - } - /** * Leave a group that this profile is a member of. * @@ -363,47 +320,6 @@ class Profile extends Memcached_DataObject } } - /** - * Request a subscription to another local or remote profile. - * This will result in either the subscription going through - * immediately, being queued for approval, or being rejected - * immediately. - * - * @param Profile $profile - * @return mixed: Subscription or Subscription_queue object on success - * @throws Exception of various types on invalid state - */ - function subscribe($profile) - { - // - } - - /** - * Cancel an outstanding subscription request to the other profile. - * - * @param Profile $profile - */ - function cancelSubscribe($profile) - { - $request = Subscribe_join_queue::pkeyGet(array('subscriber' => $this->id, - 'subscribed' => $profile->id)); - if ($request) { - if (Event::handle('StartCancelSubscription', array($this, $profile))) { - $request->delete(); - Event::handle('EndCancelSubscription', array($this, $profile)); - } - } - } - - /** - * - * @param $profile - */ - function completeSubscribe($profile) - { - - } - function getSubscriptions($offset=0, $limit=null) { $subs = Subscription::bySubscriber($this->id, diff --git a/classes/Subscription.php b/classes/Subscription.php index 797e6fef1c..70d351a0f7 100644 --- a/classes/Subscription.php +++ b/classes/Subscription.php @@ -27,6 +27,7 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; class Subscription extends Memcached_DataObject { const CACHE_WINDOW = 201; + const FORCE = true; ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -58,11 +59,12 @@ class Subscription extends Memcached_DataObject * * @param Profile $subscriber party to receive new notices * @param Profile $other party sending notices; publisher + * @param bool $force pass Subscription::FORCE to override local subscription approval * - * @return Subscription new subscription + * @return mixed Subscription or Subscription_queue: new subscription info */ - static function start($subscriber, $other) + static function start($subscriber, $other, $force=false) { // @fixme should we enforce this as profiles in callers instead? if ($subscriber instanceof User) { @@ -88,28 +90,32 @@ class Subscription extends Memcached_DataObject } if (Event::handle('StartSubscribe', array($subscriber, $other))) { - $sub = self::saveNew($subscriber->id, $other->id); - $sub->notify(); - - self::blow('user:notices_with_friends:%d', $subscriber->id); - - self::blow('subscription:by-subscriber:'.$subscriber->id); - self::blow('subscription:by-subscribed:'.$other->id); - - $subscriber->blowSubscriptionCount(); - $other->blowSubscriberCount(); - $otherUser = User::staticGet('id', $other->id); + if ($otherUser && $otherUser->subscribe_policy == User::SUBSCRIBE_POLICY_MODERATE && !$force) { + $sub = Subscription_queue::saveNew($subscriber, $other); + $sub->notify(); + } else { + $sub = self::saveNew($subscriber->id, $other->id); + $sub->notify(); - if (!empty($otherUser) && - $otherUser->autosubscribe && - !self::exists($other, $subscriber) && - !$subscriber->hasBlocked($other)) { + self::blow('user:notices_with_friends:%d', $subscriber->id); - try { - self::start($other, $subscriber); - } catch (Exception $e) { - common_log(LOG_ERR, "Exception during autosubscribe of {$other->nickname} to profile {$subscriber->id}: {$e->getMessage()}"); + self::blow('subscription:by-subscriber:'.$subscriber->id); + self::blow('subscription:by-subscribed:'.$other->id); + + $subscriber->blowSubscriptionCount(); + $other->blowSubscriberCount(); + + if (!empty($otherUser) && + $otherUser->autosubscribe && + !self::exists($other, $subscriber) && + !$subscriber->hasBlocked($other)) { + + try { + self::start($other, $subscriber); + } catch (Exception $e) { + common_log(LOG_ERR, "Exception during autosubscribe of {$other->nickname} to profile {$subscriber->id}: {$e->getMessage()}"); + } } } diff --git a/classes/Subscription_queue.php b/classes/Subscription_queue.php index 6bf4a681b2..e7572ae725 100644 --- a/classes/Subscription_queue.php +++ b/classes/Subscription_queue.php @@ -56,6 +56,35 @@ class Subscription_queue extends Managed_DataObject return $rq; } + /** + * Complete a pending subscription, as we've got approval of some sort. + * + * @return Subscription + */ + public function complete() + { + $subscriber = Profile::staticGet('id', $this->subscriber); + $subscribed = Profile::staticGet('id', $this->subscribed); + $sub = Subscription::start($subscriber, $other, Subscription::FORCE); + if ($sub) { + $this->delete(); + } + return $sub; + } + + /** + * Cancel an outstanding subscription request to the other profile. + */ + public function abort($profile) + { + $subscriber = Profile::staticGet('id', $this->subscriber); + $subscribed = Profile::staticGet('id', $this->subscribed); + if (Event::handle('StartCancelSubscription', array($subscriber, $subscribed))) { + $this->delete(); + Event::handle('EndCancelSubscription', array($subscriber, $subscribed)); + } + } + /** * Send notifications via email etc to group administrators about * this exciting new pending moderation queue item! diff --git a/classes/User.php b/classes/User.php index 1a3a7dfd72..5945456b18 100644 --- a/classes/User.php +++ b/classes/User.php @@ -30,6 +30,9 @@ require_once 'Validate.php'; class User extends Memcached_DataObject { + const SUBSCRIBE_POLICY_OPEN = 0; + const SUBSCRIBE_POLICY_MODERATE = 1; + ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -55,6 +58,7 @@ class User extends Memcached_DataObject public $smsemail; // varchar(255) public $uri; // varchar(255) unique_key public $autosubscribe; // tinyint(1) + public $subscribe_policy; // tinyint(1) public $urlshorteningservice; // varchar(50) default_ur1.ca public $inboxed; // tinyint(1) public $design_id; // int(4) diff --git a/classes/statusnet.ini b/classes/statusnet.ini index f648fb3fbf..ab2c3d02ef 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -590,6 +590,7 @@ smsreplies = 17 smsemail = 2 uri = 2 autosubscribe = 17 +subscribe_policy = 17 urlshorteningservice = 2 inboxed = 17 design_id = 1 diff --git a/db/core.php b/db/core.php index dfba0f8cd4..f44a85cad7 100644 --- a/db/core.php +++ b/db/core.php @@ -118,6 +118,7 @@ $schema['user'] = array( 'smsemail' => array('type' => 'varchar', 'length' => 255, 'description' => 'built from sms and carrier'), 'uri' => array('type' => 'varchar', 'length' => 255, 'description' => 'universally unique identifier, usually a tag URI'), 'autosubscribe' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'automatically subscribe to users who subscribe to us'), + 'subscribe_policy' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => '0 = anybody can subscribe; 1 = require approval'), 'urlshorteningservice' => array('type' => 'varchar', 'length' => 50, 'default' => 'internal', 'description' => 'service to use for auto-shortening URLs'), 'inboxed' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'has an inbox been created for this user?'), 'design_id' => array('type' => 'int', 'description' => 'id of a design'),