X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=classes%2FSubscription.php;h=c532a3c3de67474b0ca7a638b4cdac939527bcd3;hb=b374e5f08b0a5b0321ddd0ac77baf4675274aba2;hp=e83621eb86ba3bac099f61504c62bc2905276a9c;hpb=e274ec4900172b5a53f38e5d9f44d899bf490dc3;p=quix0rs-gnu-social.git diff --git a/classes/Subscription.php b/classes/Subscription.php index e83621eb86..c532a3c3de 100644 --- a/classes/Subscription.php +++ b/classes/Subscription.php @@ -17,41 +17,51 @@ * along with this program. If not, see . */ -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } +if (!defined('GNUSOCIAL')) { exit(1); } /** * Table Definition for subscription */ -require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; - -class Subscription extends Memcached_DataObject +class Subscription extends Managed_DataObject { const CACHE_WINDOW = 201; const FORCE = true; - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - public $__table = 'subscription'; // table name public $subscriber; // int(4) primary_key not_null public $subscribed; // int(4) primary_key not_null public $jabber; // tinyint(1) default_1 public $sms; // tinyint(1) default_1 - public $token; // varchar(255) - public $secret; // varchar(255) + public $token; // varchar(191) not 255 because utf8mb4 takes more space + public $secret; // varchar(191) not 255 because utf8mb4 takes more space + public $uri; // varchar(191) not 255 because utf8mb4 takes more space public $created; // datetime() not_null public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - /* Static get */ - function staticGet($k,$v=null) - { return Memcached_DataObject::staticGet('Subscription',$k,$v); } - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - function pkeyGet($kv) + public static function schemaDef() { - return Memcached_DataObject::pkeyGet('Subscription', $kv); + return array( + 'fields' => array( + 'subscriber' => array('type' => 'int', 'not null' => true, 'description' => 'profile listening'), + 'subscribed' => array('type' => 'int', 'not null' => true, 'description' => 'profile being listened to'), + 'jabber' => array('type' => 'int', 'size' => 'tiny', 'default' => 1, 'description' => 'deliver jabber messages'), + 'sms' => array('type' => 'int', 'size' => 'tiny', 'default' => 1, 'description' => 'deliver sms messages'), + 'token' => array('type' => 'varchar', 'length' => 191, 'description' => 'authorization token'), + 'secret' => array('type' => 'varchar', 'length' => 191, 'description' => 'token secret'), + 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universally unique identifier'), + 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'), + 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), + ), + 'primary key' => array('subscriber', 'subscribed'), + 'unique keys' => array( + 'subscription_uri_key' => array('uri'), + ), + 'indexes' => array( + 'subscription_subscriber_idx' => array('subscriber', 'created'), + 'subscription_subscribed_idx' => array('subscribed', 'created'), + 'subscription_token_idx' => array('token'), + ), + ); } /** @@ -64,16 +74,8 @@ class Subscription extends Memcached_DataObject * @return mixed Subscription or Subscription_queue: new subscription info */ - static function start($subscriber, $other, $force=false) + static function start(Profile $subscriber, Profile $other, $force=false) { - // @fixme should we enforce this as profiles in callers instead? - if ($subscriber instanceof User) { - $subscriber = $subscriber->getProfile(); - } - if ($other instanceof User) { - $other = $other->getProfile(); - } - if (!$subscriber->hasRight(Right::SUBSCRIBE)) { // TRANS: Exception thrown when trying to subscribe while being banned from subscribing. throw new Exception(_('You have been banned from subscribing.')); @@ -81,7 +83,7 @@ class Subscription extends Memcached_DataObject if (self::exists($subscriber, $other)) { // TRANS: Exception thrown when trying to subscribe while already subscribed. - throw new Exception(_('Already subscribed!')); + throw new AlreadyFulfilledException(_('Already subscribed!')); } if ($other->hasBlocked($subscriber)) { @@ -90,12 +92,17 @@ class Subscription extends Memcached_DataObject } if (Event::handle('StartSubscribe', array($subscriber, $other))) { - $otherUser = User::staticGet('id', $other->id); - if ($otherUser && $otherUser->subscribe_policy == User::SUBSCRIBE_POLICY_MODERATE && !$force) { - $sub = Subscription_queue::saveNew($subscriber, $other); - $sub->notify(); + // unless subscription is forced, the user policy for subscription approvals is tested + if (!$force && $other->requiresSubscriptionApproval($subscriber)) { + try { + $sub = Subscription_queue::saveNew($subscriber, $other); + $sub->notify(); + } catch (AlreadyFulfilledException $e) { + $sub = Subscription_queue::getSubQueue($subscriber, $other); + } } else { - $sub = self::saveNew($subscriber->id, $other->id); + $otherUser = User::getKV('id', $other->id); + $sub = self::saveNew($subscriber, $other); $sub->notify(); self::blow('user:notices_with_friends:%d', $subscriber->id); @@ -106,42 +113,60 @@ class Subscription extends Memcached_DataObject $subscriber->blowSubscriptionCount(); $other->blowSubscriberCount(); - if (!empty($otherUser) && + if ($otherUser instanceof User && $otherUser->autosubscribe && !self::exists($other, $subscriber) && !$subscriber->hasBlocked($other)) { try { self::start($other, $subscriber); + } catch (AlreadyFulfilledException $e) { + // This shouldn't happen due to !self::exists above + common_debug('Tried to autosubscribe a user to its new subscriber.'); } catch (Exception $e) { common_log(LOG_ERR, "Exception during autosubscribe of {$other->nickname} to profile {$subscriber->id}: {$e->getMessage()}"); } } } - Event::handle('EndSubscribe', array($subscriber, $other)); + if ($sub instanceof Subscription) { // i.e. not Subscription_queue + Event::handle('EndSubscribe', array($subscriber, $other)); + } } return $sub; } + static function ensureStart(Profile $subscriber, Profile $other, $force=false) + { + try { + $sub = self::start($subscriber, $other, $force); + } catch (AlreadyFulfilledException $e) { + return self::getSubscription($subscriber, $other); + } + return $sub; + } + /** * Low-level subscription save. * Outside callers should use Subscription::start() */ - protected function saveNew($subscriber_id, $other_id) + protected static function saveNew(Profile $subscriber, Profile $other) { $sub = new Subscription(); - $sub->subscriber = $subscriber_id; - $sub->subscribed = $other_id; + $sub->subscriber = $subscriber->getID(); + $sub->subscribed = $other->getID(); $sub->jabber = 1; $sub->sms = 1; $sub->created = common_sql_now(); + $sub->uri = self::newUri($subscriber, + $other, + $sub->created); $result = $sub->insert(); - if (!$result) { + if ($result===false) { common_log_db_error($sub, 'INSERT', __FILE__); // TRANS: Exception thrown when a subscription could not be stored on the server. throw new Exception(_('Could not save subscription.')); @@ -161,11 +186,11 @@ class Subscription extends Memcached_DataObject function notifyEmail() { - $subscribedUser = User::staticGet('id', $this->subscribed); + $subscribedUser = User::getKV('id', $this->subscribed); - if (!empty($subscribedUser)) { + if ($subscribedUser instanceof User) { - $subscriber = Profile::staticGet('id', $this->subscriber); + $subscriber = Profile::getKV('id', $this->subscriber); mail_subscribe_notify_profile($subscribedUser, $subscriber); } @@ -175,11 +200,11 @@ class Subscription extends Memcached_DataObject * Cancel a subscription * */ - function cancel($subscriber, $other) + static function cancel(Profile $subscriber, Profile $other) { if (!self::exists($subscriber, $other)) { // TRANS: Exception thrown when trying to unsibscribe without a subscription. - throw new Exception(_('Not subscribed!')); + throw new AlreadyFulfilledException(_('Not subscribed!')); } // Don't allow deleting self subs @@ -220,17 +245,43 @@ class Subscription extends Memcached_DataObject return; } - function exists($subscriber, $other) + static function exists(Profile $subscriber, Profile $other) + { + try { + $sub = self::getSubscription($subscriber, $other); + } catch (NoResultException $e) { + return false; + } + + return true; + } + + static function getSubscription(Profile $subscriber, Profile $other) + { + // This is essentially a pkeyGet but we have an object to return in NoResultException + $sub = new Subscription(); + $sub->subscriber = $subscriber->id; + $sub->subscribed = $other->id; + if (!$sub->find(true)) { + throw new NoResultException($sub); + } + return $sub; + } + + public function getSubscriber() + { + return Profile::getByID($this->subscriber); + } + + public function getSubscribed() { - $sub = Subscription::pkeyGet(array('subscriber' => $subscriber->id, - 'subscribed' => $other->id)); - return (empty($sub)) ? false : true; + return Profile::getByID($this->subscribed); } function asActivity() { - $subscriber = Profile::staticGet('id', $this->subscriber); - $subscribed = Profile::staticGet('id', $this->subscribed); + $subscriber = $this->getSubscriber(); + $subscribed = $this->getSubscribed(); $act = new Activity(); @@ -238,10 +289,7 @@ class Subscription extends Memcached_DataObject // XXX: rationalize this with the URL - $act->id = TagURI::mint('follow:%d:%d:%s', - $subscriber->id, - $subscribed->id, - common_date_iso8601($this->created)); + $act->id = $this->getUri(); $act->time = strtotime($this->created); // TRANS: Activity title when subscribing to another person. @@ -252,8 +300,8 @@ class Subscription extends Memcached_DataObject $subscriber->getBestName(), $subscribed->getBestName()); - $act->actor = ActivityObject::fromProfile($subscriber); - $act->objects[] = ActivityObject::fromProfile($subscribed); + $act->actor = $subscriber->asActivityObject(); + $act->objects[] = $subscribed->asActivityObject(); $url = common_local_url('AtomPubShowSubscription', array('subscriber' => $subscriber->id, @@ -272,117 +320,100 @@ class Subscription extends Memcached_DataObject * chronological order. Has offset & limit to make paging * easy. * - * @param integer $subscriberId Profile ID of the subscriber + * @param integer $profile_id ID of the subscriber profile * @param integer $offset Offset from latest * @param integer $limit Maximum number to fetch * * @return Subscription stream of subscriptions; use fetch() to iterate */ - static function bySubscriber($subscriberId, - $offset = 0, - $limit = PROFILES_PER_PAGE) - { - if ($offset + $limit > self::CACHE_WINDOW) { - return new ArrayWrapper(self::realBySubscriber($subscriberId, - $offset, - $limit)); - } else { - $key = 'subscription:by-subscriber:'.$subscriberId; - $window = self::cacheGet($key); - if ($window === false) { - $window = self::realBySubscriber($subscriberId, - 0, - self::CACHE_WINDOW); - self::cacheSet($key, $window); - } - return new ArrayWrapper(array_slice($window, - $offset, - $limit)); - } - } - - private static function realBySubscriber($subscriberId, - $offset, - $limit) + public static function bySubscriber($profile_id, $offset = 0, $limit = PROFILES_PER_PAGE) { - $sub = new Subscription(); - - $sub->subscriber = $subscriberId; - - $sub->whereAdd('subscribed != ' . $subscriberId); - - $sub->orderBy('created DESC'); - $sub->limit($offset, $limit); - - $sub->find(); - - $subs = array(); - - while ($sub->fetch()) { - $subs[] = clone($sub); - } - - return $subs; + // "by subscriber" means it is the list of subscribed users we want + $ids = self::getSubscribedIDs($profile_id, $offset, $limit); + return Subscription::listFind('subscribed', $ids); } /** - * Stream of subscriptions with the same subscribed profile + * Stream of subscriptions with the same subscriber * - * Useful for showing pages that list subscribers in reverse + * Useful for showing pages that list subscriptions in reverse * chronological order. Has offset & limit to make paging * easy. * - * @param integer $subscribedId Profile ID of the subscribed + * @param integer $profile_id ID of the subscribed profile * @param integer $offset Offset from latest * @param integer $limit Maximum number to fetch * * @return Subscription stream of subscriptions; use fetch() to iterate */ - static function bySubscribed($subscribedId, - $offset = 0, - $limit = PROFILES_PER_PAGE) + public static function bySubscribed($profile_id, $offset = 0, $limit = PROFILES_PER_PAGE) { - if ($offset + $limit > self::CACHE_WINDOW) { - return new ArrayWrapper(self::realBySubscribed($subscribedId, - $offset, - $limit)); - } else { - $key = 'subscription:by-subscribed:'.$subscribedId; - $window = self::cacheGet($key); - if ($window === false) { - $window = self::realBySubscribed($subscribedId, - 0, - self::CACHE_WINDOW); - self::cacheSet($key, $window); - } - return new ArrayWrapper(array_slice($window, - $offset, - $limit)); - } + // "by subscribed" means it is the list of subscribers we want + $ids = self::getSubscriberIDs($profile_id, $offset, $limit); + return Subscription::listFind('subscriber', $ids); } - private static function realBySubscribed($subscribedId, - $offset, - $limit) + + // The following are helper functions to the subscription lists, + // notably the public ones get used in places such as Profile + public static function getSubscribedIDs($profile_id, $offset, $limit) { + return self::getSubscriptionIDs('subscribed', $profile_id, $offset, $limit); + } + + public static function getSubscriberIDs($profile_id, $offset, $limit) { + return self::getSubscriptionIDs('subscriber', $profile_id, $offset, $limit); + } + + private static function getSubscriptionIDs($get_type, $profile_id, $offset, $limit) { - $sub = new Subscription(); + switch ($get_type) { + case 'subscribed': + $by_type = 'subscriber'; + break; + case 'subscriber': + $by_type = 'subscribed'; + break; + default: + throw new Exception('Bad type argument to getSubscriptionIDs'); + } + + $cacheKey = 'subscription:by-'.$by_type.':'.$profile_id; - $sub->subscribed = $subscribedId; + $queryoffset = $offset; + $querylimit = $limit; - $sub->whereAdd('subscriber != ' . $subscribedId); + if ($offset + $limit <= self::CACHE_WINDOW) { + // Oh, it seems it should be cached + $ids = self::cacheGet($cacheKey); + if (is_array($ids)) { + return array_slice($ids, $offset, $limit); + } + // Being here indicates we didn't find anything cached + // so we'll have to fill it up simultaneously + $queryoffset = 0; + $querylimit = self::CACHE_WINDOW; + } + $sub = new Subscription(); + $sub->$by_type = $profile_id; + $sub->selectAdd($get_type); + $sub->whereAdd("{$get_type} != {$profile_id}"); $sub->orderBy('created DESC'); - $sub->limit($offset, $limit); + $sub->limit($queryoffset, $querylimit); - $sub->find(); + if (!$sub->find()) { + return array(); + } - $subs = array(); + $ids = $sub->fetchAll($get_type); - while ($sub->fetch()) { - $subs[] = clone($sub); + // If we're simultaneously filling up cache, remember to slice + if ($queryoffset === 0 && $querylimit === self::CACHE_WINDOW) { + self::cacheSet($cacheKey, $ids); + return array_slice($ids, $offset, $limit); } - return $subs; + return $ids; } /** @@ -391,17 +422,20 @@ class Subscription extends Memcached_DataObject * Because we cache subscriptions, it's useful to flush them * here. * - * @param mixed $orig Original version of object + * @param mixed $dataObject Original version of object * * @return boolean success flag. */ - function update($orig=null) + function update($dataObject=false) { - $result = parent::update($orig); - self::blow('subscription:by-subscriber:'.$this->subscriber); self::blow('subscription:by-subscribed:'.$this->subscribed); - return $result; + return parent::update($dataObject); + } + + public function getUri() + { + return $this->uri ?: self::newUri($this->getSubscriber(), $this->getSubscribed(), $this->created); } }