X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=classes%2FNotice.php;h=9578d87b2b149925e8fefffc17c43abd3b3c75b3;hb=628a9371082531908a023618ad4216a2f254f481;hp=9960d3d0aaa834da7db7f8aa49497483c5505c87;hpb=4d56bc6a0adba63c5546d5d18a70fa2c989839be;p=quix0rs-gnu-social.git diff --git a/classes/Notice.php b/classes/Notice.php index 9960d3d0aa..9578d87b2b 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -32,7 +32,8 @@ define('NOTICE_CACHE_WINDOW', 61); define('NOTICE_LOCAL_PUBLIC', 1); define('NOTICE_REMOTE_OMB', 0); define('NOTICE_LOCAL_NONPUBLIC', -1); -define('NOTICE_GATEWAY', -2); + +define('MAX_BOXCARS', 128); class Notice extends Memcached_DataObject { @@ -61,6 +62,8 @@ class Notice extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE + const GATEWAY = -2; + function getProfile() { return Profile::staticGet('id', $this->profile_id); @@ -72,7 +75,21 @@ class Notice extends Memcached_DataObject $this->blowFavesCache(true); $this->blowSubsCache(true); + // For auditing purposes, save a record that the notice + // was deleted. + + $deleted = new Deleted_notice(); + + $deleted->id = $this->id; + $deleted->profile_id = $this->profile_id; + $deleted->uri = $this->uri; + $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', @@ -95,13 +112,20 @@ class Notice extends Memcached_DataObject function saveTags() { /* extract all #hastags */ - $count = preg_match_all('/(?:^|\s)#([A-Za-z0-9_\-\.]{1,64})/', strtolower($this->content), $match); + $count = preg_match_all('/(?:^|\s)#([\pL\pN_\-\.]{1,64})/', strtolower($this->content), $match); if (!$count) { return true; } + //turn each into their canonical tag + //this is needed to remove dupes before saving e.g. #hash.tag = #hashtag + $hashtags = array(); + for($i=0; $isaveTag($hashtag); } @@ -110,8 +134,6 @@ class Notice extends Memcached_DataObject function saveTag($hashtag) { - $hashtag = common_canonical_tag($hashtag); - $tag = new Notice_tag(); $tag->notice_id = $this->id; $tag->tag = $hashtag; @@ -174,29 +196,30 @@ class Notice extends Memcached_DataObject $notice->is_local = $is_local; } - $notice->query('BEGIN'); - - $notice->reply_to = $reply_to; if (!empty($created)) { $notice->created = $created; } else { $notice->created = common_sql_now(); } + $notice->content = $final; $notice->rendered = common_render_content($final, $notice); $notice->source = $source; $notice->uri = $uri; - if (!empty($reply_to)) { - $reply_notice = Notice::staticGet('id', $reply_to); - if (!empty($reply_notice)) { - $notice->reply_to = $reply_to; - $notice->conversation = $reply_notice->conversation; - } + $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final); + + if (!empty($notice->reply_to)) { + $reply = Notice::staticGet('id', $notice->reply_to); + $notice->conversation = $reply->conversation; } if (Event::handle('StartNoticeSave', array(&$notice))) { + // XXX: some of these functions write to the DB + + $notice->query('BEGIN'); + $id = $notice->insert(); if (!$id) { @@ -204,27 +227,47 @@ class Notice extends Memcached_DataObject return _('Problem saving notice.'); } - # Update the URI after the notice is in the database - if (!$uri) { - $orig = clone($notice); + // Update ID-dependent columns: URI, conversation + + $orig = clone($notice); + + $changed = false; + + if (empty($uri)) { $notice->uri = common_notice_uri($notice); + $changed = true; + } + + // If it's not part of a conversation, it's + // the beginning of a new conversation. + if (empty($notice->conversation)) { + $notice->conversation = $notice->id; + $changed = true; + } + + if ($changed) { if (!$notice->update($orig)) { common_log_db_error($notice, 'UPDATE', __FILE__); return _('Problem saving notice.'); } } - # XXX: do we need to change this for remote users? + // XXX: do we need to change this for remote users? $notice->saveReplies(); $notice->saveTags(); $notice->addToInboxes(); - $notice->saveGroups(); + $notice->saveUrls(); + + // FIXME: why do we have to re-render the content? + // Remove this if it's not necessary. + $orig2 = clone($notice); - $notice->rendered = common_render_content($final, $notice); + + $notice->rendered = common_render_content($final, $notice); if (!$notice->update($orig2)) { common_log_db_error($notice, 'UPDATE', __FILE__); return _('Problem saving notice.'); @@ -281,9 +324,9 @@ class Notice extends Memcached_DataObject $notice->profile_id = $profile_id; $notice->content = $content; if (common_config('db','type') == 'pgsql') - $notice->whereAdd('extract(epoch from now() - created) < ' . common_config('site', 'dupelimit')); + $notice->whereAdd('extract(epoch from now() - created) < ' . common_config('site', 'dupelimit')); else - $notice->whereAdd('now() - created < ' . common_config('site', 'dupelimit')); + $notice->whereAdd('now() - created < ' . common_config('site', 'dupelimit')); $cnt = $notice->count(); return ($cnt == 0); @@ -354,13 +397,15 @@ class Notice extends Memcached_DataObject $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 = 'notice:conversation_ids:'.$this->conversation; + $ck = common_cache_key('notice:conversation_ids:'.$this->conversation); $cache->delete($ck); if ($blowLast) { $cache->delete($ck.';last'); @@ -793,7 +838,7 @@ class Notice extends Memcached_DataObject $notice->selectAdd(); // clears it $notice->selectAdd('id'); - $notice->whereAdd('conversation = '.$id); + $notice->conversation = $id; $notice->orderBy('id DESC'); @@ -832,29 +877,99 @@ class Notice extends Memcached_DataObject $enabled = common_config('inboxes', 'enabled'); if ($enabled === true || $enabled === 'transitional') { + + // XXX: loads constants + $inbox = new Notice_inbox(); - $UT = common_config('db','type')=='pgsql'?'"user"':'user'; - $qry = 'INSERT INTO notice_inbox (user_id, notice_id, created) ' . - "SELECT $UT.id, " . $this->id . ", '" . $this->created . "' " . - "FROM $UT JOIN subscription ON $UT.id = subscription.subscriber " . - 'WHERE subscription.subscribed = ' . $this->profile_id . ' ' . - 'AND NOT EXISTS (SELECT user_id, notice_id ' . - 'FROM notice_inbox ' . - "WHERE user_id = $UT.id " . - 'AND notice_id = ' . $this->id . ' )'; - if ($enabled === 'transitional') { - $qry .= " AND $UT.inboxed = 1"; + + $users = $this->getSubscribedUsers(); + + // FIXME: kind of ignoring 'transitional'... + // we'll probably stop supporting inboxless mode + // in 0.9.x + + $ni = array(); + + foreach ($users as $id) { + $ni[$id] = NOTICE_INBOX_SOURCE_SUB; + } + + $groups = $this->saveGroups(); + + foreach ($groups as $group) { + $users = $group->getUserMembers(); + foreach ($users as $id) { + if (!array_key_exists($id, $ni)) { + $ni[$id] = NOTICE_INBOX_SOURCE_GROUP; + } + } + } + + $cnt = 0; + + $qryhdr = 'INSERT INTO notice_inbox (user_id, notice_id, source, created) VALUES '; + $qry = $qryhdr; + + foreach ($ni as $id => $source) { + if ($cnt > 0) { + $qry .= ', '; + } + $qry .= '('.$id.', '.$this->id.', '.$source.", '".$this->created. "') "; + $cnt++; + if (rand() % NOTICE_INBOX_SOFT_LIMIT == 0) { + Notice_inbox::gc($id); + } + if ($cnt >= MAX_BOXCARS) { + $inbox = new Notice_inbox(); + $inbox->query($qry); + $qry = $qryhdr; + $cnt = 0; + } + } + + if ($cnt > 0) { + $inbox = new Notice_inbox(); + $inbox->query($qry); } - $inbox->query($qry); } + return; } + function getSubscribedUsers() + { + $user = new User(); + + if(common_config('db','quote_identifiers')) + $user_table = '"user"'; + else $user_table = 'user'; + + $qry = + 'SELECT id ' . + 'FROM '. $user_table .' JOIN subscription '. + 'ON '. $user_table .'.id = subscription.subscriber ' . + 'WHERE subscription.subscribed = %d '; + + $user->query(sprintf($qry, $this->profile_id)); + + $ids = array(); + + while ($user->fetch()) { + $ids[] = $user->id; + } + + $user->free(); + + return $ids; + } + function saveGroups() { + $groups = array(); + $enabled = common_config('inboxes', 'enabled'); if ($enabled !== true && $enabled !== 'transitional') { - return; + return $groups; } /* extract all !group */ @@ -862,7 +977,7 @@ class Notice extends Memcached_DataObject strtolower($this->content), $match); if (!$count) { - return true; + return $groups; } $profile = $this->getProfile(); @@ -888,41 +1003,36 @@ class Notice extends Memcached_DataObject if ($profile->isMember($group)) { - $gi = new Group_inbox(); - - $gi->group_id = $group->id; - $gi->notice_id = $this->id; - $gi->created = common_sql_now(); - - $result = $gi->insert(); + $result = $this->addToGroupInbox($group); if (!$result) { common_log_db_error($gi, 'INSERT', __FILE__); } - // FIXME: do this in an offline daemon - - $this->addToGroupInboxes($group); + $groups[] = clone($group); } } + + return $groups; } - function addToGroupInboxes($group) + function addToGroupInbox($group) { - $inbox = new Notice_inbox(); - $UT = common_config('db','type')=='pgsql'?'"user"':'user'; - $qry = 'INSERT INTO notice_inbox (user_id, notice_id, created, source) ' . - "SELECT $UT.id, " . $this->id . ", '" . $this->created . "', " . NOTICE_INBOX_SOURCE_GROUP . " " . - "FROM $UT JOIN group_member ON $UT.id = group_member.profile_id " . - 'WHERE group_member.group_id = ' . $group->id . ' ' . - 'AND NOT EXISTS (SELECT user_id, notice_id ' . - 'FROM notice_inbox ' . - "WHERE user_id = $UT.id " . - 'AND notice_id = ' . $this->id . ' )'; - if ($enabled === 'transitional') { - $qry .= " AND $UT.inboxed = 1"; - } - $result = $inbox->query($qry); + $gi = Group_inbox::pkeyGet(array('group_id' => $group->id, + 'notice_id' => $this->id)); + + if (empty($gi)) { + + $gi = new Group_inbox(); + + $gi->group_id = $group->id; + $gi->notice_id = $this->id; + $gi->created = $this->created; + + return $gi->insert(); + } + + return true; } function saveReplies() @@ -955,16 +1065,6 @@ class Notice extends Memcached_DataObject if (!$recipient) { continue; } - if ($i == 0 && ($recipient->id != $sender->id) && !$this->reply_to) { // Don't save reply to self - $reply_for = $recipient; - $recipient_notice = $reply_for->getCurrentNotice(); - if ($recipient_notice) { - $orig = clone($this); - $this->reply_to = $recipient_notice->id; - $this->conversation = $recipient_notice->conversation; - $this->update($orig); - } - } // Don't save replies from blocked profile to local user $recipient_user = User::staticGet('id', $recipient->id); if ($recipient_user && $recipient_user->hasBlocked($sender)) { @@ -1011,14 +1111,6 @@ class Notice extends Memcached_DataObject } } - // If it's not a reply, make it the root of a new conversation - - if (empty($this->conversation)) { - $orig = clone($this); - $this->conversation = $this->id; - $this->update($orig); - } - foreach (array_keys($replied) as $recipient) { $user = User::staticGet('id', $recipient); if ($user) { @@ -1104,6 +1196,20 @@ class Notice extends Memcached_DataObject } $tag->free(); + # Enclosures + $attachments = $this->attachments(); + if($attachments){ + foreach($attachments as $attachment){ + if ($attachment->isEnclosure()) { + $attributes = array('rel'=>'enclosure','href'=>$attachment->url,'type'=>$attachment->mimetype,'length'=>$attachment->size); + if($attachment->title){ + $attributes['title']=$attachment->title; + } + $xs->element('link', $attributes, null); + } + } + } + $xs->elementEnd('entry'); return $xs->getString(); @@ -1127,6 +1233,7 @@ class Notice extends Memcached_DataObject if (empty($cache) || $since_id != 0 || $max_id != 0 || (!is_null($since) && $since > 0) || + is_null($limit) || ($offset + $limit) > NOTICE_CACHE_WINDOW) { return call_user_func_array($fn, array_merge($args, array($offset, $limit, $since_id, $max_id, $since))); @@ -1149,7 +1256,7 @@ class Notice extends Memcached_DataObject $window = explode(',', $laststr); $last_id = $window[0]; $new_ids = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW, - $last_id, 0, null, $tag))); + $last_id, 0, null))); $new_window = array_merge($new_ids, $window); @@ -1164,7 +1271,7 @@ class Notice extends Memcached_DataObject } $window = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW, - 0, 0, null, $tag))); + 0, 0, null))); $windowstr = implode(',', $window); @@ -1175,4 +1282,76 @@ class Notice extends Memcached_DataObject return $ids; } + + /** + * Determine which notice, if any, a new notice is in reply to. + * + * For conversation tracking, we try to see where this notice fits + * in the tree. Rough algorithm is: + * + * if (reply_to is set and valid) { + * return reply_to; + * } else if ((source not API or Web) and (content starts with "T NAME" or "@name ")) { + * return ID of last notice by initial @name in content; + * } + * + * Note that all @nickname instances will still be used to save "reply" records, + * so the notice shows up in the mentioned users' "replies" tab. + * + * @param integer $reply_to ID passed in by Web or API + * @param integer $profile_id ID of author + * @param string $source Source tag, like 'web' or 'gwibber' + * @param string $content Final notice content + * + * @return integer ID of replied-to notice, or null for not a reply. + */ + + static function getReplyTo($reply_to, $profile_id, $source, $content) + { + static $lb = array('xmpp', 'mail', 'sms', 'omb'); + + // If $reply_to is specified, we check that it exists, and then + // return it if it does + + if (!empty($reply_to)) { + $reply_notice = Notice::staticGet('id', $reply_to); + if (!empty($reply_notice)) { + return $reply_to; + } + } + + // If it's not a "low bandwidth" source (one where you can't set + // a reply_to argument), we return. This is mostly web and API + // clients. + + if (!in_array($source, $lb)) { + return null; + } + + // Is there an initial @ or T? + + if (preg_match('/^T ([A-Z0-9]{1,64}) /', $content, $match) || + preg_match('/^@([a-z0-9]{1,64})\s+/', $content, $match)) { + $nickname = common_canonical_nickname($match[1]); + } else { + return null; + } + + // Figure out who that is. + + $sender = Profile::staticGet('id', $profile_id); + $recipient = common_relative_profile($sender, $nickname, common_sql_now()); + + if (empty($recipient)) { + return null; + } + + // Get their last notice + + $last = $recipient->getCurrentNotice(); + + if (!empty($last)) { + return $last->id; + } + } }