summaryrefslogtreecommitdiff
path: root/classes
diff options
context:
space:
mode:
Diffstat (limited to 'classes')
-rw-r--r--classes/Design.php2
-rw-r--r--classes/Login_token.php55
-rw-r--r--classes/Memcached_DataObject.php14
-rw-r--r--classes/Message.php8
-rw-r--r--classes/Notice.php456
-rw-r--r--classes/Notice_inbox.php42
-rw-r--r--classes/Profile.php11
-rw-r--r--classes/User.php277
-rw-r--r--classes/statusnet.ini2
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