summaryrefslogtreecommitdiff
path: root/classes
diff options
context:
space:
mode:
authorCraig Andrews <candrews@integralblue.com>2010-02-24 20:52:45 -0500
committerCraig Andrews <candrews@integralblue.com>2010-02-24 20:52:45 -0500
commitc187bf55974347f7ddb4f28714af57861dce8f08 (patch)
tree4398b456d88ce79977959b25ba1a0f6fe0c1d77f /classes
parent20d6a7caed6636c28cc7b95c584549691dff4388 (diff)
parent8914b69d5055c1bc7d0604ee338ffdaf6b0a8606 (diff)
Merge branch '0.9.x' into 1.0.x
Conflicts: EVENTS.txt db/statusnet.sql lib/queuemanager.php
Diffstat (limited to 'classes')
-rwxr-xr-xclasses/Conversation.php78
-rw-r--r--classes/Fave.php44
-rw-r--r--classes/Memcached_DataObject.php6
-rw-r--r--classes/Notice.php316
-rw-r--r--classes/Profile.php80
-rw-r--r--classes/Subscription.php154
-rw-r--r--classes/User.php19
-rw-r--r--classes/User_group.php18
-rw-r--r--classes/statusnet.ini10
9 files changed, 571 insertions, 154 deletions
diff --git a/classes/Conversation.php b/classes/Conversation.php
new file mode 100755
index 000000000..ea8bd87b5
--- /dev/null
+++ b/classes/Conversation.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Data class for Conversations
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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/>.
+ *
+ * @category Data
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
+
+class Conversation extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'conversation'; // table name
+ public $id; // int(4) primary_key not_null
+ public $uri; // varchar(225) unique_key
+ public $created; // datetime not_null
+ public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('conversation',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ /**
+ * Factory method for creating a new conversation
+ *
+ * @return Conversation the new conversation DO
+ */
+ static function create()
+ {
+ $conv = new Conversation();
+ $conv->created = common_sql_now();
+ $id = $conv->insert();
+
+ if (empty($id)) {
+ common_log_db_error($conv, 'INSERT', __FILE__);
+ return null;
+ }
+
+ $orig = clone($conv);
+ $orig->uri = common_local_url('conversation', array('id' => $id));
+ $result = $orig->update($conv);
+
+ if (empty($result)) {
+ common_log_db_error($conv, 'UPDATE', __FILE__);
+ return null;
+ }
+
+ return $conv;
+ }
+
+}
+
diff --git a/classes/Fave.php b/classes/Fave.php
index 8113c8e16..0b6eec2bc 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);
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 73b22d58a..e8d5c45cb 100644
--- a/classes/Notice.php
+++ b/classes/Notice.php
@@ -187,13 +187,21 @@ 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
+ * @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 +264,16 @@ class Notice extends Memcached_DataObject
}
$notice->content = $final;
- $notice->rendered = common_render_content($final, $notice);
+
+ if (!empty($rendered)) {
+ $notice->rendered = $rendered;
+ } else {
+ $notice->rendered = common_render_content($final, $notice);
+ }
+
$notice->source = $source;
$notice->uri = $uri;
+ $notice->url = $url;
// Handle repeat case
@@ -309,7 +324,8 @@ class Notice extends Memcached_DataObject
// the beginning of a new conversation.
if (empty($notice->conversation)) {
- $notice->conversation = $notice->id;
+ $conv = Conversation::create();
+ $notice->conversation = $conv->id;
$changed = true;
}
@@ -324,21 +340,35 @@ 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();
+ if (isset($replies)) {
+ $notice->saveKnownReplies($replies);
+ } else {
+ $notice->saveReplies();
+ }
+
+ if (isset($groups)) {
+ $notice->saveKnownGroups($groups);
+ } else {
+ $notice->saveGroups();
+ }
+
$notice->distribute();
return $notice;
}
- function blowOnInsert()
+ function blowOnInsert($conversation = false)
{
self::blow('profile:notice_ids:%d', $this->profile_id);
self::blow('public');
- if ($this->conversation != $this->id) {
- self::blow('notice:conversation_ids:%d', $this->conversation);
- }
+ // XXX: Before we were blowing the casche only if the notice id
+ // was not the root of the conversation. What to do now?
+
+ self::blow('notice:conversation_ids:%d', $this->conversation);
if (!empty($this->repeat_of)) {
self::blow('notice:repeats:%d', $this->repeat_of);
@@ -675,11 +705,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;
}
@@ -712,6 +770,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()
@@ -795,8 +889,49 @@ 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();
+ }
+ }
+
+ 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
@@ -805,76 +940,44 @@ 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);
-
- $names = array();
-
- 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->profile_id, $this->content);
+
$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;
}
}
}
@@ -915,9 +1018,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()
{
@@ -940,7 +1044,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;
+ }
}
}
@@ -960,6 +1067,7 @@ 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:poco' => 'http://portablecontacts.net/spec/1.0',
'xmlns:ostatus' => 'http://ostatus.org/schema/1.0');
} else {
$attrs = array();
@@ -997,6 +1105,7 @@ class Notice extends Memcached_DataObject
$xs->raw($profile->asActivityActor());
$xs->element('link', array('rel' => 'alternate',
+ 'type' => 'text/html',
'href' => $this->bestUrl()));
$xs->element('id', null, $this->uri);
@@ -1015,33 +1124,45 @@ class Notice extends Memcached_DataObject
}
}
- if (!empty($this->conversation)
- && $this->conversation != $this->id) {
- $xs->element(
- 'link', array(
- 'rel' => 'ostatus:conversation',
- 'href' => common_local_url(
- 'conversation',
- array('id' => $this->conversation)
- )
+ if (!empty($this->conversation)) {
+
+ $conv = Conversation::staticGet('id', $this->conversation);
+
+ if (!empty($conv)) {
+ $xs->element(
+ 'link', array(
+ 'rel' => 'ostatus:conversation',
+ 'href' => $conv->uri
)
);
+ }
}
$reply_ids = $this->getReplies();
foreach ($reply_ids as $id) {
$profile = Profile::staticGet('id', $id);
- if (!empty($profile)) {
+ if (!empty($profile)) {
$xs->element(
'link', array(
'rel' => 'ostatus:attention',
- 'href' => $profile->getAcctUri()
+ 'href' => $profile->getUri()
)
);
}
}
+ $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)) {
@@ -1087,6 +1208,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)) {
@@ -1484,6 +1620,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/Profile.php b/classes/Profile.php
index ab05bb854..78223b34a 100644
--- a/classes/Profile.php
+++ b/classes/Profile.php
@@ -769,7 +769,7 @@ class Profile extends Memcached_DataObject
$xs->elementStart('author');
$xs->element('name', null, $this->nickname);
- $xs->element('uri', null, $this->profileurl);
+ $xs->element('uri', null, $this->getUri());
$xs->elementEnd('author');
return $xs->getString();
@@ -792,51 +792,59 @@ 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);
+ $noun = ActivityObject::fromProfile($this);
+ return $noun->asString('activity:' . $element);
+ }
- $xs->elementStart('activity:' . $element);
- $xs->element(
- 'activity:object-type',
- null,
- 'http://activitystrea.ms/schema/1.0/person'
- );
- $xs->element(
- 'id',
- null,
- common_local_url(
- 'userbyid',
- array('id' => $this->id)
- )
- );
- $xs->element('title', null, $this->getBestName());
-
- $avatar = $this->getAvatar(AVATAR_PROFILE_SIZE);
-
- $xs->element(
- 'link', array(
- 'type' => empty($avatar) ? 'image/png' : $avatar->mediatype,
- 'href' => empty($avatar)
- ? Avatar::defaultImage(AVATAR_PROFILE_SIZE)
- : $avatar->displayUrl()
- ),
- ''
- );
-
- $xs->elementEnd('activity:' . $element);
+ /**
+ * Returns the best URI for a profile. Plugins may override.
+ *
+ * @return string $uri
+ */
+ function getUri()
+ {
+ $uri = null;
- return $xs->getString();
+ // 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));
+ }
+
+ return $uri;
}
- function getAcctUri()
+ function hasBlocked($other)
{
- return $this->nickname . '@' . common_config('site', 'server');
- }
+ $block = Profile_block::get($this->id, $other->id);
+ if (empty($block)) {
+ $result = false;
+ } else {
+ $result = true;
+ }
+
+ return $result;
+ }
}
diff --git a/classes/Subscription.php b/classes/Subscription.php
index faf1331cd..d6fb3fcbd 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,155 @@ 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));
+
+ $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 1c967b527..0f84ed813 100644
--- a/classes/User.php
+++ b/classes/User.php
@@ -75,11 +75,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.
@@ -162,17 +158,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);
}
/**
diff --git a/classes/User_group.php b/classes/User_group.php
index 379e6b721..1382aa407 100644
--- a/classes/User_group.php
+++ b/classes/User_group.php
@@ -39,14 +39,24 @@ 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))) {
+ $url = common_local_url('showgroup',
+ array('nickname' => $this->nickname));
+ }
+ Event::handle('EndUserGroupHomeUrl', array($this, &$url));
+ return $url;
}
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)
diff --git a/classes/statusnet.ini b/classes/statusnet.ini
index bcff744bd..d116b16a3 100644
--- a/classes/statusnet.ini
+++ b/classes/statusnet.ini
@@ -47,6 +47,16 @@ modified = 384
[consumer__keys]
consumer_key = K
+[conversation]
+id = 129
+uri = 2
+created = 142
+modified = 384
+
+[conversation__keys]
+id = N
+uri = U
+
[deleted_notice]
id = 129
profile_id = 129