X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=classes%2FSubscription.php;h=4f27537f278ff205260c71c36e7a2b215eb72adf;hb=3ba6374b9d63852d122b792f923208fb720da5f3;hp=0225ed4df97b9282bc8b5efd6d2175f8e216e8f2;hpb=bee50840728ca8af99e988bcce71876849ba61d3;p=quix0rs-gnu-social.git diff --git a/classes/Subscription.php b/classes/Subscription.php index 0225ed4df9..4f27537f27 100644 --- a/classes/Subscription.php +++ b/classes/Subscription.php @@ -24,8 +24,11 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } */ 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 */ @@ -36,19 +39,34 @@ class Subscription extends Memcached_DataObject public $sms; // tinyint(1) default_1 public $token; // varchar(255) public $secret; // varchar(255) + public $uri; // varchar(255) 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' => 255, 'description' => 'authorization token'), + 'secret' => array('type' => 'varchar', 'length' => 255, 'description' => 'token secret'), + 'uri' => array('type' => 'varchar', 'length' => 255, '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'), + ), + ); } /** @@ -56,20 +74,13 @@ 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(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.')); @@ -86,32 +97,39 @@ 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); - - $subscriber->blowSubscriptionCount(); - $other->blowSubscriberCount(); - - $otherUser = User::staticGet('id', $other->id); - - 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()}"); + $otherUser = User::getKV('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(); + + 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(); + + 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()}"); + } } } Event::handle('EndSubscribe', array($subscriber, $other)); } - return true; + return $sub; } /** @@ -127,6 +145,9 @@ class Subscription extends Memcached_DataObject $sub->jabber = 1; $sub->sms = 1; $sub->created = common_sql_now(); + $sub->uri = self::newURI($sub->subscriber, + $sub->subscribed, + $sub->created); $result = $sub->insert(); @@ -141,20 +162,20 @@ class Subscription extends Memcached_DataObject function notify() { - # XXX: add other notifications (Jabber, SMS) here - # XXX: queue this and handle it offline - # XXX: Whatever happens, do it in Twitter-like API, too + // XXX: add other notifications (Jabber, SMS) here + // XXX: queue this and handle it offline + // XXX: Whatever happens, do it in Twitter-like API, too $this->notifyEmail(); } function notifyEmail() { - $subscribedUser = User::staticGet('id', $this->subscribed); + $subscribedUser = User::getKV('id', $this->subscribed); if (!empty($subscribedUser)) { - $subscriber = Profile::staticGet('id', $this->subscriber); + $subscriber = Profile::getKV('id', $this->subscriber); mail_subscribe_notify_profile($subscribedUser, $subscriber); } @@ -164,7 +185,7 @@ 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. @@ -187,29 +208,6 @@ class Subscription extends Memcached_DataObject assert(!empty($sub)); - // @todo: move this block to EndSubscribe handler for - // OMB plugin when it exists. - - if (!empty($sub->token)) { - - $token = new Token(); - - $token->tok = $sub->token; - - if ($token->find(true)) { - - $result = $token->delete(); - - if (!$result) { - common_log_db_error($token, 'DELETE', __FILE__); - // TRANS: Exception thrown when the OMB token for a subscription could not deleted on the server. - throw new Exception(_('Could not delete subscription OMB token.')); - } - } else { - common_log(LOG_ERR, "Couldn't find credentials with token {$token->tok}"); - } - } - $result = $sub->delete(); if (!$result) { @@ -220,6 +218,9 @@ class Subscription extends Memcached_DataObject 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(); @@ -229,10 +230,191 @@ class Subscription extends Memcached_DataObject return; } - function exists($subscriber, $other) + static function exists(Profile $subscriber, Profile $other) { $sub = Subscription::pkeyGet(array('subscriber' => $subscriber->id, 'subscribed' => $other->id)); return (empty($sub)) ? false : true; } + + function asActivity() + { + $subscriber = Profile::getKV('id', $this->subscriber); + $subscribed = Profile::getKV('id', $this->subscribed); + + if (empty($subscriber)) { + throw new Exception(sprintf(_('No profile for the subscriber: %d'), $this->subscriber)); + } + + if (empty($subscribed)) { + throw new Exception(sprintf(_('No profile for the subscribed: %d'), $this->subscribed)); + } + + $act = new Activity(); + + $act->verb = ActivityVerb::FOLLOW; + + // XXX: rationalize this with the URL + + $act->id = $this->getURI(); + + $act->time = strtotime($this->created); + // TRANS: Activity title when subscribing to another person. + $act->title = _m('TITLE','Follow'); + // TRANS: Notification given when one person starts following another. + // TRANS: %1$s is the subscriber, %2$s is the subscribed. + $act->content = sprintf(_('%1$s is now following %2$s.'), + $subscriber->getBestName(), + $subscribed->getBestName()); + + $act->actor = ActivityObject::fromProfile($subscriber); + $act->objects[] = ActivityObject::fromProfile($subscribed); + + $url = common_local_url('AtomPubShowSubscription', + array('subscriber' => $subscriber->id, + 'subscribed' => $subscribed->id)); + + $act->selfLink = $url; + $act->editLink = $url; + + return $act; + } + + /** + * Stream of subscriptions with the same subscriber + * + * Useful for showing pages that list subscriptions in reverse + * chronological order. Has offset & limit to make paging + * easy. + * + * @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 + */ + public static function bySubscriber($profile_id, $offset = 0, $limit = PROFILES_PER_PAGE) + { + // "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 subscriber + * + * Useful for showing pages that list subscriptions in reverse + * chronological order. Has offset & limit to make paging + * easy. + * + * @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 + */ + public static function bySubscribed($profile_id, $offset = 0, $limit = PROFILES_PER_PAGE) + { + // "by subscribed" means it is the list of subscribers we want + $ids = self::getSubscriberIDs($profile_id, $offset, $limit); + return Subscription::listFind('subscriber', $ids); + } + + + // 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) + { + 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; + + $queryoffset = $offset; + $querylimit = $limit; + + 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($queryoffset, $querylimit); + + if (!$sub->find()) { + return array(); + } + + $ids = $sub->fetchAll($get_type); + + // If we're simultaneously filling up cache, remember to slice + if ($offset === 0 && $querylimit === self::CACHE_WINDOW) { + self::cacheSet($cacheKey, $ids); + return array_slice($ids, $offset, $limit); + } + + return $ids; + } + + /** + * Flush cached subscriptions when subscription is updated + * + * Because we cache subscriptions, it's useful to flush them + * here. + * + * @param mixed $dataObject Original version of object + * + * @return boolean success flag. + */ + function update($dataObject=false) + { + self::blow('subscription:by-subscriber:'.$this->subscriber); + self::blow('subscription:by-subscribed:'.$this->subscribed); + + return parent::update($dataObject); + } + + function getURI() + { + if (!empty($this->uri)) { + return $this->uri; + } else { + return self::newURI($this->subscriber, $this->subscribed, $this->created); + } + } + + static function newURI($subscriber_id, $subscribed_id, $created) + { + return TagURI::mint('follow:%d:%d:%s', + $subscriber_id, + $subscribed_id, + common_date_iso8601($created)); + } }