diff options
Diffstat (limited to 'classes/Notice.php')
-rw-r--r-- | classes/Notice.php | 327 |
1 files changed, 277 insertions, 50 deletions
diff --git a/classes/Notice.php b/classes/Notice.php index bca4b22c4..8a018068a 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1,7 +1,7 @@ <?php /* * Laconica - a distributed open-source microblogging tool - * Copyright (C) 2008, Controlez-Vous, Inc. + * Copyright (C) 2008, 2009, Control Yourself, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -29,6 +29,13 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; 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 { ###START_AUTOCODE @@ -46,6 +53,7 @@ class Notice extends Memcached_DataObject public $reply_to; // int(4) public $is_local; // tinyint(1) public $source; // varchar(32) + public $conversation; // int(4) /* Static get */ function staticGet($k,$v=NULL) { @@ -119,7 +127,8 @@ class Notice extends Memcached_DataObject } } - static function saveNew($profile_id, $content, $source=null, $is_local=1, $reply_to=null, $uri=null) { + static function saveNew($profile_id, $content, $source=null, + $is_local=1, $reply_to=null, $uri=null, $created=null) { $profile = Profile::staticGet($profile_id); @@ -170,12 +179,24 @@ class Notice extends Memcached_DataObject $notice->query('BEGIN'); $notice->reply_to = $reply_to; - $notice->created = common_sql_now(); + 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; + } + } + if (Event::handle('StartNoticeSave', array(&$notice))) { $id = $notice->insert(); @@ -202,7 +223,14 @@ class Notice extends Memcached_DataObject $notice->saveTags(); $notice->addToInboxes(); - $notice->saveGroups(); + + $notice->saveUrls(); + $orig2 = clone($notice); + $notice->rendered = common_render_content($final, $notice); + if (!$notice->update($orig2)) { + common_log_db_error($notice, 'UPDATE', __FILE__); + return _('Problem saving notice.'); + } $notice->query('COMMIT'); @@ -217,6 +245,22 @@ class Notice extends Memcached_DataObject return $notice; } + /** save all urls in the notice to the db + * + * follow redirects and save all available file information + * (mimetype, date, size, oembed, etc.) + * + * @return void + */ + function saveUrls() { + common_replace_urls_callback($this->content, array($this, 'saveUrl'), $this->id); + } + + function saveUrl($data) { + list($url, $notice_id) = $data; + File::processNew($url, $notice_id); + } + static function checkDupes($profile_id, $content) { $profile = Profile::staticGet($profile_id); if (!$profile) { @@ -265,6 +309,44 @@ class Notice extends Memcached_DataObject return true; } + function getUploadedAttachment() { + $post = clone $this; + $query = 'select file.url as up, file.id as i from file join file_to_post on file.id = file_id where post_id=' . $post->escape($post->id) . ' and url like "%/notice/%/file"'; + $post->query($query); + $post->fetch(); + if (empty($post->up) || empty($post->i)) { + $ret = false; + } else { + $ret = array($post->up, $post->i); + } + $post->free(); + return $ret; + } + + function hasAttachments() { + $post = clone $this; + $query = "select count(file_id) as n_attachments from file join file_to_post on (file_id = file.id) join notice on (post_id = notice.id) where post_id = " . $post->escape($post->id); + $post->query($query); + $post->fetch(); + $n_attachments = intval($post->n_attachments); + $post->free(); + return $n_attachments; + } + + function attachments() { + // XXX: cache this + $att = array(); + $f2p = new File_to_post; + $f2p->post_id = $this->id; + if ($f2p->find()) { + while ($f2p->fetch()) { + $f = File::staticGet($f2p->file_id); + $att[] = clone($f); + } + } + return $att; + } + function blowCaches($blowLast=false) { $this->blowSubsCache($blowLast); @@ -273,6 +355,19 @@ class Notice extends Memcached_DataObject $this->blowPublicCache($blowLast); $this->blowTagCache($blowLast); $this->blowGroupCache($blowLast); + $this->blowConversationCache($blowLast); + } + + 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) @@ -313,6 +408,12 @@ class Notice extends Memcached_DataObject 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(); @@ -334,8 +435,10 @@ class Notice extends Memcached_DataObject 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(); @@ -397,8 +500,10 @@ class Notice extends Memcached_DataObject 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')); } } } @@ -601,7 +706,10 @@ class Notice extends Memcached_DataObject if (!empty($cache)) { $notices = array(); foreach ($ids as $id) { - $notices[] = Notice::staticGet('id', $id); + $n = Notice::staticGet('id', $id); + if (!empty($n)) { + $notices[] = $n; + } } return new ArrayWrapper($notices); } else { @@ -670,34 +778,148 @@ class Notice extends Memcached_DataObject return $ids; } + function conversationStream($id, $offset=0, $limit=20, $since_id=0, $max_id=0, $since=null) + { + $ids = Notice::stream(array('Notice', '_conversationStreamDirect'), + array($id), + 'notice:conversation_ids:'.$id, + $offset, $limit, $since_id, $max_id, $since); + + return Notice::getStreamByIds($ids); + } + + function _conversationStreamDirect($id, $offset=0, $limit=20, $since_id=0, $max_id=0, $since=null) + { + $notice = new Notice(); + + $notice->selectAdd(); // clears it + $notice->selectAdd('id'); + + $notice->conversation = $id; + + $notice->orderBy('id DESC'); + + if (!is_null($offset)) { + $notice->limit($offset, $limit); + } + + if ($since_id != 0) { + $notice->whereAdd('id > ' . $since_id); + } + + if ($max_id != 0) { + $notice->whereAdd('id <= ' . $max_id); + } + + if (!is_null($since)) { + $notice->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\''); + } + + $ids = array(); + + if ($notice->find()) { + while ($notice->fetch()) { + $ids[] = $notice->id; + } + } + + $notice->free(); + $notice = NULL; + + return $ids; + } + function addToInboxes() { $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"; - } - $inbox->query($qry); + + $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 ($cnt >= MAX_BOXCARS) { + $inbox = new Notice_inbox(); + $inbox->query($qry); + $qry = $qryhdr; + $cnt = 0; + } + } + + if ($cnt > 0) { + $inbox = new Notice_inbox(); + $inbox->query($qry); + } } + return; } + function getSubscribedUsers() + { + $user = new User(); + + $qry = + 'SELECT id ' . + 'FROM user JOIN subscription '. + 'ON user.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 */ @@ -705,7 +927,7 @@ class Notice extends Memcached_DataObject strtolower($this->content), $match); if (!$count) { - return true; + return $groups; } $profile = $this->getProfile(); @@ -714,16 +936,16 @@ class Notice extends Memcached_DataObject foreach (array_unique($match[1]) as $nickname) { /* XXX: remote groups. */ - $group = User_group::staticGet('nickname', $nickname); + $group = User_group::getForNickname($nickname); - if (!$group) { + if (empty($group)) { continue; } // we automatically add a tag for every group name, too $tag = Notice_tag::pkeyGet(array('tag' => common_canonical_tag($nickname), - 'notice_id' => $this->id)); + 'notice_id' => $this->id)); if (is_null($tag)) { $this->saveTag($nickname); @@ -731,41 +953,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 . "', 2 " . - "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() @@ -804,6 +1021,7 @@ class Notice extends Memcached_DataObject if ($recipient_notice) { $orig = clone($this); $this->reply_to = $recipient_notice->id; + $this->conversation = $recipient_notice->conversation; $this->update($orig); } } @@ -853,6 +1071,14 @@ 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) { @@ -961,6 +1187,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))); @@ -983,7 +1210,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))); + $last_id, 0, null, $tag))); $new_window = array_merge($new_ids, $window); @@ -998,7 +1225,7 @@ class Notice extends Memcached_DataObject } $window = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW, - 0, 0, null))); + 0, 0, null, $tag))); $windowstr = implode(',', $window); |