*/
static function saveNew($profile_id, $content, $source, $options=null) {
$defaults = array('uri' => null,
+ 'url' => null,
'reply_to' => null,
'repeat_of' => null);
}
$notice->content = $final;
- $notice->rendered = common_render_content($final, $notice);
+
+ if (!empty($rendered)) {
+ $notice->rendered = $rendered;
+ } else {
+ $notice->rendered = common_render_content($final, $notice);
+ }
+
$notice->source = $source;
$notice->uri = $uri;
+ $notice->url = $url;
// Handle repeat case
// the beginning of a new conversation.
if (empty($notice->conversation)) {
- $notice->conversation = $notice->id;
+ $conv = Conversation::create();
+ $notice->conversation = $conv->id;
$changed = true;
}
# 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();
+ if (isset($replies)) {
+ $notice->saveKnownReplies($replies);
+ } else {
+ $notice->saveReplies();
+ }
+
$notice->distribute();
return $notice;
}
- function blowOnInsert()
+ function blowOnInsert($conversation = false)
{
self::blow('profile:notice_ids:%d', $this->profile_id);
self::blow('public');
- if ($this->conversation != $this->id) {
- self::blow('notice:conversation_ids:%d', $this->conversation);
- }
+ // XXX: Before we were blowing the casche only if the notice id
+ // was not the root of the conversation. What to do now?
+
+ self::blow('notice:conversation_ids:%d', $this->conversation);
if (!empty($this->repeat_of)) {
self::blow('notice:repeats:%d', $this->repeat_of);
{
$ni = $this->whoGets($groups, $recipients);
- Inbox::bulkInsert($this->id, array_keys($ni));
+ $ids = array_keys($ni);
+
+ // We remove the author (if they're a local user),
+ // since we'll have already done this in distribute()
+
+ $i = array_search($this->profile_id, $ids);
+
+ if ($i !== false) {
+ unset($ids[$i]);
+ }
+
+ // Bulk insert
+
+ Inbox::bulkInsert($this->id, $ids);
return;
}
return true;
}
+ function saveKnownReplies($uris)
+ {
+ foreach ($uris as $uri) {
+
+ $user = User::staticGet('uri', $uri);
+
+ if (!empty($user)) {
+
+ $reply = new Reply();
+
+ $reply->notice_id = $this->id;
+ $reply->profile_id = $user->id;
+
+ $id = $reply->insert();
+ }
+ }
+
+ return;
+ }
+
/**
* @return array of integer profile IDs
*/
+
function saveReplies()
{
// Don't save reply data for repeats
return array();
}
- // Alternative reply format
- $tname = false;
- if (preg_match('/^T ([A-Z0-9]{1,64}) /', $this->content, $match)) {
- $tname = $match[1];
- }
- // extract all @messages
- $cnt = preg_match_all('/(?:^|\s)@([a-z0-9]{1,64})/', $this->content, $match);
-
- $names = array();
-
- if ($cnt || $tname) {
- // XXX: is there another way to make an array copy?
- $names = ($tname) ? array_unique(array_merge(array(strtolower($tname)), $match[1])) : array_unique($match[1]);
- }
-
$sender = Profile::staticGet($this->profile_id);
+ $mentions = common_find_mentions($this->profile_id, $this->content);
+
$replied = array();
// store replied only for first @ (what user/notice what the reply directed,
// we assume first @ is it)
- for ($i=0; $i<count($names); $i++) {
- $nickname = $names[$i];
- $recipient = common_relative_profile($sender, $nickname, $this->created);
- if (empty($recipient)) {
- continue;
- }
- // Don't save replies from blocked profile to local user
- $recipient_user = User::staticGet('id', $recipient->id);
- if (!empty($recipient_user) && $recipient_user->hasBlocked($sender)) {
- continue;
- }
- $reply = new Reply();
- $reply->notice_id = $this->id;
- $reply->profile_id = $recipient->id;
- $id = $reply->insert();
- if (!$id) {
- $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
- common_log(LOG_ERR, 'DB error inserting reply: ' . $last_error->message);
- common_server_error(sprintf(_('DB error inserting reply: %s'), $last_error->message));
- return array();
- } else {
- $replied[$recipient->id] = 1;
- }
- }
+ foreach ($mentions as $mention) {
- // Hash format replies, too
- $cnt = preg_match_all('/(?:^|\s)@#([a-z0-9]{1,64})/', $this->content, $match);
- if ($cnt) {
- foreach ($match[1] as $tag) {
- $tagged = Profile_tag::getTagged($sender->id, $tag);
- foreach ($tagged as $t) {
- if (!$replied[$t->id]) {
- // Don't save replies from blocked profile to local user
- $t_user = User::staticGet('id', $t->id);
- if ($t_user && $t_user->hasBlocked($sender)) {
- continue;
- }
- $reply = new Reply();
- $reply->notice_id = $this->id;
- $reply->profile_id = $t->id;
- $id = $reply->insert();
- if (!$id) {
- common_log_db_error($reply, 'INSERT', __FILE__);
- return array();
- } else {
- $replied[$recipient->id] = 1;
- }
- }
+ foreach ($mention['mentioned'] as $mentioned) {
+
+ // skip if they're already covered
+
+ if (!empty($replied[$mentioned->id])) {
+ continue;
+ }
+
+ // Don't save replies from blocked profile to local user
+
+ $mentioned_user = User::staticGet('id', $mentioned->id);
+ if (!empty($mentioned_user) && $mentioned_user->hasBlocked($sender)) {
+ continue;
+ }
+
+ $reply = new Reply();
+
+ $reply->notice_id = $this->id;
+ $reply->profile_id = $mentioned->id;
+
+ $id = $reply->insert();
+
+ if (!$id) {
+ common_log_db_error($reply, 'INSERT', __FILE__);
+ throw new ServerException("Couldn't save reply for {$this->id}, {$mentioned->id}");
+ } else {
+ $replied[$mentioned->id] = 1;
}
}
}
if ($namespace) {
$attrs = array('xmlns' => 'http://www.w3.org/2005/Atom',
- 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0');
+ 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0',
+ 'xmlns:georss' => 'http://www.georss.org/georss',
+ 'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/',
+ 'xmlns:ostatus' => 'http://ostatus.org/schema/1.0');
} else {
$attrs = array();
}
$xs->element('icon', null, $profile->avatarUrl(AVATAR_PROFILE_SIZE));
}
- $xs->elementStart('author');
- $xs->element('name', null, $profile->nickname);
- $xs->element('uri', null, $profile->profileurl);
- $xs->elementEnd('author');
-
if ($source) {
$xs->elementEnd('source');
}
$xs->element('title', null, $this->content);
$xs->element('summary', null, $this->content);
+ $xs->raw($profile->asAtomAuthor());
+ $xs->raw($profile->asActivityActor());
+
$xs->element('link', array('rel' => 'alternate',
+ 'type' => 'text/html',
'href' => $this->bestUrl()));
$xs->element('id', null, $this->uri);
}
}
+ if (!empty($this->conversation)) {
+
+ $conv = Conversation::staticGet('id', $this->conversation);
+
+ if (!empty($conv)) {
+ $xs->element(
+ 'link', array(
+ 'rel' => 'ostatus:conversation',
+ 'href' => $conv->uri
+ )
+ );
+ }
+ }
+
+ $reply_ids = $this->getReplies();
+
+ foreach ($reply_ids as $id) {
+ $profile = Profile::staticGet('id', $id);
+ if (!empty($profile)) {
+ $xs->element(
+ 'link', array(
+ 'rel' => 'ostatus:attention',
+ 'href' => $profile->getUri()
+ )
+ );
+ }
+ }
+
+ if (!empty($this->repeat_of)) {
+ $repeat = Notice::staticGet('id', $this->repeat_of);
+ if (!empty($repeat)) {
+ $xs->element(
+ 'ostatus:forward',
+ array('ref' => $repeat->uri, 'href' => $repeat->bestUrl())
+ );
+ }
+ }
+
$xs->element('content', array('type' => 'html'), $this->rendered);
$tag = new Notice_tag();
}
if (!empty($this->lat) && !empty($this->lon)) {
- $xs->elementStart('geo', array('xmlns:georss' => 'http://www.georss.org/georss'));
$xs->element('georss:point', null, $this->lat . ' ' . $this->lon);
- $xs->elementEnd('geo');
}
$xs->elementEnd('entry');
return $xs->getString();
}
+ /**
+ * Returns an XML string fragment with a reference to a notice as an
+ * Activity Streams noun object with the given element type.
+ *
+ * Assumes that 'activity' namespace has been previously defined.
+ *
+ * @param string $element one of 'subject', 'object', 'target'
+ * @return string
+ */
+ function asActivityNoun($element)
+ {
+ $xs = new XMLStringer(true);
+
+ $xs->elementStart('activity:' . $element);
+ $xs->element('activity:object-type',
+ null,
+ 'http://activitystrea.ms/schema/1.0/note');
+ $xs->element('id',
+ null,
+ $this->uri);
+ $xs->element('content',
+ array('type' => 'text/html'),
+ $this->rendered);
+ $xs->element('link',
+ array('type' => 'text/html',
+ 'rel' => 'alternate',
+ 'href' => $this->bestUrl()));
+ $xs->elementEnd('activity:' . $element);
+
+ return $xs->getString();
+ }
+
function bestUrl()
{
if (!empty($this->url)) {
function distribute()
{
+ // We always insert for the author so they don't
+ // have to wait
+
+ $user = User::staticGet('id', $this->profile_id);
+ if (!empty($user)) {
+ Inbox::insertNotice($user->id, $this->id);
+ }
+
if (common_config('queue', 'inboxes')) {
// If there's a failure, we want to _force_
// distribution at this point.