diff options
Diffstat (limited to 'classes')
-rw-r--r-- | classes/Fave.php | 50 | ||||
-rw-r--r-- | classes/File.php | 6 | ||||
-rw-r--r-- | classes/Inbox.php | 8 | ||||
-rw-r--r-- | classes/Local_group.php | 46 | ||||
-rw-r--r-- | classes/Memcached_DataObject.php | 6 | ||||
-rw-r--r-- | classes/Notice.php | 349 | ||||
-rw-r--r-- | classes/Notice_inbox.php | 4 | ||||
-rw-r--r-- | classes/Notice_tag.php | 6 | ||||
-rw-r--r-- | classes/Profile.php | 91 | ||||
-rw-r--r-- | classes/Reply.php | 10 | ||||
-rw-r--r-- | classes/Subscription.php | 176 | ||||
-rw-r--r-- | classes/User.php | 62 | ||||
-rw-r--r-- | classes/User_group.php | 151 | ||||
-rw-r--r-- | classes/statusnet.ini | 34 |
14 files changed, 712 insertions, 287 deletions
diff --git a/classes/Fave.php b/classes/Fave.php index 8113c8e16..a04f15e9c 100644 --- a/classes/Fave.php +++ b/classes/Fave.php @@ -21,17 +21,47 @@ class Fave extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - static function addNew($user, $notice) { - $fave = new Fave(); - $fave->user_id = $user->id; - $fave->notice_id = $notice->id; - if (!$fave->insert()) { - common_log_db_error($fave, 'INSERT', __FILE__); - return false; + static function addNew($profile, $notice) { + + $fave = null; + + if (Event::handle('StartFavorNotice', array($profile, $notice, &$fave))) { + + $fave = new Fave(); + + $fave->user_id = $profile->id; + $fave->notice_id = $notice->id; + + if (!$fave->insert()) { + common_log_db_error($fave, 'INSERT', __FILE__); + return false; + } + + Event::handle('EndFavorNotice', array($profile, $notice)); } + return $fave; } + function delete() + { + $profile = Profile::staticGet('id', $this->user_id); + $notice = Notice::staticGet('id', $this->notice_id); + + $result = null; + + if (Event::handle('StartDisfavorNotice', array($profile, $notice, &$result))) { + + $result = parent::delete(); + + if ($result) { + Event::handle('EndDisfavorNotice', array($profile, $notice)); + } + } + + return $result; + } + function pkeyGet($kv) { return Memcached_DataObject::pkeyGet('Fave', $kv); @@ -47,7 +77,7 @@ class Fave extends Memcached_DataObject return $ids; } - function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id, $since) + function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id) { $fav = new Fave(); $qry = null; @@ -70,10 +100,6 @@ class Fave extends Memcached_DataObject $qry .= 'AND notice_id <= ' . $max_id . ' '; } - if (!is_null($since)) { - $qry .= 'AND modified > \'' . date('Y-m-d H:i:s', $since) . '\' '; - } - // NOTE: we sort by fave time, not by notice time! $qry .= 'ORDER BY modified DESC '; diff --git a/classes/File.php b/classes/File.php index 189e04ce0..79a7d6681 100644 --- a/classes/File.php +++ b/classes/File.php @@ -169,7 +169,11 @@ class File extends Memcached_DataObject { require_once 'MIME/Type/Extension.php'; $mte = new MIME_Type_Extension(); - $ext = $mte->getExtension($mimetype); + try { + $ext = $mte->getExtension($mimetype); + } catch ( Exception $e) { + $ext = strtolower(preg_replace('/\W/', '', $mimetype)); + } $nickname = $profile->nickname; $datestamp = strftime('%Y%m%dT%H%M%S', time()); $random = strtolower(common_confirmation_code(32)); diff --git a/classes/Inbox.php b/classes/Inbox.php index be62611a1..014ba3d82 100644 --- a/classes/Inbox.php +++ b/classes/Inbox.php @@ -137,7 +137,7 @@ class Inbox extends Memcached_DataObject } } - function stream($user_id, $offset, $limit, $since_id, $max_id, $since, $own=false) + function stream($user_id, $offset, $limit, $since_id, $max_id, $own=false) { $inbox = Inbox::staticGet('user_id', $user_id); @@ -195,15 +195,15 @@ class Inbox extends Memcached_DataObject * @param int $limit * @param mixed $since_id return only notices after but not including this id * @param mixed $max_id return only notices up to and including this id - * @param mixed $since obsolete/ignored * @param mixed $own ignored? * @return array of Notice objects * * @todo consider repacking the inbox when this happens? + * @fixme reimplement $own if we need it? */ - function streamNotices($user_id, $offset, $limit, $since_id, $max_id, $since, $own=false) + function streamNotices($user_id, $offset, $limit, $since_id, $max_id, $own=false) { - $ids = self::stream($user_id, $offset, self::MAX_NOTICES, $since_id, $max_id, $since, $own); + $ids = self::stream($user_id, $offset, self::MAX_NOTICES, $since_id, $max_id, $own); // Do a bulk lookup for the first $limit items // Fast path when nothing's deleted. diff --git a/classes/Local_group.php b/classes/Local_group.php new file mode 100644 index 000000000..42312ec63 --- /dev/null +++ b/classes/Local_group.php @@ -0,0 +1,46 @@ +<?php +/** + * Table Definition for local_group + */ + +class Local_group extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'local_group'; // table name + public $group_id; // int(4) primary_key not_null + public $nickname; // varchar(64) unique_key + public $created; // datetime not_null default_0000-00-00%2000%3A00%3A00 + public $modified; // timestamp not_null default_CURRENT_TIMESTAMP + + /* Static get */ + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Local_group',$k,$v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + function sequenceKey() + { + return array(false, false, false); + } + + function setNickname($nickname) + { + $this->decache(); + $qry = 'UPDATE local_group set nickname = "'.$nickname.'" where group_id = ' . $this->group_id; + + $result = $this->query($qry); + + if ($result) { + $this->nickname = $nickname; + $this->fixupTimestamps(); + $this->encache(); + } else { + common_log_db_error($local, 'UPDATE', __FILE__); + throw new ServerException(_('Could not update local group.')); + } + + return $result; + } +} diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 40576dc71..bc4c3a000 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -501,7 +501,11 @@ class Memcached_DataObject extends Safe_DataObject function raiseError($message, $type = null, $behaviour = null) { - throw new ServerException("DB_DataObject error [$type]: $message"); + $id = get_class($this); + if ($this->id) { + $id .= ':' . $this->id; + } + throw new ServerException("[$id] DB_DataObject error [$type]: $message"); } static function cacheGet($keyPart) diff --git a/classes/Notice.php b/classes/Notice.php index b0edb6de6..63dc96897 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -121,6 +121,9 @@ class Notice extends Memcached_DataObject $result = parent::delete(); } + /** + * Extract #hashtags from this notice's content and save them to the database. + */ function saveTags() { /* extract all #hastags */ @@ -129,14 +132,22 @@ class Notice extends Memcached_DataObject return true; } + /* Add them to the database */ + return $this->saveKnownTags($match[1]); + } + + /** + * Record the given set of hash tags in the db for this notice. + * Given tag strings will be normalized and checked for dupes. + */ + function saveKnownTags($hashtags) + { //turn each into their canonical tag //this is needed to remove dupes before saving e.g. #hash.tag = #hashtag - $hashtags = array(); - for($i=0; $i<count($match[1]); $i++) { - $hashtags[] = common_canonical_tag($match[1][$i]); + for($i=0; $i<count($hashtags); $i++) { + $hashtags[$i] = common_canonical_tag($hashtags[$i]); } - /* Add them to the database */ foreach(array_unique($hashtags) as $hashtag) { /* elide characters we don't want in the tag */ $this->saveTag($hashtag); @@ -145,6 +156,10 @@ class Notice extends Memcached_DataObject return true; } + /** + * Record a single hash tag as associated with this notice. + * Tag format and uniqueness must be validated by caller. + */ function saveTag($hashtag) { $tag = new Notice_tag(); @@ -187,13 +202,23 @@ class Notice extends Memcached_DataObject * 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 + * string 'uri' unique ID for notice; defaults to local notice URL + * string 'url' permalink to notice; defaults to local notice URL + * string 'rendered' rendered HTML version of content + * array 'replies' list of profile URIs for reply delivery in + * place of extracting @-replies from content. + * array 'groups' list of group IDs to deliver to, in place of + * extracting ! tags from content + * array 'tags' list of hashtag strings to save with the notice + * in place of extracting # tags from content + * @fixme tag override * * @return Notice * @throws ClientException */ static function saveNew($profile_id, $content, $source, $options=null) { $defaults = array('uri' => null, + 'url' => null, 'reply_to' => null, 'repeat_of' => null); @@ -256,9 +281,10 @@ class Notice extends Memcached_DataObject } $notice->content = $final; - $notice->rendered = common_render_content($final, $notice); + $notice->source = $source; $notice->uri = $uri; + $notice->url = $url; // Handle repeat case @@ -283,6 +309,12 @@ class Notice extends Memcached_DataObject $notice->location_ns = $location_ns; } + if (!empty($rendered)) { + $notice->rendered = $rendered; + } else { + $notice->rendered = common_render_content($final, $notice); + } + if (Event::handle('StartNoticeSave', array(&$notice))) { // XXX: some of these functions write to the DB @@ -325,8 +357,33 @@ class Notice extends Memcached_DataObject # 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(); + // Save per-notice metadata... + + if (isset($replies)) { + $notice->saveKnownReplies($replies); + } else { + $notice->saveReplies(); + } + + if (isset($groups)) { + $notice->saveKnownGroups($groups); + } else { + $notice->saveGroups(); + } + + if (isset($tags)) { + $notice->saveKnownTags($tags); + } else { + $notice->saveTags(); + } + + // @fixme pass in data for URLs too? + $notice->saveUrls(); + + // Prepare inbox delivery, may be queued to background. $notice->distribute(); return $notice; @@ -502,17 +559,17 @@ class Notice extends Memcached_DataObject } } - function publicStream($offset=0, $limit=20, $since_id=0, $max_id=0, $since=null) + function publicStream($offset=0, $limit=20, $since_id=0, $max_id=0) { $ids = Notice::stream(array('Notice', '_publicStreamDirect'), array(), 'public', - $offset, $limit, $since_id, $max_id, $since); + $offset, $limit, $since_id, $max_id); return Notice::getStreamByIds($ids); } - function _publicStreamDirect($offset=0, $limit=20, $since_id=0, $max_id=0, $since=null) + function _publicStreamDirect($offset=0, $limit=20, $since_id=0, $max_id=0) { $notice = new Notice(); @@ -541,10 +598,6 @@ class Notice extends Memcached_DataObject $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()) { @@ -559,17 +612,17 @@ class Notice extends Memcached_DataObject return $ids; } - function conversationStream($id, $offset=0, $limit=20, $since_id=0, $max_id=0, $since=null) + function conversationStream($id, $offset=0, $limit=20, $since_id=0, $max_id=0) { $ids = Notice::stream(array('Notice', '_conversationStreamDirect'), array($id), 'notice:conversation_ids:'.$id, - $offset, $limit, $since_id, $max_id, $since); + $offset, $limit, $since_id, $max_id); return Notice::getStreamByIds($ids); } - function _conversationStreamDirect($id, $offset=0, $limit=20, $since_id=0, $max_id=0, $since=null) + function _conversationStreamDirect($id, $offset=0, $limit=20, $since_id=0, $max_id=0) { $notice = new Notice(); @@ -592,10 +645,6 @@ class Notice extends Memcached_DataObject $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()) { @@ -677,11 +726,39 @@ class Notice extends Memcached_DataObject return $ni; } - function addToInboxes($groups, $recipients) + /** + * Adds this notice to the inboxes of each local user who should receive + * it, based on author subscriptions, group memberships, and @-replies. + * + * Warning: running a second time currently will make items appear + * multiple times in users' inboxes. + * + * @fixme make more robust against errors + * @fixme break up massive deliveries to smaller background tasks + * + * @param array $groups optional list of Group objects; + * if left empty, will be loaded from group_inbox records + * @param array $recipient optional list of reply profile ids + * if left empty, will be loaded from reply records + */ + function addToInboxes($groups=null, $recipients=null) { $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; } @@ -714,6 +791,42 @@ class Notice extends Memcached_DataObject } /** + * Record this notice to the given group inboxes for delivery. + * Overrides the regular parsing of !group markup. + * + * @param string $group_ids + * @fixme might prefer URIs as identifiers, as for replies? + * best with generalizations on user_group to support + * remote groups better. + */ + function saveKnownGroups($group_ids) + { + if (!is_array($group_ids)) { + throw new ServerException("Bad type provided to saveKnownGroups"); + } + + $groups = array(); + foreach ($group_ids as $id) { + $group = User_group::staticGet('id', $id); + if ($group) { + common_log(LOG_ERR, "Local delivery to group id $id, $group->nickname"); + $result = $this->addToGroupInbox($group); + if (!$result) { + common_log_db_error($gi, 'INSERT', __FILE__); + } + + // @fixme should we save the tags here or not? + $groups[] = clone($group); + } else { + common_log(LOG_ERR, "Local delivery to group id $id skipped, doesn't exist"); + } + } + + return $groups; + } + + /** + * Parse !group delivery and record targets into group_inbox. * @return array of Group objects */ function saveGroups() @@ -797,8 +910,51 @@ class Notice extends Memcached_DataObject } /** + * Save reply records indicating that this notice needs to be + * delivered to the local users with the given URIs. + * + * Since this is expected to be used when saving foreign-sourced + * messages, we won't deliver to any remote targets as that's the + * source service's responsibility. + * + * @fixme Unlike saveReplies() there's no mail notification here. + * Move that to distrib queue handler? + * + * @param array of unique identifier URIs for recipients + */ + 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(); + + self::blow('reply:stream:%d', $user->id); + } + } + + return; + } + + /** + * Pull @-replies from this message's content in StatusNet markup format + * and save reply records indicating that this message needs to be + * delivered to those users. + * + * Side effect: local recipients get e-mail notifications here. + * @fixme move mail notifications to distrib? + * * @return array of integer profile IDs */ + function saveReplies() { // Don't save reply data for repeats @@ -807,76 +963,47 @@ class Notice extends Memcached_DataObject 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); + $sender = Profile::staticGet($this->profile_id); - $names = array(); + // @todo ideally this parser information would only + // be calculated once. - 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->content, $this); $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; } } } @@ -917,9 +1044,10 @@ class Notice extends Memcached_DataObject } /** - * Same calculation as saveGroups but without the saving - * @fixme merge the functions - * @return array of Group_inbox objects + * Pull list of groups this notice needs to be delivered to, + * as previously recorded by saveGroups() or saveKnownGroups(). + * + * @return array of Group objects */ function getGroups() { @@ -942,7 +1070,10 @@ class Notice extends Memcached_DataObject if ($gi->find()) { while ($gi->fetch()) { - $groups[] = clone($gi); + $group = User_group::staticGet('id', $gi->group_id); + if ($group) { + $groups[] = $group; + } } } @@ -962,6 +1093,8 @@ class Notice extends Memcached_DataObject '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:media' => 'http://purl.org/syndication/atommedia', + 'xmlns:poco' => 'http://portablecontacts.net/spec/1.0', 'xmlns:ostatus' => 'http://ostatus.org/schema/1.0'); } else { $attrs = array(); @@ -993,12 +1126,12 @@ class Notice extends Memcached_DataObject } $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); @@ -1045,6 +1178,17 @@ class Notice extends Memcached_DataObject } } + $groups = $this->getGroups(); + + foreach ($groups as $group) { + $xs->element( + 'link', array( + 'rel' => 'ostatus:attention', + 'href' => $group->permalink() + ) + ); + } + if (!empty($this->repeat_of)) { $repeat = Notice::staticGet('id', $this->repeat_of); if (!empty($repeat)) { @@ -1090,6 +1234,21 @@ class Notice extends Memcached_DataObject 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) + { + $noun = ActivityObject::fromNotice($this); + return $noun->asString('activity:' . $element); + } + function bestUrl() { if (!empty($this->url)) { @@ -1102,16 +1261,16 @@ class Notice extends Memcached_DataObject } } - function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0, $since=null) + function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0) { $cache = common_memcache(); if (empty($cache) || - $since_id != 0 || $max_id != 0 || (!is_null($since) && $since > 0) || + $since_id != 0 || $max_id != 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))); + $max_id))); } $idkey = common_cache_key($cachekey); @@ -1487,6 +1646,14 @@ class Notice extends Memcached_DataObject 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. diff --git a/classes/Notice_inbox.php b/classes/Notice_inbox.php index c27dcdfd6..47ed6b22d 100644 --- a/classes/Notice_inbox.php +++ b/classes/Notice_inbox.php @@ -49,12 +49,12 @@ class Notice_inbox extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - function stream($user_id, $offset, $limit, $since_id, $max_id, $since, $own=false) + function stream($user_id, $offset, $limit, $since_id, $max_id, $own=false) { throw new Exception('Notice_inbox no longer used; use Inbox'); } - function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id, $since) + function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id) { throw new Exception('Notice_inbox no longer used; use Inbox'); } diff --git a/classes/Notice_tag.php b/classes/Notice_tag.php index 4fd76e8ea..a5d0716a7 100644 --- a/classes/Notice_tag.php +++ b/classes/Notice_tag.php @@ -46,7 +46,7 @@ class Notice_tag extends Memcached_DataObject return Notice::getStreamByIds($ids); } - function _streamDirect($tag, $offset, $limit, $since_id, $max_id, $since) + function _streamDirect($tag, $offset, $limit, $since_id, $max_id) { $nt = new Notice_tag(); @@ -63,10 +63,6 @@ class Notice_tag extends Memcached_DataObject $nt->whereAdd('notice_id < ' . $max_id); } - if (!is_null($since)) { - $nt->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\''); - } - $nt->orderBy('notice_id DESC'); if (!is_null($offset)) { diff --git a/classes/Profile.php b/classes/Profile.php index 494c697e4..470ef3320 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -163,27 +163,27 @@ class Profile extends Memcached_DataObject return null; } - function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null) + function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) { $ids = Notice::stream(array($this, '_streamTaggedDirect'), array($tag), 'profile:notice_ids_tagged:' . $this->id . ':' . $tag, - $offset, $limit, $since_id, $max_id, $since); + $offset, $limit, $since_id, $max_id); return Notice::getStreamByIds($ids); } - function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null) + function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) { // XXX: I'm not sure this is going to be any faster. It probably isn't. $ids = Notice::stream(array($this, '_streamDirect'), array(), 'profile:notice_ids:' . $this->id, - $offset, $limit, $since_id, $max_id, $since); + $offset, $limit, $since_id, $max_id); return Notice::getStreamByIds($ids); } - function _streamTaggedDirect($tag, $offset, $limit, $since_id, $max_id, $since) + function _streamTaggedDirect($tag, $offset, $limit, $since_id, $max_id) { // XXX It would be nice to do this without a join @@ -202,10 +202,6 @@ class Profile extends Memcached_DataObject $query .= " and id < $max_id"; } - if (!is_null($since)) { - $query .= " and created > '" . date('Y-m-d H:i:s', $since) . "'"; - } - $query .= ' order by id DESC'; if (!is_null($offset)) { @@ -223,7 +219,7 @@ class Profile extends Memcached_DataObject return $ids; } - function _streamDirect($offset, $limit, $since_id, $max_id, $since = null) + function _streamDirect($offset, $limit, $since_id, $max_id) { $notice = new Notice(); @@ -240,10 +236,6 @@ class Profile extends Memcached_DataObject $notice->whereAdd('id <= ' . $max_id); } - if (!is_null($since)) { - $notice->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\''); - } - $notice->orderBy('id DESC'); if (!is_null($offset)) { @@ -792,44 +784,17 @@ class Profile extends Memcached_DataObject * Returns an XML string fragment with profile information as an * Activity Streams noun object with the given element type. * - * Assumes that 'activity' namespace has been previously defined. + * Assumes that 'activity', 'georss', and 'poco' namespace has been + * previously defined. * * @param string $element one of 'actor', '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/person' - ); - $xs->element( - 'id', - null, - $this->getUri() - ); - $xs->element('title', null, $this->getBestName()); - - $avatar = $this->getAvatar(AVATAR_PROFILE_SIZE); - - $xs->element( - 'link', array( - 'type' => empty($avatar) ? 'image/png' : $avatar->mediatype, - 'rel' => 'avatar', - 'href' => empty($avatar) - ? Avatar::defaultImage(AVATAR_PROFILE_SIZE) - : $avatar->displayUrl() - ), - '' - ); - - $xs->elementEnd('activity:' . $element); - - return $xs->getString(); + $noun = ActivityObject::fromProfile($this); + return $noun->asString('activity:' . $element); } /** @@ -841,31 +806,37 @@ class Profile extends Memcached_DataObject { $uri = null; - // check for a local user first - $user = User::staticGet('id', $this->id); - - if (!empty($user)) { - $uri = common_local_url( - 'userbyid', - array('id' => $user->id) - ); - } else { + // give plugins a chance to set the URI + if (Event::handle('StartGetProfileUri', array($this, &$uri))) { - // give plugins a chance to set the URI - if (Event::handle('StartGetProfileUri', array($this, &$uri))) { + // check for a local user first + $user = User::staticGet('id', $this->id); + if (!empty($user)) { + $uri = $user->uri; + } else { // return OMB profile if any $remote = Remote_profile::staticGet('id', $this->id); - if (!empty($remote)) { $uri = $remote->uri; } - - Event::handle('EndGetProfileUri', array($this, &$uri)); } + Event::handle('EndGetProfileUri', array($this, &$uri)); } return $uri; } + function hasBlocked($other) + { + $block = Profile_block::get($this->id, $other->id); + + if (empty($block)) { + $result = false; + } else { + $result = true; + } + + return $result; + } } diff --git a/classes/Reply.php b/classes/Reply.php index 49b1e05e5..659e04c92 100644 --- a/classes/Reply.php +++ b/classes/Reply.php @@ -22,16 +22,16 @@ class Reply extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null) + function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) { $ids = Notice::stream(array('Reply', '_streamDirect'), array($user_id), 'reply:stream:' . $user_id, - $offset, $limit, $since_id, $max_id, $since); + $offset, $limit, $since_id, $max_id); return $ids; } - function _streamDirect($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null) + function _streamDirect($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) { $reply = new Reply(); $reply->profile_id = $user_id; @@ -44,10 +44,6 @@ class Reply extends Memcached_DataObject $reply->whereAdd('notice_id < ' . $max_id); } - if (!is_null($since)) { - $reply->whereAdd('modified > \'' . date('Y-m-d H:i:s', $since) . '\''); - } - $reply->orderBy('notice_id DESC'); if (!is_null($offset)) { diff --git a/classes/Subscription.php b/classes/Subscription.php index faf1331cd..9cef2df1a 100644 --- a/classes/Subscription.php +++ b/classes/Subscription.php @@ -24,7 +24,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } */ require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; -class Subscription extends Memcached_DataObject +class Subscription extends Memcached_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -34,8 +34,8 @@ class Subscription extends Memcached_DataObject public $subscribed; // int(4) primary_key not_null public $jabber; // tinyint(1) default_1 public $sms; // tinyint(1) default_1 - public $token; // varchar(255) - public $secret; // varchar(255) + public $token; // varchar(255) + public $secret; // varchar(255) public $created; // datetime() not_null public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP @@ -45,9 +45,177 @@ class Subscription extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - + function pkeyGet($kv) { return Memcached_DataObject::pkeyGet('Subscription', $kv); } + + /** + * Make a new subscription + * + * @param Profile $subscriber party to receive new notices + * @param Profile $other party sending notices; publisher + * + * @return Subscription new subscription + */ + + static function start($subscriber, $other) + { + if (!$subscriber->hasRight(Right::SUBSCRIBE)) { + throw new Exception(_('You have been banned from subscribing.')); + } + + if (self::exists($subscriber, $other)) { + throw new Exception(_('Already subscribed!')); + } + + if ($other->hasBlocked($subscriber)) { + throw new Exception(_('User has blocked you.')); + } + + if (Event::handle('StartSubscribe', array($subscriber, $other))) { + + $sub = new Subscription(); + + $sub->subscriber = $subscriber->id; + $sub->subscribed = $other->id; + $sub->created = common_sql_now(); + + $result = $sub->insert(); + + if (!$result) { + common_log_db_error($sub, 'INSERT', __FILE__); + throw new Exception(_('Could not save subscription.')); + } + + $sub->notify(); + + self::blow('user:notices_with_friends:%d', $subscriber->id); + + $subscriber->blowSubscriptionsCount(); + $other->blowSubscribersCount(); + + $otherUser = User::staticGet('id', $other->id); + + if (!empty($otherUser) && + $otherUser->autosubscribe && + !self::exists($other, $subscriber) && + !$subscriber->hasBlocked($other)) { + + $auto = new Subscription(); + + $auto->subscriber = $subscriber->id; + $auto->subscribed = $other->id; + $auto->created = common_sql_now(); + + $result = $auto->insert(); + + if (!$result) { + common_log_db_error($auto, 'INSERT', __FILE__); + throw new Exception(_('Could not save subscription.')); + } + + $auto->notify(); + } + + Event::handle('EndSubscribe', array($subscriber, $other)); + } + + return true; + } + + 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 + + $this->notifyEmail(); + } + + function notifyEmail() + { + $subscribedUser = User::staticGet('id', $this->subscribed); + + if (!empty($subscribedUser)) { + + $subscriber = Profile::staticGet('id', $this->subscriber); + + mail_subscribe_notify_profile($subscribedUser, $subscriber); + } + } + + /** + * Cancel a subscription + * + */ + + function cancel($subscriber, $other) + { + if (!self::exists($subscriber, $other)) { + throw new Exception(_('Not subscribed!')); + } + + // Don't allow deleting self subs + + if ($subscriber->id == $other->id) { + throw new Exception(_('Couldn\'t delete self-subscription.')); + } + + if (Event::handle('StartUnsubscribe', array($subscriber, $other))) { + + $sub = Subscription::pkeyGet(array('subscriber' => $subscriber->id, + 'subscribed' => $other->id)); + + // note we checked for existence above + + 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__); + throw new Exception(_('Couldn\'t delete subscription OMB token.')); + } + } else { + common_log(LOG_ERR, "Couldn't find credentials with token {$token->tok}"); + } + } + + $result = $sub->delete(); + + if (!$result) { + common_log_db_error($sub, 'DELETE', __FILE__); + throw new Exception(_('Couldn\'t delete subscription.')); + } + + self::blow('user:notices_with_friends:%d', $subscriber->id); + + $subscriber->blowSubscriptionsCount(); + $other->blowSubscribersCount(); + + Event::handle('EndUnsubscribe', array($subscriber, $other)); + } + + return; + } + + function exists($subscriber, $other) + { + $sub = Subscription::pkeyGet(array('subscriber' => $subscriber->id, + 'subscribed' => $other->id)); + return (empty($sub)) ? false : true; + } } diff --git a/classes/User.php b/classes/User.php index 72c3f39e9..357c1e501 100644 --- a/classes/User.php +++ b/classes/User.php @@ -80,11 +80,7 @@ class User extends Memcached_DataObject function isSubscribed($other) { - assert(!is_null($other)); - // XXX: cache results of this query - $sub = Subscription::pkeyGet(array('subscriber' => $this->id, - 'subscribed' => $other->id)); - return (is_null($sub)) ? false : true; + return Subscription::exists($this->getProfile(), $other); } // 'update' won't write key columns, so we have to do it ourselves. @@ -167,17 +163,8 @@ class User extends Memcached_DataObject function hasBlocked($other) { - - $block = Profile_block::get($this->id, $other->id); - - if (is_null($block)) { - $result = false; - } else { - $result = true; - $block->free(); - } - - return $result; + $profile = $this->getProfile(); + return $profile->hasBlocked($other); } /** @@ -219,6 +206,7 @@ class User extends Memcached_DataObject if(! User::allowed_nickname($nickname)){ common_log(LOG_WARNING, sprintf("Attempted to register a nickname that is not allowed: %s", $profile->nickname), __FILE__); + return false; } $profile->profileurl = common_profile_url($nickname); @@ -469,28 +457,28 @@ class User extends Memcached_DataObject return $user; } - function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) { - $ids = Reply::stream($this->id, $offset, $limit, $since_id, $before_id, $since); + $ids = Reply::stream($this->id, $offset, $limit, $since_id, $before_id); return Notice::getStreamByIds($ids); } - function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) { + function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) { $profile = $this->getProfile(); if (!$profile) { return null; } else { - return $profile->getTaggedNotices($tag, $offset, $limit, $since_id, $before_id, $since); + return $profile->getTaggedNotices($tag, $offset, $limit, $since_id, $before_id); } } - function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) { $profile = $this->getProfile(); if (!$profile) { return null; } else { - return $profile->getNotices($offset, $limit, $since_id, $before_id, $since); + return $profile->getNotices($offset, $limit, $since_id, $before_id); } } @@ -500,24 +488,24 @@ class User extends Memcached_DataObject return Notice::getStreamByIds($ids); } - function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) { - return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, $since, false); + return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, false); } - function noticeInbox($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + function noticeInbox($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) { - return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, $since, true); + return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, true); } - function friendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + function friendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) { - return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, $since, false); + return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, false); } - function ownFriendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + function ownFriendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) { - return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, $since, true); + return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, true); } function blowFavesCache() @@ -802,7 +790,7 @@ class User extends Memcached_DataObject return Notice::getStreamByIds($ids); } - function _repeatedByMeDirect($offset, $limit, $since_id, $max_id, $since) + function _repeatedByMeDirect($offset, $limit, $since_id, $max_id) { $notice = new Notice(); @@ -826,10 +814,6 @@ class User extends Memcached_DataObject $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()) { @@ -849,12 +833,12 @@ class User extends Memcached_DataObject $ids = Notice::stream(array($this, '_repeatsOfMeDirect'), array(), 'user:repeats_of_me:'.$this->id, - $offset, $limit, $since_id, $max_id, null); + $offset, $limit, $since_id, $max_id); return Notice::getStreamByIds($ids); } - function _repeatsOfMeDirect($offset, $limit, $since_id, $max_id, $since) + function _repeatsOfMeDirect($offset, $limit, $since_id, $max_id) { $qry = 'SELECT DISTINCT original.id AS id ' . @@ -869,10 +853,6 @@ class User extends Memcached_DataObject $qry .= 'AND original.id <= ' . $max_id . ' '; } - if (!is_null($since)) { - $qry .= 'AND original.modified > \'' . date('Y-m-d H:i:s', $since) . '\' '; - } - // NOTE: we sort by fave time, not by notice time! $qry .= 'ORDER BY original.id DESC '; diff --git a/classes/User_group.php b/classes/User_group.php index 379e6b721..e92887474 100644 --- a/classes/User_group.php +++ b/classes/User_group.php @@ -10,21 +10,23 @@ class User_group extends Memcached_DataObject public $__table = 'user_group'; // table name public $id; // int(4) primary_key not_null - public $nickname; // varchar(64) unique_key + public $nickname; // varchar(64) public $fullname; // varchar(255) public $homepage; // varchar(255) - public $description; // text() + public $description; // text public $location; // varchar(255) public $original_logo; // varchar(255) public $homepage_logo; // varchar(255) public $stream_logo; // varchar(255) public $mini_logo; // varchar(255) public $design_id; // int(4) - public $created; // datetime() not_null - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + public $created; // datetime not_null default_0000-00-00%2000%3A00%3A00 + public $modified; // timestamp not_null default_CURRENT_TIMESTAMP + public $uri; // varchar(255) unique_key + public $mainpage; // varchar(255) /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User_group',$k,$v); } + function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('User_group',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE @@ -39,14 +41,44 @@ class User_group extends Memcached_DataObject function homeUrl() { - return common_local_url('showgroup', - array('nickname' => $this->nickname)); + $url = null; + if (Event::handle('StartUserGroupHomeUrl', array($this, &$url))) { + // normally stored in mainpage, but older ones may be null + if (!empty($this->mainpage)) { + $url = $this->mainpage; + } else { + $url = common_local_url('showgroup', + array('nickname' => $this->nickname)); + } + } + Event::handle('EndUserGroupHomeUrl', array($this, &$url)); + return $url; + } + + function getUri() + { + $uri = null; + if (Event::handle('StartUserGroupGetUri', array($this, &$uri))) { + if (!empty($this->uri)) { + $uri = $this->uri; + } else { + $uri = common_local_url('groupbyid', + array('id' => $this->id)); + } + } + Event::handle('EndUserGroupGetUri', array($this, &$uri)); + return $uri; } function permalink() { - return common_local_url('groupbyid', - array('id' => $this->id)); + $url = null; + if (Event::handle('StartUserGroupPermalink', array($this, &$url))) { + $url = common_local_url('groupbyid', + array('id' => $this->id)); + } + Event::handle('EndUserGroupPermalink', array($this, &$url)); + return $url; } function getNotices($offset, $limit, $since_id=null, $max_id=null) @@ -59,7 +91,7 @@ class User_group extends Memcached_DataObject return Notice::getStreamByIds($ids); } - function _streamDirect($offset, $limit, $since_id, $max_id, $since) + function _streamDirect($offset, $limit, $since_id, $max_id) { $inbox = new Group_inbox(); @@ -76,10 +108,6 @@ class User_group extends Memcached_DataObject $inbox->whereAdd('notice_id <= ' . $max_id); } - if (!is_null($since)) { - $inbox->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\''); - } - $inbox->orderBy('notice_id DESC'); if (!is_null($offset)) { @@ -367,25 +395,41 @@ class User_group extends Memcached_DataObject return $xs->getString(); } + /** + * Returns an XML string fragment with group information as an + * Activity Streams <activity:subject> element. + * + * Assumes that 'activity' namespace has been previously defined. + * + * @return string + */ function asActivitySubject() { - $xs = new XMLStringer(true); + return $this->asActivityNoun('subject'); + } - $xs->elementStart('activity:subject'); - $xs->element('activity:object', null, 'http://activitystrea.ms/schema/1.0/group'); - $xs->element('id', null, $this->permalink()); - $xs->element('title', null, $this->getBestName()); - $xs->element( - 'link', array( - 'rel' => 'avatar', - 'href' => empty($this->homepage_logo) - ? User_group::defaultLogo(AVATAR_PROFILE_SIZE) - : $this->homepage_logo - ) - ); - $xs->elementEnd('activity:subject'); + /** + * Returns an XML string fragment with group information as an + * Activity Streams noun object with the given element type. + * + * Assumes that 'activity', 'georss', and 'poco' namespace has been + * previously defined. + * + * @param string $element one of 'actor', 'subject', 'object', 'target' + * + * @return string + */ + function asActivityNoun($element) + { + $noun = ActivityObject::fromGroup($this); + return $noun->asString('activity:' . $element); + } - return $xs->getString(); + function getAvatar() + { + return empty($this->homepage_logo) + ? User_group::defaultLogo(AVATAR_PROFILE_SIZE) + : $this->homepage_logo; } static function register($fields) { @@ -403,28 +447,31 @@ class User_group extends Memcached_DataObject $group->homepage = $homepage; $group->description = $description; $group->location = $location; + $group->uri = $uri; + $group->mainpage = $mainpage; $group->created = common_sql_now(); $result = $group->insert(); if (!$result) { common_log_db_error($group, 'INSERT', __FILE__); - $this->serverError( - _('Could not create group.'), - 500, - $this->format - ); - return; + throw new ServerException(_('Could not create group.')); + } + + if (!isset($uri) || empty($uri)) { + $orig = clone($group); + $group->uri = common_local_url('groupbyid', array('id' => $group->id)); + $result = $group->update($orig); + if (!$result) { + common_log_db_error($group, 'UPDATE', __FILE__); + throw new ServerException(_('Could not set group URI.')); + } } + $result = $group->setAliases($aliases); if (!$result) { - $this->serverError( - _('Could not create aliases.'), - 500, - $this->format - ); - return; + throw new ServerException(_('Could not create aliases.')); } $member = new Group_member(); @@ -438,12 +485,22 @@ class User_group extends Memcached_DataObject if (!$result) { common_log_db_error($member, 'INSERT', __FILE__); - $this->serverError( - _('Could not set group membership.'), - 500, - $this->format - ); - return; + throw new ServerException(_('Could not set group membership.')); + } + + if ($local) { + $local_group = new Local_group(); + + $local_group->group_id = $group->id; + $local_group->nickname = $nickname; + $local_group->created = common_sql_now(); + + $result = $local_group->insert(); + + if (!$result) { + common_log_db_error($local_group, 'INSERT', __FILE__); + throw new ServerException(_('Could not save local group info.')); + } } $group->query('COMMIT'); diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 81c1b68b2..3fb8ee208 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -245,13 +245,6 @@ modified = 384 group_id = K profile_id = K -[invitation] -code = 130 -user_id = 129 -address = 130 -address_type = 130 -created = 142 - [inbox] user_id = 129 notice_ids = 66 @@ -259,9 +252,26 @@ notice_ids = 66 [inbox__keys] user_id = K +[invitation] +code = 130 +user_id = 129 +address = 130 +address_type = 130 +created = 142 + [invitation__keys] code = K +[local_group] +group_id = 129 +nickname = 2 +created = 142 +modified = 384 + +[local_group__keys] +group_id = K +nickname = U + [location_namespace] id = 129 description = 2 @@ -369,7 +379,7 @@ icon = 130 source_url = 2 organization = 2 homepage = 2 -callback_url = 130 +callback_url = 2 type = 17 access_type = 17 created = 142 @@ -440,13 +450,13 @@ tag = K [queue_item] id = 129 -frame = 66 +frame = 194 transport = 130 created = 142 claimed = 14 [queue_item__keys] -id = K +id = N [related_group] group_id = 129 @@ -593,10 +603,11 @@ mini_logo = 2 design_id = 1 created = 142 modified = 384 +uri = 2 +mainpage = 2 [user_group__keys] id = N -nickname = U [user_openid] canonical = 130 @@ -627,4 +638,3 @@ modified = 384 [user_location_prefs__keys] user_id = K - |