diff options
-rw-r--r-- | classes/Profile.php | 12 | ||||
-rw-r--r-- | classes/Subscription.php | 154 | ||||
-rw-r--r-- | classes/User.php | 19 | ||||
-rw-r--r-- | lib/subs.php | 112 | ||||
-rw-r--r-- | plugins/OStatus/actions/salmon.php | 1 | ||||
-rw-r--r-- | plugins/OStatus/classes/Ostatus_profile.php | 32 | ||||
-rw-r--r-- | plugins/OStatus/lib/activity.php | 85 | ||||
-rw-r--r-- | plugins/OStatus/tests/ActivityParseTests.php | 53 |
8 files changed, 308 insertions, 160 deletions
diff --git a/classes/Profile.php b/classes/Profile.php index 494c697e4..6b396c8c3 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -868,4 +868,16 @@ class Profile extends Memcached_DataObject 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/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 72c3f39e9..10b1f4865 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); } /** diff --git a/lib/subs.php b/lib/subs.php index 5ac1a75a5..5376e21bd 100644 --- a/lib/subs.php +++ b/lib/subs.php @@ -19,8 +19,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once('XMPPHP/XMPP.php'); - /* Subscribe $user to nickname $other_nickname Returns true or an error message. */ @@ -44,72 +42,12 @@ function subs_subscribe_user($user, $other_nickname) function subs_subscribe_to($user, $other) { - if (!$user->hasRight(Right::SUBSCRIBE)) { - return _('You have been banned from subscribing.'); - } - - if ($user->isSubscribed($other)) { - return _('Already subscribed!'); - } - - if ($other->hasBlocked($user)) { - return _('User has blocked you.'); - } - try { - if (Event::handle('StartSubscribe', array($user, $other))) { - - if (!$user->subscribeTo($other)) { - return _('Could not subscribe.'); - return; - } - - subs_notify($other, $user); - - $cache = common_memcache(); - - if ($cache) { - $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id)); - } - - $profile = $user->getProfile(); - - $profile->blowSubscriptionsCount(); - $other->blowSubscribersCount(); - - if ($other->autosubscribe && !$other->isSubscribed($user) && !$user->hasBlocked($other)) { - if (!$other->subscribeTo($user)) { - return _('Could not subscribe other to you.'); - } - $cache = common_memcache(); - - if ($cache) { - $cache->delete(common_cache_key('user:notices_with_friends:' . $other->id)); - } - - subs_notify($user, $other); - } - - Event::handle('EndSubscribe', array($user, $other)); - } + Subscription::start($user->getProfile(), $other); + return true; } catch (Exception $e) { return $e->getMessage(); } - - return true; -} - -function subs_notify($listenee, $listener) -{ - # XXX: add other notifications (Jabber, SMS) here - # XXX: queue this and handle it offline - # XXX: Whatever happens, do it in Twitter-like API, too - subs_notify_email($listenee, $listener); -} - -function subs_notify_email($listenee, $listener) -{ - mail_subscribe_notify($listenee, $listener); } /* Unsubscribe $user from nickname $other_nickname @@ -128,52 +66,12 @@ function subs_unsubscribe_user($user, $other_nickname) return subs_unsubscribe_to($user, $other->getProfile()); } -/* Unsubscribe user $user from profile $other - * NB: other can be a remote user. */ - function subs_unsubscribe_to($user, $other) { - if (!$user->isSubscribed($other)) - return _('Not subscribed!'); - - // Don't allow deleting self subs - - if ($user->id == $other->id) { - return _('Couldn\'t delete self-subscription.'); - } - try { - if (Event::handle('StartUnsubscribe', array($user, $other))) { - - $sub = DB_DataObject::factory('subscription'); - - $sub->subscriber = $user->id; - $sub->subscribed = $other->id; - - $sub->find(true); - - // note we checked for existence above - - if (!$sub->delete()) - return _('Couldn\'t delete subscription.'); - - $cache = common_memcache(); - - if ($cache) { - $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id)); - } - - $profile = $user->getProfile(); - - $profile->blowSubscriptionsCount(); - $other->blowSubscribersCount(); - - Event::handle('EndUnsubscribe', array($user, $other)); - } + Subscription::cancel($user->getProfile(), $other); + return true; } catch (Exception $e) { return $e->getMessage(); } - - return true; -} - +}
\ No newline at end of file diff --git a/plugins/OStatus/actions/salmon.php b/plugins/OStatus/actions/salmon.php index ea5b8e4ea..e9d6015f4 100644 --- a/plugins/OStatus/actions/salmon.php +++ b/plugins/OStatus/actions/salmon.php @@ -71,6 +71,7 @@ class SalmonAction extends Action /** * @fixme probably call Ostatus_profile::processFeed */ + function handle($args) { common_log(LOG_INFO, 'Salmon: incoming post for user '. $this->user->id); diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 1ce8ac491..e0cb467e5 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -489,36 +489,6 @@ class Ostatus_profile extends Memcached_DataObject } /** - * Parse location given as a GeoRSS-simple point, if provided. - * http://www.georss.org/simple - * - * @param feed item $entry - * @return mixed Location or false - */ - function getLocation($dom) - { - $points = $dom->getElementsByTagNameNS('http://www.georss.org/georss', 'point'); - - for ($i = 0; $i < $points->length; $i++) { - $point = $points->item(0)->textContent; - $point = str_replace(',', ' ', $point); // per spec "treat commas as whitespace" - $point = preg_replace('/\s+/', ' ', $point); - $point = trim($point); - $coords = explode(' ', $point); - if (count($coords) == 2) { - list($lat, $lon) = $coords; - if (is_numeric($lat) && is_numeric($lon)) { - common_log(LOG_INFO, "Looking up location for $lat $lon from georss"); - return Location::fromLatLon($lat, $lon); - } - } - common_log(LOG_ERR, "Ignoring bogus georss:point value $point"); - } - - return false; - } - - /** * @param string $profile_url * @return Ostatus_profile * @throws FeedSubException @@ -560,7 +530,7 @@ class Ostatus_profile extends Memcached_DataObject // ripped from oauthstore.php (for old OMB client) $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar'); copy($url, $temp_filename); - + // @fixme should we be using different ids? $imagefile = new ImageFile($this->id, $temp_filename); $filename = Avatar::filename($this->id, diff --git a/plugins/OStatus/lib/activity.php b/plugins/OStatus/lib/activity.php index 3ed613dc7..5bc8f78e5 100644 --- a/plugins/OStatus/lib/activity.php +++ b/plugins/OStatus/lib/activity.php @@ -311,6 +311,87 @@ class ActivityVerb const LEAVE = 'http://ostatus.org/schema/1.0/leave'; } +class ActivityContext +{ + public $replyToID; + public $replyToUrl; + public $location; + public $attention = array(); + public $conversation; + + const THR = 'http://purl.org/syndication/thread/1.0'; + const GEORSS = 'http://www.georss.org/georss'; + const OSTATUS = 'http://ostatus.org/schema/1.0'; + + const INREPLYTO = 'in-reply-to'; + const REF = 'ref'; + const HREF = 'href'; + + const POINT = 'point'; + + const ATTENTION = 'ostatus:attention'; + const CONVERSATION = 'ostatus:conversation'; + + function __construct($element) + { + $replyToEl = ActivityUtils::child($element, self::INREPLYTO, self::THR); + + if (!empty($replyToEl)) { + $this->replyToID = $replyToEl->getAttribute(self::REF); + $this->replyToUrl = $replyToEl->getAttribute(self::HREF); + } + + $this->location = $this->getLocation($element); + + $this->conversation = ActivityUtils::getLink($element, self::CONVERSATION); + + // Multiple attention links allowed + + $links = $element->getElementsByTagNameNS(ActivityUtils::ATOM, ActivityUtils::LINK); + + for ($i = 0; $i < $links->length; $i++) { + + $link = $links->item($i); + + $linkRel = $link->getAttribute(ActivityUtils::REL); + + if ($linkRel == self::ATTENTION) { + $this->attention[] = $link->getAttribute(self::HREF); + } + } + } + + /** + * Parse location given as a GeoRSS-simple point, if provided. + * http://www.georss.org/simple + * + * @param feed item $entry + * @return mixed Location or false + */ + function getLocation($dom) + { + $points = $dom->getElementsByTagNameNS(self::GEORSS, self::POINT); + + for ($i = 0; $i < $points->length; $i++) { + $point = $points->item($i)->textContent; + $point = str_replace(',', ' ', $point); // per spec "treat commas as whitespace" + $point = preg_replace('/\s+/', ' ', $point); + $point = trim($point); + $coords = explode(' ', $point); + if (count($coords) == 2) { + list($lat, $lon) = $coords; + if (is_numeric($lat) && is_numeric($lon)) { + common_log(LOG_INFO, "Looking up location for $lat $lon from georss"); + return Location::fromLatLon($lat, $lon); + } + } + common_log(LOG_ERR, "Ignoring bogus georss:point value $point"); + } + + return null; + } +} + /** * An activity in the ActivityStrea.ms world * @@ -426,7 +507,9 @@ class Activity $contextEl = $this->_child($entry, self::CONTEXT); if (!empty($contextEl)) { - $this->context = new ActivityObject($contextEl); + $this->context = new ActivityContext($contextEl); + } else { + $this->context = new ActivityContext($entry); } $targetEl = $this->_child($entry, self::TARGET); diff --git a/plugins/OStatus/tests/ActivityParseTests.php b/plugins/OStatus/tests/ActivityParseTests.php index fa8bcdda2..35b4b0f9d 100644 --- a/plugins/OStatus/tests/ActivityParseTests.php +++ b/plugins/OStatus/tests/ActivityParseTests.php @@ -57,12 +57,35 @@ class ActivityParseTests extends PHPUnit_Framework_TestCase $this->assertEquals($act->object->summary, 'Some text.'); $this->assertEquals($act->object->link, 'http://example.org/2003/12/13/atom03.html'); - $this->assertTrue(empty($act->context)); + $this->assertFalse(empty($act->context)); + $this->assertTrue(empty($act->target)); $this->assertEquals($act->entry, $entry); $this->assertEquals($act->feed, $feed); } + + public function testExample4() + { + global $_example4; + $dom = DOMDocument::loadXML($_example4); + + $entry = $dom->documentElement; + + $act = new Activity($entry); + + $this->assertFalse(empty($act)); + $this->assertEquals(1266547958, $act->time); + $this->assertEquals('http://example.net/notice/14', $act->link); + + $this->assertFalse(empty($act->context)); + $this->assertEquals('http://example.net/notice/12', $act->context->replyToID); + $this->assertEquals('http://example.net/notice/12', $act->context->replyToUrl); + $this->assertEquals('http://example.net/conversation/11', $act->context->conversation); + $this->assertEquals(array('http://example.net/user/1'), $act->context->attention); + + $this->assertFalse(empty($act->actor)); + } } $_example1 = <<<EXAMPLE1 @@ -145,3 +168,31 @@ $_example3 = <<<EXAMPLE3 </feed> EXAMPLE3; + +$_example4 = <<<EXAMPLE4 +<?xml version='1.0' encoding='UTF-8'?> +<entry xmlns="http://www.w3.org/2005/Atom" 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:ostatus="http://ostatus.org/schema/1.0"> + <title>@evan now is the time for all good men to come to the aid of their country. #thetime</title> + <summary>@evan now is the time for all good men to come to the aid of their country. #thetime</summary> +<author> + <name>spock</name> + <uri>http://example.net/user/2</uri> +</author> +<activity:actor> + <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type> + <id>http://example.net/user/2</id> + <title>spock</title> + <link type="image/png" rel="avatar" href="http://example.net/theme/identica/default-avatar-profile.png"></link> +</activity:actor> + <link rel="alternate" type="text/html" href="http://example.net/notice/14"/> + <id>http://example.net/notice/14</id> + <published>2010-02-19T02:52:38+00:00</published> + <updated>2010-02-19T02:52:38+00:00</updated> + <link rel="related" href="http://example.net/notice/12"/> + <thr:in-reply-to ref="http://example.net/notice/12" href="http://example.net/notice/12"></thr:in-reply-to> + <link rel="ostatus:conversation" href="http://example.net/conversation/11"/> + <link rel="ostatus:attention" href="http://example.net/user/1"/> + <content type="html">@<span class="vcard"><a href="http://example.net/user/1" class="url"><span class="fn nickname">evan</span></a></span> now is the time for all good men to come to the aid of their country. #<span class="tag"><a href="http://example.net/tag/thetime" rel="tag">thetime</a></span></content> + <category term="thetime"></category> +</entry> +EXAMPLE4; |