public $__table = 'notice'; // table name
public $id; // int(4) primary_key not_null
- public $profile_id; // int(4) not_null
+ public $profile_id; // int(4) multiple_key not_null
public $uri; // varchar(255) unique_key
- public $content; // text()
- public $rendered; // text()
+ public $content; // text
+ public $rendered; // text
public $url; // varchar(255)
- public $created; // datetime() not_null
- public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+ public $created; // datetime multiple_key not_null default_0000-00-00%2000%3A00%3A00
+ public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
public $reply_to; // int(4)
- public $is_local; // tinyint(1)
+ public $is_local; // int(4)
public $source; // varchar(32)
public $conversation; // int(4)
public $lat; // decimal(10,7)
public $lon; // decimal(10,7)
public $location_id; // int(4)
public $location_ns; // int(4)
+ public $repeat_of; // int(4)
/* Static get */
- function staticGet($k,$v=NULL) {
+ function staticGet($k,$v=NULL)
+ {
return Memcached_DataObject::staticGet('Notice',$k,$v);
}
function delete()
{
- $this->blowCaches(true);
- $this->blowFavesCache(true);
- $this->blowSubsCache(true);
-
// For auditing purposes, save a record that the notice
// was deleted.
$deleted->created = $this->created;
$deleted->deleted = common_sql_now();
- $this->query('BEGIN');
-
$deleted->insert();
- //Null any notices that are replies to this notice
- $this->query(sprintf("UPDATE notice set reply_to = null WHERE reply_to = %d", $this->id));
- $related = array('Reply',
- 'Fave',
- 'Notice_tag',
- 'Group_inbox',
- 'Queue_item',
- 'Notice_inbox');
-
- foreach ($related as $cls) {
- $inst = new $cls();
- $inst->notice_id = $this->id;
- $inst->delete();
- }
+ // Clear related records
+
+ $this->clearReplies();
+ $this->clearRepeats();
+ $this->clearFaves();
+ $this->clearTags();
+ $this->clearGroupInboxes();
+
+ // NOTE: we don't clear inboxes
+ // NOTE: we don't clear queue items
+
$result = parent::delete();
- $this->query('COMMIT');
}
function saveTags()
foreach(array_unique($hashtags) as $hashtag) {
/* elide characters we don't want in the tag */
$this->saveTag($hashtag);
+ self::blow('profile:notice_ids_tagged:%d:%s', $this->profile_id, $tag->tag);
}
return true;
}
$last_error->message));
return;
}
+
+ // if it's saved, blow its cache
+ $tag->blowCache(false);
}
- static function saveNew($profile_id, $content, $source=null,
- $is_local=Notice::LOCAL_PUBLIC, $reply_to=null, $uri=null, $created=null,
- $lat=null, $lon=null, $location_id=null, $location_ns=null) {
+ /**
+ * Save a new notice and push it out to subscribers' inboxes.
+ * Poster's permissions are checked before sending.
+ *
+ * @param int $profile_id Profile ID of the poster
+ * @param string $content source message text; links may be shortened
+ * per current user's preference
+ * @param string $source source key ('web', 'api', etc)
+ * @param array $options Associative array of optional properties:
+ * string 'created' timestamp of notice; defaults to now
+ * int 'is_local' source/gateway ID, one of:
+ * Notice::LOCAL_PUBLIC - Local, ok to appear in public timeline
+ * Notice::REMOTE_OMB - Sent from a remote OMB service;
+ * hide from public timeline but show in
+ * local "and friends" timelines
+ * Notice::LOCAL_NONPUBLIC - Local, but hide from public timeline
+ * Notice::GATEWAY - From another non-OMB service;
+ * will not appear in public views
+ * float 'lat' decimal latitude for geolocation
+ * float 'lon' decimal longitude for geolocation
+ * int 'location_id' geoname identifier
+ * int 'location_ns' geoname namespace to interpret location_id
+ * int 'reply_to'; notice ID this is a reply to
+ * int 'repeat_of'; notice ID this is a repeat of
+ * string 'uri' permalink to notice; defaults to local notice URL
+ *
+ * @return Notice
+ * @throws ClientException
+ */
+ static function saveNew($profile_id, $content, $source, $options=null) {
+ $defaults = array('uri' => null,
+ 'reply_to' => null,
+ 'repeat_of' => null);
+
+ if (!empty($options)) {
+ $options = $options + $defaults;
+ extract($options);
+ }
+
+ if (!isset($is_local)) {
+ $is_local = Notice::LOCAL_PUBLIC;
+ }
$profile = Profile::staticGet($profile_id);
$notice->source = $source;
$notice->uri = $uri;
- $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final);
+ // Handle repeat case
+
+ if (isset($repeat_of)) {
+ $notice->repeat_of = $repeat_of;
+ } else {
+ $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final);
+ }
if (!empty($notice->reply_to)) {
$reply = Notice::staticGet('id', $notice->reply_to);
if (!empty($lat) && !empty($lon)) {
$notice->lat = $lat;
$notice->lon = $lon;
+ }
+
+ if (!empty($location_ns) && !empty($location_id)) {
$notice->location_id = $location_id;
$notice->location_ns = $location_ns;
- } else if (!empty($location_ns) && !empty($location_id)) {
- $location = Location::fromId($location_id, $location_ns);
- if (!empty($location)) {
- $notice->lat = $location->lat;
- $notice->lon = $location->lon;
- $notice->location_id = $location_id;
- $notice->location_ns = $location_ns;
- }
- } else {
- $notice->lat = $profile->lat;
- $notice->lon = $profile->lon;
- $notice->location_id = $profile->location_id;
- $notice->location_ns = $profile->location_ns;
}
if (Event::handle('StartNoticeSave', array(&$notice))) {
// XXX: some of these functions write to the DB
- $notice->query('BEGIN');
-
$id = $notice->insert();
if (!$id) {
}
}
- // XXX: do we need to change this for remote users?
+ }
- $notice->saveTags();
+ # Clear the cache for subscribed users, so they'll update at next request
+ # XXX: someone clever could prepend instead of clearing the cache
+ $notice->blowOnInsert();
- $notice->addToInboxes();
+ if (common_config('queue', 'inboxes')) {
+ $qm = QueueManager::get();
+ $qm->enqueue($notice, 'distrib');
+ } else {
+ $handler = new DistribQueueHandler();
+ $handler->handle($notice);
+ }
- $notice->saveUrls();
+ return $notice;
+ }
- $notice->query('COMMIT');
+ function blowOnInsert()
+ {
+ self::blow('profile:notice_ids:%d', $this->profile_id);
+ self::blow('public');
- Event::handle('EndNoticeSave', array($notice));
+ if ($this->conversation != $this->id) {
+ self::blow('notice:conversation_ids:%d', $this->conversation);
}
- # Clear the cache for subscribed users, so they'll update at next request
- # XXX: someone clever could prepend instead of clearing the cache
+ if (!empty($this->repeat_of)) {
+ self::blow('notice:repeats:%d', $this->repeat_of);
+ }
- $notice->blowCaches();
+ $original = Notice::staticGet('id', $this->repeat_of);
- return $notice;
+ if (!empty($original)) {
+ $originalUser = User::staticGet('id', $original->profile_id);
+ if (!empty($originalUser)) {
+ self::blow('user:repeats_of_me:%d', $originalUser->id);
+ }
+ }
+
+ $profile = Profile::staticGet($this->profile_id);
+ $profile->blowNoticeCount();
}
/** save all urls in the notice to the db
return $att;
}
- function blowCaches($blowLast=false)
- {
- $this->blowSubsCache($blowLast);
- $this->blowNoticeCache($blowLast);
- $this->blowRepliesCache($blowLast);
- $this->blowPublicCache($blowLast);
- $this->blowTagCache($blowLast);
- $this->blowGroupCache($blowLast);
- $this->blowConversationCache($blowLast);
- $profile = Profile::staticGet($this->profile_id);
- $profile->blowNoticeCount();
- }
-
- function blowConversationCache($blowLast=false)
- {
- $cache = common_memcache();
- if ($cache) {
- $ck = common_cache_key('notice:conversation_ids:'.$this->conversation);
- $cache->delete($ck);
- if ($blowLast) {
- $cache->delete($ck.';last');
- }
- }
- }
-
- function blowGroupCache($blowLast=false)
- {
- $cache = common_memcache();
- if ($cache) {
- $group_inbox = new Group_inbox();
- $group_inbox->notice_id = $this->id;
- if ($group_inbox->find()) {
- while ($group_inbox->fetch()) {
- $cache->delete(common_cache_key('user_group:notice_ids:' . $group_inbox->group_id));
- if ($blowLast) {
- $cache->delete(common_cache_key('user_group:notice_ids:' . $group_inbox->group_id.';last'));
- }
- $member = new Group_member();
- $member->group_id = $group_inbox->group_id;
- if ($member->find()) {
- while ($member->fetch()) {
- $cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id));
- if ($blowLast) {
- $cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id . ';last'));
- }
- }
- }
- }
- }
- $group_inbox->free();
- unset($group_inbox);
- }
- }
-
- function blowTagCache($blowLast=false)
- {
- $cache = common_memcache();
- if ($cache) {
- $tag = new Notice_tag();
- $tag->notice_id = $this->id;
- if ($tag->find()) {
- while ($tag->fetch()) {
- $tag->blowCache($blowLast);
- $ck = 'profile:notice_ids_tagged:' . $this->profile_id . ':' . $tag->tag;
-
- $cache->delete($ck);
- if ($blowLast) {
- $cache->delete($ck . ';last');
- }
- }
- }
- $tag->free();
- unset($tag);
- }
- }
-
- function blowSubsCache($blowLast=false)
- {
- $cache = common_memcache();
- if ($cache) {
- $user = new User();
-
- $UT = common_config('db','type')=='pgsql'?'"user"':'user';
- $user->query('SELECT id ' .
-
- "FROM $UT JOIN subscription ON $UT.id = subscription.subscriber " .
- 'WHERE subscription.subscribed = ' . $this->profile_id);
-
- while ($user->fetch()) {
- $cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id));
- $cache->delete(common_cache_key('notice_inbox:by_user_own:'.$user->id));
- if ($blowLast) {
- $cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id.';last'));
- $cache->delete(common_cache_key('notice_inbox:by_user_own:'.$user->id.';last'));
- }
- }
- $user->free();
- unset($user);
- }
- }
-
- function blowNoticeCache($blowLast=false)
- {
- if ($this->is_local) {
- $cache = common_memcache();
- if (!empty($cache)) {
- $cache->delete(common_cache_key('profile:notice_ids:'.$this->profile_id));
- if ($blowLast) {
- $cache->delete(common_cache_key('profile:notice_ids:'.$this->profile_id.';last'));
- }
- }
- }
- }
-
- function blowRepliesCache($blowLast=false)
- {
- $cache = common_memcache();
- if ($cache) {
- $reply = new Reply();
- $reply->notice_id = $this->id;
- if ($reply->find()) {
- while ($reply->fetch()) {
- $cache->delete(common_cache_key('reply:stream:'.$reply->profile_id));
- if ($blowLast) {
- $cache->delete(common_cache_key('reply:stream:'.$reply->profile_id.';last'));
- }
- }
- }
- $reply->free();
- unset($reply);
- }
- }
-
- function blowPublicCache($blowLast=false)
- {
- if ($this->is_local == Notice::LOCAL_PUBLIC) {
- $cache = common_memcache();
- if ($cache) {
- $cache->delete(common_cache_key('public'));
- if ($blowLast) {
- $cache->delete(common_cache_key('public').';last');
- }
- }
- }
- }
-
- function blowFavesCache($blowLast=false)
- {
- $cache = common_memcache();
- if ($cache) {
- $fave = new Fave();
- $fave->notice_id = $this->id;
- if ($fave->find()) {
- while ($fave->fetch()) {
- $cache->delete(common_cache_key('fave:ids_by_user:'.$fave->user_id));
- $cache->delete(common_cache_key('fave:by_user_own:'.$fave->user_id));
- if ($blowLast) {
- $cache->delete(common_cache_key('fave:ids_by_user:'.$fave->user_id.';last'));
- $cache->delete(common_cache_key('fave:by_user_own:'.$fave->user_id.';last'));
- }
- }
- }
- $fave->free();
- unset($fave);
- }
- }
-
- # XXX: too many args; we need to move to named params or even a separate
- # class for notice streams
-
- static function getStream($qry, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0, $order=null, $since=null) {
-
- if (common_config('memcached', 'enabled')) {
-
- # Skip the cache if this is a since, since_id or max_id qry
- if ($since_id > 0 || $max_id > 0 || $since) {
- return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since);
- } else {
- return Notice::getCachedStream($qry, $cachekey, $offset, $limit, $order);
- }
- }
-
- return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since);
- }
-
- static function getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since) {
-
- $needAnd = false;
- $needWhere = true;
-
- if (preg_match('/\bWHERE\b/i', $qry)) {
- $needWhere = false;
- $needAnd = true;
- }
-
- if ($since_id > 0) {
-
- if ($needWhere) {
- $qry .= ' WHERE ';
- $needWhere = false;
- } else {
- $qry .= ' AND ';
- }
-
- $qry .= ' notice.id > ' . $since_id;
- }
-
- if ($max_id > 0) {
-
- if ($needWhere) {
- $qry .= ' WHERE ';
- $needWhere = false;
- } else {
- $qry .= ' AND ';
- }
-
- $qry .= ' notice.id <= ' . $max_id;
- }
-
- if ($since) {
-
- if ($needWhere) {
- $qry .= ' WHERE ';
- $needWhere = false;
- } else {
- $qry .= ' AND ';
- }
-
- $qry .= ' notice.created > \'' . date('Y-m-d H:i:s', $since) . '\'';
- }
-
- # Allow ORDER override
-
- if ($order) {
- $qry .= $order;
- } else {
- $qry .= ' ORDER BY notice.created DESC, notice.id DESC ';
- }
-
- if (common_config('db','type') == 'pgsql') {
- $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
- } else {
- $qry .= ' LIMIT ' . $offset . ', ' . $limit;
- }
-
- $notice = new Notice();
-
- $notice->query($qry);
-
- return $notice;
- }
-
- # XXX: this is pretty long and should probably be broken up into
- # some helper functions
-
- static function getCachedStream($qry, $cachekey, $offset, $limit, $order) {
-
- # If outside our cache window, just go to the DB
-
- if ($offset + $limit > NOTICE_CACHE_WINDOW) {
- return Notice::getStreamDirect($qry, $offset, $limit, null, null, $order, null);
- }
-
- # Get the cache; if we can't, just go to the DB
-
- $cache = common_memcache();
-
- if (empty($cache)) {
- return Notice::getStreamDirect($qry, $offset, $limit, null, null, $order, null);
- }
-
- # Get the notices out of the cache
-
- $notices = $cache->get(common_cache_key($cachekey));
-
- # On a cache hit, return a DB-object-like wrapper
-
- if ($notices !== false) {
- $wrapper = new ArrayWrapper(array_slice($notices, $offset, $limit));
- return $wrapper;
- }
-
- # If the cache was invalidated because of new data being
- # added, we can try and just get the new stuff. We keep an additional
- # copy of the data at the key + ';last'
-
- # No cache hit. Try to get the *last* cached version
-
- $last_notices = $cache->get(common_cache_key($cachekey) . ';last');
-
- if ($last_notices) {
-
- # Reverse-chron order, so last ID is last.
-
- $last_id = $last_notices[0]->id;
-
- # XXX: this assumes monotonically increasing IDs; a fair
- # bet with our DB.
-
- $new_notice = Notice::getStreamDirect($qry, 0, NOTICE_CACHE_WINDOW,
- $last_id, null, $order, null);
-
- if ($new_notice) {
- $new_notices = array();
- while ($new_notice->fetch()) {
- $new_notices[] = clone($new_notice);
- }
- $new_notice->free();
- $notices = array_slice(array_merge($new_notices, $last_notices),
- 0, NOTICE_CACHE_WINDOW);
-
- # Store the array in the cache for next time
-
- $result = $cache->set(common_cache_key($cachekey), $notices);
- $result = $cache->set(common_cache_key($cachekey) . ';last', $notices);
-
- # return a wrapper of the array for use now
-
- return new ArrayWrapper(array_slice($notices, $offset, $limit));
- }
- }
-
- # Otherwise, get the full cache window out of the DB
-
- $notice = Notice::getStreamDirect($qry, 0, NOTICE_CACHE_WINDOW, null, null, $order, null);
-
- # If there are no hits, just return the value
-
- if (empty($notice)) {
- return $notice;
- }
-
- # Pack results into an array
-
- $notices = array();
-
- while ($notice->fetch()) {
- $notices[] = clone($notice);
- }
-
- $notice->free();
-
- # Store the array in the cache for next time
-
- $result = $cache->set(common_cache_key($cachekey), $notices);
- $result = $cache->set(common_cache_key($cachekey) . ';last', $notices);
-
- # return a wrapper of the array for use now
-
- $wrapper = new ArrayWrapper(array_slice($notices, $offset, $limit));
-
- return $wrapper;
- }
-
function getStreamByIds($ids)
{
$cache = common_memcache();
return $notice;
}
$notice->whereAdd('id in (' . implode(', ', $ids) . ')');
- $notice->orderBy('id DESC');
$notice->find();
- return $notice;
+
+ $temp = array();
+
+ while ($notice->fetch()) {
+ $temp[$notice->id] = clone($notice);
+ }
+
+ $wrapped = array();
+
+ foreach ($ids as $id) {
+ if (array_key_exists($id, $temp)) {
+ $wrapped[] = $temp[$id];
+ }
+ }
+
+ return new ArrayWrapper($wrapped);
}
}
return $ids;
}
- function addToInboxes()
+ /**
+ * @param $groups array of Group *objects*
+ * @param $recipients array of profile *ids*
+ */
+ function whoGets($groups=null, $recipients=null)
{
- // XXX: loads constants
+ $c = self::memcache();
+
+ if (!empty($c)) {
+ $ni = $c->get(common_cache_key('notice:who_gets:'.$this->id));
+ if ($ni !== false) {
+ return $ni;
+ }
+ }
+
+ if (is_null($groups)) {
+ $groups = $this->getGroups();
+ }
- $inbox = new Notice_inbox();
+ if (is_null($recipients)) {
+ $recipients = $this->getReplies();
+ }
$users = $this->getSubscribedUsers();
$ni[$id] = NOTICE_INBOX_SOURCE_SUB;
}
- $groups = $this->saveGroups();
$profile = $this->getProfile();
foreach ($groups as $group) {
}
}
- $recipients = $this->saveReplies();
-
foreach ($recipients as $recipient) {
if (!array_key_exists($recipient, $ni)) {
}
}
- $cnt = 0;
+ if (!empty($c)) {
+ // XXX: pack this data better
+ $c->set(common_cache_key('notice:who_gets:'.$this->id), $ni);
+ }
- $qryhdr = 'INSERT INTO notice_inbox (user_id, notice_id, source, created) VALUES ';
- $qry = $qryhdr;
+ return $ni;
+ }
- foreach ($ni as $id => $source) {
- if ($cnt > 0) {
- $qry .= ', ';
- }
- $qry .= '('.$id.', '.$this->id.', '.$source.", '".$this->created. "') ";
- $cnt++;
- if (rand() % NOTICE_INBOX_SOFT_LIMIT == 0) {
- // FIXME: Causes lag in replicated servers
- // Notice_inbox::gc($id);
- }
- if ($cnt >= MAX_BOXCARS) {
- $inbox = new Notice_inbox();
- $result = $inbox->query($qry);
- if (PEAR::isError($result)) {
- common_log_db_error($inbox, $qry);
- }
- $qry = $qryhdr;
- $cnt = 0;
- }
- }
+ function addToInboxes($groups, $recipients)
+ {
+ $ni = $this->whoGets($groups, $recipients);
- if ($cnt > 0) {
- $inbox = new Notice_inbox();
- $result = $inbox->query($qry);
- if (PEAR::isError($result)) {
- common_log_db_error($inbox, $qry);
- }
- }
+ Inbox::bulkInsert($this->id, array_keys($ni));
return;
}
return $ids;
}
+ /**
+ * @return array of Group objects
+ */
function saveGroups()
{
+ // Don't save groups for repeats
+
+ if (!empty($this->repeat_of)) {
+ return array();
+ }
+
$groups = array();
/* extract all !group */
$gi->notice_id = $this->id;
$gi->created = $this->created;
- return $gi->insert();
+ $result = $gi->insert();
+
+ if (!result) {
+ common_log_db_error($gi, 'INSERT', __FILE__);
+ throw new ServerException(_('Problem saving group inbox.'));
+ }
+
+ self::blow('user_group:notice_ids:%d', $gi->group_id);
}
return true;
}
+ /**
+ * @return array of integer profile IDs
+ */
function saveReplies()
{
+ // Don't save reply data for repeats
+
+ if (!empty($this->repeat_of)) {
+ return array();
+ }
+
// Alternative reply format
$tname = false;
if (preg_match('/^T ([A-Z0-9]{1,64}) /', $this->content, $match)) {
$recipientIds = array_keys($replied);
- foreach ($recipientIds as $recipient) {
- $user = User::staticGet('id', $recipient);
- if ($user) {
+ foreach ($recipientIds as $recipientId) {
+ $user = User::staticGet('id', $recipientId);
+ if (!empty($user)) {
+ self::blow('reply:stream:%d', $reply->profile_id);
mail_notify_attn($user, $this);
}
}
return $recipientIds;
}
+ function getReplies()
+ {
+ // XXX: cache me
+
+ $ids = array();
+
+ $reply = new Reply();
+ $reply->selectAdd();
+ $reply->selectAdd('profile_id');
+ $reply->notice_id = $this->id;
+
+ if ($reply->find()) {
+ while($reply->fetch()) {
+ $ids[] = $reply->profile_id;
+ }
+ }
+
+ $reply->free();
+
+ return $ids;
+ }
+
+ /**
+ * Same calculation as saveGroups but without the saving
+ * @fixme merge the functions
+ * @return array of Group objects
+ */
+ function getGroups()
+ {
+ // Don't save groups for repeats
+
+ if (!empty($this->repeat_of)) {
+ return array();
+ }
+
+ // XXX: cache me
+
+ $groups = array();
+
+ $gi = new Group_inbox();
+
+ $gi->selectAdd();
+ $gi->selectAdd('group_id');
+
+ $gi->notice_id = $this->id;
+
+ if ($gi->find()) {
+ while ($gi->fetch()) {
+ $groups[] = clone($gi);
+ }
+ }
+
+ $gi->free();
+
+ return $groups;
+ }
+
function asAtomEntry($namespace=false, $source=false)
{
$profile = $this->getProfile();
$xs->element('id', null, $this->uri);
$xs->element('published', null, common_date_w3dtf($this->created));
- $xs->element('updated', null, common_date_w3dtf($this->modified));
+ $xs->element('updated', null, common_date_w3dtf($this->created));
if ($this->reply_to) {
$reply_notice = Notice::staticGet('id', $this->reply_to);
$idstr = $cache->get($idkey);
- if (!empty($idstr)) {
+ if ($idstr !== false) {
// Cache hit! Woohoo!
$window = explode(',', $idstr);
$ids = array_slice($window, $offset, $limit);
$laststr = $cache->get($idkey.';last');
- if (!empty($laststr)) {
+ if ($laststr !== false) {
$window = explode(',', $laststr);
$last_id = $window[0];
$new_ids = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW,
return $location;
}
+
+ function repeat($repeater_id, $source)
+ {
+ $author = Profile::staticGet('id', $this->profile_id);
+
+ $content = sprintf(_('RT @%1$s %2$s'),
+ $author->nickname,
+ $this->content);
+
+ $maxlen = common_config('site', 'textlimit');
+ if ($maxlen > 0 && mb_strlen($content) > $maxlen) {
+ // Web interface and current Twitter API clients will
+ // pull the original notice's text, but some older
+ // clients and RSS/Atom feeds will see this trimmed text.
+ //
+ // Unfortunately this is likely to lose tags or URLs
+ // at the end of long notices.
+ $content = mb_substr($content, 0, $maxlen - 4) . ' ...';
+ }
+
+ return self::saveNew($repeater_id, $content, $source,
+ array('repeat_of' => $this->id));
+ }
+
+ // These are supposed to be in chron order!
+
+ function repeatStream($limit=100)
+ {
+ $cache = common_memcache();
+
+ if (empty($cache)) {
+ $ids = $this->_repeatStreamDirect($limit);
+ } else {
+ $idstr = $cache->get(common_cache_key('notice:repeats:'.$this->id));
+ if ($idstr !== false) {
+ $ids = explode(',', $idstr);
+ } else {
+ $ids = $this->_repeatStreamDirect(100);
+ $cache->set(common_cache_key('notice:repeats:'.$this->id), implode(',', $ids));
+ }
+ if ($limit < 100) {
+ // We do a max of 100, so slice down to limit
+ $ids = array_slice($ids, 0, $limit);
+ }
+ }
+
+ return Notice::getStreamByIds($ids);
+ }
+
+ function _repeatStreamDirect($limit)
+ {
+ $notice = new Notice();
+
+ $notice->selectAdd(); // clears it
+ $notice->selectAdd('id');
+
+ $notice->repeat_of = $this->id;
+
+ $notice->orderBy('created'); // NB: asc!
+
+ if (!is_null($offset)) {
+ $notice->limit($offset, $limit);
+ }
+
+ $ids = array();
+
+ if ($notice->find()) {
+ while ($notice->fetch()) {
+ $ids[] = $notice->id;
+ }
+ }
+
+ $notice->free();
+ $notice = NULL;
+
+ return $ids;
+ }
+
+ function locationOptions($lat, $lon, $location_id, $location_ns, $profile = null)
+ {
+ $options = array();
+
+ if (!empty($location_id) && !empty($location_ns)) {
+
+ $options['location_id'] = $location_id;
+ $options['location_ns'] = $location_ns;
+
+ $location = Location::fromId($location_id, $location_ns);
+
+ if (!empty($location)) {
+ $options['lat'] = $location->lat;
+ $options['lon'] = $location->lon;
+ }
+
+ } else if (!empty($lat) && !empty($lon)) {
+
+ $options['lat'] = $lat;
+ $options['lon'] = $lon;
+
+ $location = Location::fromLatLon($lat, $lon);
+
+ if (!empty($location)) {
+ $options['location_id'] = $location->location_id;
+ $options['location_ns'] = $location->location_ns;
+ }
+ } else if (!empty($profile)) {
+
+ if (isset($profile->lat) && isset($profile->lon)) {
+ $options['lat'] = $profile->lat;
+ $options['lon'] = $profile->lon;
+ }
+
+ if (isset($profile->location_id) && isset($profile->location_ns)) {
+ $options['location_id'] = $profile->location_id;
+ $options['location_ns'] = $profile->location_ns;
+ }
+ }
+
+ return $options;
+ }
+
+ function clearReplies()
+ {
+ $replyNotice = new Notice();
+ $replyNotice->reply_to = $this->id;
+
+ //Null any notices that are replies to this notice
+
+ if ($replyNotice->find()) {
+ while ($replyNotice->fetch()) {
+ $orig = clone($replyNotice);
+ $replyNotice->reply_to = null;
+ $replyNotice->update($orig);
+ }
+ }
+
+ // Reply records
+
+ $reply = new Reply();
+ $reply->notice_id = $this->id;
+
+ if ($reply->find()) {
+ while($reply->fetch()) {
+ self::blow('reply:stream:%d', $reply->profile_id);
+ $reply->delete();
+ }
+ }
+
+ $reply->free();
+ }
+
+ function clearRepeats()
+ {
+ $repeatNotice = new Notice();
+ $repeatNotice->repeat_of = $this->id;
+
+ //Null any notices that are repeats of this notice
+
+ if ($repeatNotice->find()) {
+ while ($repeatNotice->fetch()) {
+ $orig = clone($repeatNotice);
+ $repeatNotice->repeat_of = null;
+ $repeatNotice->update($orig);
+ }
+ }
+ }
+
+ function clearFaves()
+ {
+ $fave = new Fave();
+ $fave->notice_id = $this->id;
+
+ if ($fave->find()) {
+ while ($fave->fetch()) {
+ self::blow('fave:ids_by_user_own:%d', $fave->user_id);
+ self::blow('fave:ids_by_user_own:%d;last', $fave->user_id);
+ self::blow('fave:ids_by_user:%d', $fave->user_id);
+ self::blow('fave:ids_by_user:%d;last', $fave->user_id);
+ $fave->delete();
+ }
+ }
+
+ $fave->free();
+ }
+
+ function clearTags()
+ {
+ $tag = new Notice_tag();
+ $tag->notice_id = $this->id;
+
+ if ($tag->find()) {
+ while ($tag->fetch()) {
+ self::blow('profile:notice_ids_tagged:%d:%s', $this->profile_id, common_keyize($tag->tag));
+ self::blow('profile:notice_ids_tagged:%d:%s;last', $this->profile_id, common_keyize($tag->tag));
+ self::blow('notice_tag:notice_ids:%s', common_keyize($tag->tag));
+ self::blow('notice_tag:notice_ids:%s;last', common_keyize($tag->tag));
+ $tag->delete();
+ }
+ }
+
+ $tag->free();
+ }
+
+ function clearGroupInboxes()
+ {
+ $gi = new Group_inbox();
+
+ $gi->notice_id = $this->id;
+
+ if ($gi->find()) {
+ while ($gi->fetch()) {
+ self::blow('user_group:notice_ids:%d', $gi->group_id);
+ $gi->delete();
+ }
+ }
+
+ $gi->free();
+ }
}