diff options
author | Evan Prodromou <evan@status.net> | 2009-12-22 16:44:19 -0800 |
---|---|---|
committer | Evan Prodromou <evan@status.net> | 2009-12-22 16:44:19 -0800 |
commit | f6bf9529805cd58fdd1671dd9b133bde05e8ae87 (patch) | |
tree | cf272bd1105da48f016b635db4d9c34810adcbda /classes | |
parent | f987273f118a12d443b6789c2ab59d7a4b01f678 (diff) | |
parent | 30c2e2ce83282f0bc268153d7ec465fbb5cf00ca (diff) |
Merge branch 'testing'
Diffstat (limited to 'classes')
-rw-r--r-- | classes/Design.php | 2 | ||||
-rw-r--r-- | classes/Login_token.php | 55 | ||||
-rw-r--r-- | classes/Memcached_DataObject.php | 14 | ||||
-rw-r--r-- | classes/Message.php | 8 | ||||
-rw-r--r-- | classes/Notice.php | 456 | ||||
-rw-r--r-- | classes/Notice_inbox.php | 42 | ||||
-rw-r--r-- | classes/Profile.php | 11 | ||||
-rw-r--r-- | classes/User.php | 277 | ||||
-rw-r--r-- | classes/statusnet.ini | 2 |
9 files changed, 620 insertions, 247 deletions
diff --git a/classes/Design.php b/classes/Design.php index 89ae50c8c..4e7d7dfb2 100644 --- a/classes/Design.php +++ b/classes/Design.php @@ -101,7 +101,7 @@ class Design extends Memcached_DataObject } if (0 != mb_strlen($css)) { - $out->element('style', array('type' => 'text/css'), $css); + $out->style($css); } } diff --git a/classes/Login_token.php b/classes/Login_token.php new file mode 100644 index 000000000..746cd7f22 --- /dev/null +++ b/classes/Login_token.php @@ -0,0 +1,55 @@ +<?php +/** + * Table Definition for login_token + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2009, StatusNet, 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; + +class Login_token extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'login_token'; // table name + public $user_id; // int(4) primary_key not_null + public $token; // char(32) not_null + public $created; // datetime() not_null + public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + + /* Static get */ + function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('Login_token',$k,$v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + /* + DB_DataObject calculates the sequence key(s) by taking the first key returned by the keys() function. + In this case, the keys() function returns user_id as the first key. user_id is not a sequence, but + DB_DataObject's sequenceKey() will incorrectly think it is. Then, since the sequenceKey() is a numeric + type, but is not set to autoincrement in the database, DB_DataObject will create a _seq table and + manage the sequence itself. This is not the correct behavior for the user_id in this class. + So we override that incorrect behavior, and simply say there is no sequence key. + */ + function sequenceKey() + { + return array(false,false); + } +} diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 644b84d5c..d8b0db5a6 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -23,6 +23,20 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; class Memcached_DataObject extends DB_DataObject { + /** + * Destructor to free global memory resources associated with + * this data object when it's unset or goes out of scope. + * DB_DataObject doesn't do this yet by itself. + */ + + function __destruct() + { + $this->free(); + if (method_exists('DB_DataObject', '__destruct')) { + parent::__destruct(); + } + } + function &staticGet($cls, $k, $v=null) { if (is_null($v)) { diff --git a/classes/Message.php b/classes/Message.php index 718a9d922..16d0c60b3 100644 --- a/classes/Message.php +++ b/classes/Message.php @@ -89,4 +89,12 @@ class Message extends Memcached_DataObject $contentlimit = self::maxContent(); return ($contentlimit > 0 && !empty($content) && (mb_strlen($content) > $contentlimit)); } + + function notify() + { + $from = User::staticGet('id', $this->from_profile); + $to = User::staticGet('id', $this->to_profile); + + mail_notify_message($this, $from, $to); + } } diff --git a/classes/Notice.php b/classes/Notice.php index 661072156..7651d8bd5 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -55,13 +55,13 @@ class Notice extends Memcached_DataObject 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 $source; // varchar(32) @@ -70,9 +70,11 @@ class Notice extends Memcached_DataObject 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); } @@ -113,6 +115,12 @@ class Notice extends Memcached_DataObject //Null any notices that are replies to this notice $this->query(sprintf("UPDATE notice set reply_to = null WHERE reply_to = %d", $this->id)); + + //Null any notices that are repeats of this notice + //XXX: probably need to uncache these, too + + $this->query(sprintf("UPDATE notice set repeat_of = null WHERE repeat_of = %d", $this->id)); + $related = array('Reply', 'Fave', 'Notice_tag', @@ -167,9 +175,48 @@ class Notice extends Memcached_DataObject } } - 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 (empty($is_local)) { + $is_local = Notice::LOCAL_PUBLIC; + } $profile = Profile::staticGet($profile_id); @@ -225,7 +272,14 @@ class Notice extends Memcached_DataObject $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; + $notice->reply_to = $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); @@ -423,10 +477,60 @@ class Notice extends Memcached_DataObject $this->blowTagCache($blowLast); $this->blowGroupCache($blowLast); $this->blowConversationCache($blowLast); + $this->blowRepeatCache(); $profile = Profile::staticGet($this->profile_id); $profile->blowNoticeCount(); } + function blowRepeatCache() + { + if (!empty($this->repeat_of)) { + $cache = common_memcache(); + if (!empty($cache)) { + // XXX: only blow if <100 in cache + $ck = common_cache_key('notice:repeats:'.$this->repeat_of); + $result = $cache->delete($ck); + + $user = User::staticGet('id', $this->profile_id); + + if (!empty($user)) { + $uk = common_cache_key('user:repeated_by_me:'.$user->id); + $cache->delete($uk); + $user->free(); + unset($user); + } + + $original = Notice::staticGet('id', $this->repeat_of); + + if (!empty($original)) { + $originalUser = User::staticGet('id', $original->profile_id); + if (!empty($originalUser)) { + $ouk = common_cache_key('user:repeats_of_me:'.$originalUser->id); + $cache->delete($ouk); + $originalUser->free(); + unset($originalUser); + } + $original->free(); + unset($original); + } + + $ni = new Notice_inbox(); + + $ni->notice_id = $this->id; + + if ($ni->find()) { + while ($ni->fetch()) { + $tmk = common_cache_key('user:repeated_to_me:'.$ni->user_id); + $cache->delete($tmk); + } + } + + $ni->free(); + unset($ni); + } + } + } + function blowConversationCache($blowLast=false) { $cache = common_memcache(); @@ -456,8 +560,18 @@ class Notice extends Memcached_DataObject if ($member->find()) { while ($member->fetch()) { $cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id)); + $cache->delete(common_cache_key('notice_inbox:by_user_own:' . $member->profile_id)); + if (empty($this->repeat_of)) { + $cache->delete(common_cache_key('user:friends_timeline:' . $member->profile_id)); + $cache->delete(common_cache_key('user:friends_timeline_own:' . $member->profile_id)); + } if ($blowLast) { $cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id . ';last')); + $cache->delete(common_cache_key('notice_inbox:by_user_own:' . $member->profile_id . ';last')); + if (empty($this->repeat_of)) { + $cache->delete(common_cache_key('user:friends_timeline:' . $member->profile_id . ';last')); + $cache->delete(common_cache_key('user:friends_timeline_own:' . $member->profile_id . ';last')); + } } } } @@ -505,9 +619,17 @@ 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 (empty($this->repeat_of)) { + $cache->delete(common_cache_key('user:friends_timeline:'.$user->id)); + $cache->delete(common_cache_key('user:friends_timeline_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')); + if (empty($this->repeat_of)) { + $cache->delete(common_cache_key('user:friends_timeline:'.$user->id.';last')); + $cache->delete(common_cache_key('user:friends_timeline_own:'.$user->id.';last')); + } } } $user->free(); @@ -581,193 +703,6 @@ class Notice extends Memcached_DataObject } } - # 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(); @@ -788,10 +723,24 @@ class Notice extends Memcached_DataObject 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); } } @@ -948,39 +897,7 @@ class Notice extends Memcached_DataObject } } - $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) { - // 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; - } - } - - if ($cnt > 0) { - $inbox = new Notice_inbox(); - $result = $inbox->query($qry); - if (PEAR::isError($result)) { - common_log_db_error($inbox, $qry); - } - } + Notice_inbox::bulkInsert($this->id, $this->created, $ni); return; } @@ -1079,6 +996,9 @@ class Notice extends Memcached_DataObject return true; } + /** + * @return array of integer profile IDs + */ function saveReplies() { // Alternative reply format @@ -1157,8 +1077,8 @@ class Notice extends Memcached_DataObject $recipientIds = array_keys($replied); - foreach ($recipientIds as $recipient) { - $user = User::staticGet('id', $recipient); + foreach ($recipientIds as $recipientId) { + $user = User::staticGet('id', $recipientId); if ($user) { mail_notify_attn($user, $this); } @@ -1441,4 +1361,72 @@ class Notice extends Memcached_DataObject return $location; } + + function repeat($repeater_id, $source) + { + $author = Profile::staticGet('id', $this->profile_id); + + // FIXME: truncate on long repeats...? + + $content = sprintf(_('RT @%1$s %2$s'), + $author->nickname, + $this->content); + + 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 (!empty($idstr)) { + $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; + } } diff --git a/classes/Notice_inbox.php b/classes/Notice_inbox.php index d3e7853b1..b39006542 100644 --- a/classes/Notice_inbox.php +++ b/classes/Notice_inbox.php @@ -32,6 +32,7 @@ define('NOTICE_INBOX_SOFT_LIMIT', 1000); define('NOTICE_INBOX_SOURCE_SUB', 1); define('NOTICE_INBOX_SOURCE_GROUP', 2); define('NOTICE_INBOX_SOURCE_REPLY', 3); +define('NOTICE_INBOX_SOURCE_FORWARD', 4); define('NOTICE_INBOX_SOURCE_GATEWAY', -1); class Notice_inbox extends Memcached_DataObject @@ -83,7 +84,7 @@ class Notice_inbox extends Memcached_DataObject $inbox->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\''); } - $inbox->orderBy('notice_id DESC'); + $inbox->orderBy('created DESC'); if (!is_null($offset)) { $inbox->limit($offset, $limit); @@ -141,4 +142,43 @@ class Notice_inbox extends Memcached_DataObject 'WHERE user_id = ' . $user_id . ' ' . 'AND notice_id in ('.implode(',', $notices).')'); } + + static function bulkInsert($notice_id, $created, $ni) + { + $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.', '.$notice_id.', '.$source.", '".$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; + } + } + + if ($cnt > 0) { + $inbox = new Notice_inbox(); + $result = $inbox->query($qry); + if (PEAR::isError($result)) { + common_log_db_error($inbox, $qry); + } + } + + return; + } } diff --git a/classes/Profile.php b/classes/Profile.php index 4b2e09006..03196447b 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -716,4 +716,15 @@ class Profile extends Memcached_DataObject } return $result; } + + function hasRepeated($notice_id) + { + // XXX: not really a pkey, but should work + + $notice = Memcached_DataObject::pkeyGet('Notice', + array('profile_id' => $this->id, + 'repeat_of' => $notice_id)); + + return !empty($notice); + } } diff --git a/classes/User.php b/classes/User.php index f905ea2b7..ae709b46b 100644 --- a/classes/User.php +++ b/classes/User.php @@ -180,6 +180,27 @@ class User extends Memcached_DataObject return $result; } + /** + * Register a new user account and profile and set up default subscriptions. + * If a new-user welcome message is configured, this will be sent. + * + * @param array $fields associative array of optional properties + * string 'bio' + * string 'email' + * bool 'email_confirmed' pass true to mark email as pre-confirmed + * string 'fullname' + * string 'homepage' + * string 'location' informal string description of geolocation + * 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 + * string 'nickname' REQUIRED + * string 'password' (may be missing for eg OpenID registrations) + * string 'code' invite code + * ?string 'uri' permalink to notice; defaults to local notice URL + * @return mixed User object or false on failure + */ static function register($fields) { // MAGICALLY put fields into current scope @@ -329,7 +350,7 @@ class User extends Memcached_DataObject $profile->query('COMMIT'); - if ($email && !$user->email) { + if (!empty($email) && !$user->email) { mail_confirm_address($user, $confirm->code, $profile->nickname, $email); } @@ -473,6 +494,77 @@ class User extends Memcached_DataObject return Notice::getStreamByIds($ids); } + function friendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + { + $ids = Notice::stream(array($this, '_friendsTimelineDirect'), + array(false), + 'user:friends_timeline:'.$this->id, + $offset, $limit, $since_id, $before_id, $since); + + return Notice::getStreamByIds($ids); + } + + function ownFriendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + { + $ids = Notice::stream(array($this, '_friendsTimelineDirect'), + array(true), + 'user:friends_timeline_own:'.$this->id, + $offset, $limit, $since_id, $before_id, $since); + + return Notice::getStreamByIds($ids); + } + + function _friendsTimelineDirect($own, $offset, $limit, $since_id, $max_id, $since) + { + $qry = + 'SELECT notice.id AS id ' . + 'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' . + 'WHERE notice_inbox.user_id = ' . $this->id . ' ' . + 'AND notice.repeat_of IS NULL '; + + if (!$own) { + // XXX: autoload notice inbox for constant + $inbox = new Notice_inbox(); + + $qry .= 'AND notice_inbox.source != ' . NOTICE_INBOX_SOURCE_GATEWAY . ' '; + } + + if ($since_id != 0) { + $qry .= 'AND notice.id > ' . $since_id . ' '; + } + + if ($max_id != 0) { + $qry .= 'AND notice.id <= ' . $max_id . ' '; + } + + if (!is_null($since)) { + $qry .= 'AND notice.modified > \'' . date('Y-m-d H:i:s', $since) . '\' '; + } + + // NOTE: we sort by fave time, not by notice time! + + $qry .= 'ORDER BY notice.id DESC '; + + if (!is_null($offset)) { + $qry .= "LIMIT $limit OFFSET $offset"; + } + + $ids = array(); + + $notice = new Notice(); + + $notice->query($qry); + + while ($notice->fetch()) { + $ids[] = $notice->id; + } + + $notice->free(); + $notice = NULL; + + return $ids; + } + function blowFavesCache() { $cache = common_memcache(); @@ -502,6 +594,19 @@ class User extends Memcached_DataObject { // Add a new block record + // no blocking (and thus unsubbing from) yourself + + if ($this->id == $other->id) { + common_log(LOG_WARNING, + sprintf( + "Profile ID %d (%s) tried to block his or herself.", + $profile->id, + $profile->nickname + ) + ); + return false; + } + $block = new Profile_block(); // Begin a transaction @@ -520,16 +625,7 @@ class User extends Memcached_DataObject // Cancel their subscription, if it exists - $sub = Subscription::pkeyGet(array('subscriber' => $other->id, - 'subscribed' => $this->id)); - - if ($sub) { - $result = $sub->delete(); - if (!$result) { - common_log_db_error($sub, 'DELETE', __FILE__); - return false; - } - } + subs_unsubscribe_to($other->getUser(),$this->getProfile()); $block->query('COMMIT'); @@ -737,4 +833,163 @@ class User extends Memcached_DataObject $profile = $this->getProfile(); return $profile->isSilenced(); } + + function repeatedByMe($offset=0, $limit=20, $since_id=null, $max_id=null) + { + $ids = Notice::stream(array($this, '_repeatedByMeDirect'), + array(), + 'user:repeated_by_me:'.$this->id, + $offset, $limit, $since_id, $max_id, null); + + return Notice::getStreamByIds($ids); + } + + function _repeatedByMeDirect($offset, $limit, $since_id, $max_id, $since) + { + $notice = new Notice(); + + $notice->selectAdd(); // clears it + $notice->selectAdd('id'); + + $notice->profile_id = $this->id; + $notice->whereAdd('repeat_of IS NOT NULL'); + + $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 repeatsOfMe($offset=0, $limit=20, $since_id=null, $max_id=null) + { + $ids = Notice::stream(array($this, '_repeatsOfMeDirect'), + array(), + 'user:repeats_of_me:'.$this->id, + $offset, $limit, $since_id, $max_id, null); + + return Notice::getStreamByIds($ids); + } + + function _repeatsOfMeDirect($offset, $limit, $since_id, $max_id, $since) + { + $qry = + 'SELECT DISTINCT original.id AS id ' . + 'FROM notice original JOIN notice rept ON original.id = rept.repeat_of ' . + 'WHERE original.profile_id = ' . $this->id . ' '; + + if ($since_id != 0) { + $qry .= 'AND original.id > ' . $since_id . ' '; + } + + if ($max_id != 0) { + $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 '; + + if (!is_null($offset)) { + $qry .= "LIMIT $limit OFFSET $offset"; + } + + $ids = array(); + + $notice = new Notice(); + + $notice->query($qry); + + while ($notice->fetch()) { + $ids[] = $notice->id; + } + + $notice->free(); + $notice = NULL; + + return $ids; + } + + function repeatedToMe($offset=0, $limit=20, $since_id=null, $max_id=null) + { + $ids = Notice::stream(array($this, '_repeatedToMeDirect'), + array(), + 'user:repeated_to_me:'.$this->id, + $offset, $limit, $since_id, $max_id, null); + + return Notice::getStreamByIds($ids); + } + + function _repeatedToMeDirect($offset, $limit, $since_id, $max_id, $since) + { + $qry = + 'SELECT notice.id AS id ' . + 'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' . + 'WHERE notice_inbox.user_id = ' . $this->id . ' ' . + 'AND notice.repeat_of IS NOT NULL '; + + if ($since_id != 0) { + $qry .= 'AND notice.id > ' . $since_id . ' '; + } + + if ($max_id != 0) { + $qry .= 'AND notice.id <= ' . $max_id . ' '; + } + + if (!is_null($since)) { + $qry .= 'AND notice.modified > \'' . date('Y-m-d H:i:s', $since) . '\' '; + } + + // NOTE: we sort by fave time, not by notice time! + + $qry .= 'ORDER BY notice.id DESC '; + + if (!is_null($offset)) { + $qry .= "LIMIT $limit OFFSET $offset"; + } + + $ids = array(); + + $notice = new Notice(); + + $notice->query($qry); + + while ($notice->fetch()) { + $ids[] = $notice->id; + } + + $notice->free(); + $notice = NULL; + + return $ids; + } } diff --git a/classes/statusnet.ini b/classes/statusnet.ini index f12707ba1..2cc37dbfe 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -1,3 +1,4 @@ + [avatar] profile_id = 129 original = 17 @@ -306,6 +307,7 @@ lat = 1 lon = 1 location_id = 1 location_ns = 1 +repeat_of = 1 [notice__keys] id = N |