From 905aded81a8a001fefc211981c435c5c21bb3a99 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 7 Jan 2011 19:48:50 -0500 Subject: [PATCH] move account-moving classes to their own libraries --- lib/accountmover.php | 171 +++++++++++++++++++++++++++++++ lib/activitysink.php | 155 ++++++++++++++++++++++++++++ scripts/moveuser.php | 235 ------------------------------------------- 3 files changed, 326 insertions(+), 235 deletions(-) create mode 100644 lib/accountmover.php create mode 100644 lib/activitysink.php diff --git a/lib/accountmover.php b/lib/accountmover.php new file mode 100644 index 0000000000..ba9da0f6fd --- /dev/null +++ b/lib/accountmover.php @@ -0,0 +1,171 @@ +. + * + * @category Account + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Moves an account from this server to another + * + * @category Account + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class AccountMover +{ + private $_user = null; + private $_profile = null; + private $_remote = null; + private $_sink = null; + + function __construct($user, $remote, $password) + { + $this->_user = $user; + $this->_profile = $user->getProfile(); + + $oprofile = Ostatus_profile::ensureProfileURI($remote); + + if (empty($oprofile)) { + throw new Exception("Can't locate account {$remote}"); + } + + $this->_remote = $oprofile->localProfile(); + + list($svcDocUrl, $username) = self::getServiceDocument($remote); + + $this->_sink = new ActivitySink($svcDocUrl, $username, $password); + } + + static function getServiceDocument($remote) + { + $discovery = new Discovery(); + + $xrd = $discovery->lookup($remote); + + if (empty($xrd)) { + throw new Exception("Can't find XRD for $remote"); + } + + $svcDocUrl = null; + $username = null; + + foreach ($xrd->links as $link) { + if ($link['rel'] == 'http://apinamespace.org/atom' && + $link['type'] == 'application/atomsvc+xml') { + $svcDocUrl = $link['href']; + if (!empty($link['property'])) { + foreach ($link['property'] as $property) { + if ($property['type'] == 'http://apinamespace.org/atom/username') { + $username = $property['value']; + break; + } + } + } + break; + } + } + + if (empty($svcDocUrl)) { + throw new Exception("No AtomPub API service for $remote."); + } + + return array($svcDocUrl, $username); + } + + function move() + { + $stream = new UserActivityStream($this->_user); + + $acts = array_reverse($stream->activities); + + // Reverse activities to run in correct chron order + + foreach ($acts as $act) { + $this->_moveActivity($act); + } + } + + private function _moveActivity($act) + { + switch ($act->verb) { + case ActivityVerb::FAVORITE: + // push it, then delete local + $this->_sink->postActivity($act); + $notice = Notice::staticGet('uri', $act->objects[0]->id); + if (!empty($notice)) { + $fave = Fave::pkeyGet(array('user_id' => $this->_user->id, + 'notice_id' => $notice->id)); + $fave->delete(); + } + break; + case ActivityVerb::POST: + // XXX: send a reshare, not a post + common_log(LOG_INFO, "Pushing notice {$act->objects[0]->id} to {$this->_remote->getURI()}"); + $this->_sink->postActivity($act); + $notice = Notice::staticGet('uri', $act->objects[0]->id); + if (!empty($notice)) { + $notice->delete(); + } + break; + case ActivityVerb::JOIN: + $this->_sink->postActivity($act); + $group = User_group::staticGet('uri', $act->objects[0]->id); + if (!empty($group)) { + Group_member::leave($group->id, $this->_user->id); + } + break; + case ActivityVerb::FOLLOW: + if ($act->actor->id == $this->_user->uri) { + $this->_sink->postActivity($act); + $other = Profile::fromURI($act->objects[0]->id); + if (!empty($other)) { + Subscription::cancel($this->_profile, $other); + } + } else { + $otherUser = User::staticGet('uri', $act->actor->id); + if (!empty($otherUser)) { + $otherProfile = $otherUser->getProfile(); + Subscription::start($otherProfile, $this->_remote); + Subscription::cancel($otherProfile, $this->_user->getProfile()); + } else { + // It's a remote subscription. Do something here! + } + } + break; + } + } +} diff --git a/lib/activitysink.php b/lib/activitysink.php new file mode 100644 index 0000000000..287fd8f0ab --- /dev/null +++ b/lib/activitysink.php @@ -0,0 +1,155 @@ +. + * + * @category AtomPub + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * A remote service that supports AtomPub + * + * @category AtomPub + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class ActivitySink +{ + protected $svcDocUrl = null; + protected $username = null; + protected $password = null; + protected $collections = array(); + + function __construct($svcDocUrl, $username, $password) + { + $this->svcDocUrl = $svcDocUrl; + $this->username = $username; + $this->password = $password; + + $this->_parseSvcDoc(); + } + + private function _parseSvcDoc() + { + $client = new HTTPClient(); + $response = $client->get($this->svcDocUrl); + + if ($response->getStatus() != 200) { + throw new Exception("Can't get {$this->svcDocUrl}; response status " . $response->getStatus()); + } + + $xml = $response->getBody(); + + $dom = new DOMDocument(); + + // We don't want to bother with white spaces + $dom->preserveWhiteSpace = false; + + // Don't spew XML warnings to output + $old = error_reporting(); + error_reporting($old & ~E_WARNING); + $ok = $dom->loadXML($xml); + error_reporting($old); + + $path = new DOMXPath($dom); + + $path->registerNamespace('atom', 'http://www.w3.org/2005/Atom'); + $path->registerNamespace('app', 'http://www.w3.org/2007/app'); + $path->registerNamespace('activity', 'http://activitystrea.ms/spec/1.0/'); + + $collections = $path->query('//app:collection'); + + for ($i = 0; $i < $collections->length; $i++) { + $collection = $collections->item($i); + $url = $collection->getAttribute('href'); + $takesEntries = false; + $accepts = $path->query('app:accept', $collection); + for ($j = 0; $j < $accepts->length; $j++) { + $accept = $accepts->item($j); + $acceptValue = $accept->nodeValue; + if (preg_match('#application/atom\+xml(;\s*type=entry)?#', $acceptValue)) { + $takesEntries = true; + break; + } + } + if (!$takesEntries) { + continue; + } + $verbs = $path->query('activity:verb', $collection); + if ($verbs->length == 0) { + $this->_addCollection(ActivityVerb::POST, $url); + } else { + for ($k = 0; $k < $verbs->length; $k++) { + $verb = $verbs->item($k); + $this->_addCollection($verb->nodeValue, $url); + } + } + } + } + + private function _addCollection($verb, $url) + { + if (array_key_exists($verb, $this->collections)) { + $this->collections[$verb][] = $url; + } else { + $this->collections[$verb] = array($url); + } + return; + } + + function postActivity($activity) + { + if (!array_key_exists($activity->verb, $this->collections)) { + throw new Exception("No collection for verb {$activity->verb}"); + } else { + if (count($this->collections[$activity->verb]) > 1) { + common_log(LOG_NOTICE, "More than one collection for verb {$activity->verb}"); + } + $this->postToCollection($this->collections[$activity->verb][0], $activity); + } + } + + function postToCollection($url, $activity) + { + $client = new HTTPClient($url); + + $client->setMethod('POST'); + $client->setAuth($this->username, $this->password); + $client->setHeader('Content-Type', 'application/atom+xml;type=entry'); + $client->setBody($activity->asString(true, true, true)); + + $response = $client->send(); + } +} diff --git a/scripts/moveuser.php b/scripts/moveuser.php index feaca15103..b02b10b1e5 100644 --- a/scripts/moveuser.php +++ b/scripts/moveuser.php @@ -37,241 +37,6 @@ an HTTP or HTTPS URL (http://example.com/social/site/user/nickname). END_OF_MOVEUSER_HELP; require_once INSTALLDIR.'/scripts/commandline.inc'; -require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php'; - -class ActivitySink -{ - protected $svcDocUrl = null; - protected $username = null; - protected $password = null; - protected $collections = array(); - - function __construct($svcDocUrl, $username, $password) - { - $this->svcDocUrl = $svcDocUrl; - $this->username = $username; - $this->password = $password; - - $this->_parseSvcDoc(); - } - - private function _parseSvcDoc() - { - $client = new HTTPClient(); - $response = $client->get($this->svcDocUrl); - - if ($response->getStatus() != 200) { - throw new Exception("Can't get {$this->svcDocUrl}; response status " . $response->getStatus()); - } - - $xml = $response->getBody(); - - $dom = new DOMDocument(); - - // We don't want to bother with white spaces - $dom->preserveWhiteSpace = false; - - // Don't spew XML warnings to output - $old = error_reporting(); - error_reporting($old & ~E_WARNING); - $ok = $dom->loadXML($xml); - error_reporting($old); - - $path = new DOMXPath($dom); - - $path->registerNamespace('atom', 'http://www.w3.org/2005/Atom'); - $path->registerNamespace('app', 'http://www.w3.org/2007/app'); - $path->registerNamespace('activity', 'http://activitystrea.ms/spec/1.0/'); - - $collections = $path->query('//app:collection'); - - for ($i = 0; $i < $collections->length; $i++) { - $collection = $collections->item($i); - $url = $collection->getAttribute('href'); - $takesEntries = false; - $accepts = $path->query('app:accept', $collection); - for ($j = 0; $j < $accepts->length; $j++) { - $accept = $accepts->item($j); - $acceptValue = $accept->nodeValue; - if (preg_match('#application/atom\+xml(;\s*type=entry)?#', $acceptValue)) { - $takesEntries = true; - break; - } - } - if (!$takesEntries) { - continue; - } - $verbs = $path->query('activity:verb', $collection); - if ($verbs->length == 0) { - $this->_addCollection(ActivityVerb::POST, $url); - } else { - for ($k = 0; $k < $verbs->length; $k++) { - $verb = $verbs->item($k); - $this->_addCollection($verb->nodeValue, $url); - } - } - } - } - - private function _addCollection($verb, $url) - { - if (array_key_exists($verb, $this->collections)) { - $this->collections[$verb][] = $url; - } else { - $this->collections[$verb] = array($url); - } - return; - } - - function postActivity($activity) - { - if (!array_key_exists($activity->verb, $this->collections)) { - throw new Exception("No collection for verb {$activity->verb}"); - } else { - if (count($this->collections[$activity->verb]) > 1) { - common_log(LOG_NOTICE, "More than one collection for verb {$activity->verb}"); - } - $this->postToCollection($this->collections[$activity->verb][0], $activity); - } - } - - function postToCollection($url, $activity) - { - $client = new HTTPClient($url); - - $client->setMethod('POST'); - $client->setAuth($this->username, $this->password); - $client->setHeader('Content-Type', 'application/atom+xml;type=entry'); - $client->setBody($activity->asString(true, true, true)); - - $response = $client->send(); - } -} - -function getServiceDocument($remote) -{ - $discovery = new Discovery(); - - $xrd = $discovery->lookup($remote); - - if (empty($xrd)) { - throw new Exception("Can't find XRD for $remote"); - } - - $svcDocUrl = null; - $username = null; - - foreach ($xrd->links as $link) { - if ($link['rel'] == 'http://apinamespace.org/atom' && - $link['type'] == 'application/atomsvc+xml') { - $svcDocUrl = $link['href']; - if (!empty($link['property'])) { - foreach ($link['property'] as $property) { - if ($property['type'] == 'http://apinamespace.org/atom/username') { - $username = $property['value']; - break; - } - } - } - break; - } - } - - if (empty($svcDocUrl)) { - throw new Exception("No AtomPub API service for $remote."); - } - - return array($svcDocUrl, $username); -} - -class AccountMover -{ - private $_user = null; - private $_profile = null; - private $_remote = null; - private $_sink = null; - - function __construct($user, $remote, $password) - { - $this->_user = $user; - $this->_profile = $user->getProfile(); - - $oprofile = Ostatus_profile::ensureProfileURI($remote); - - if (empty($oprofile)) { - throw new Exception("Can't locate account {$remote}"); - } - - $this->_remote = $oprofile->localProfile(); - - list($svcDocUrl, $username) = getServiceDocument($remote); - - $this->_sink = new ActivitySink($svcDocUrl, $username, $password); - } - - function move() - { - $stream = new UserActivityStream($this->_user); - - $acts = array_reverse($stream->activities); - - // Reverse activities to run in correct chron order - - foreach ($acts as $act) { - $this->_moveActivity($act); - } - } - - private function _moveActivity($act) - { - switch ($act->verb) { - case ActivityVerb::FAVORITE: - // push it, then delete local - $this->_sink->postActivity($act); - $notice = Notice::staticGet('uri', $act->objects[0]->id); - if (!empty($notice)) { - $fave = Fave::pkeyGet(array('user_id' => $this->_user->id, - 'notice_id' => $notice->id)); - $fave->delete(); - } - break; - case ActivityVerb::POST: - // XXX: send a reshare, not a post - common_log(LOG_INFO, "Pushing notice {$act->objects[0]->id} to {$this->_remote->getURI()}"); - $this->_sink->postActivity($act); - $notice = Notice::staticGet('uri', $act->objects[0]->id); - if (!empty($notice)) { - $notice->delete(); - } - break; - case ActivityVerb::JOIN: - $this->_sink->postActivity($act); - $group = User_group::staticGet('uri', $act->objects[0]->id); - if (!empty($group)) { - Group_member::leave($group->id, $this->_user->id); - } - break; - case ActivityVerb::FOLLOW: - if ($act->actor->id == $this->_user->uri) { - $this->_sink->postActivity($act); - $other = Profile::fromURI($act->objects[0]->id); - if (!empty($other)) { - Subscription::cancel($this->_profile, $other); - } - } else { - $otherUser = User::staticGet('uri', $act->actor->id); - if (!empty($otherUser)) { - $otherProfile = $otherUser->getProfile(); - Subscription::start($otherProfile, $this->_remote); - Subscription::cancel($otherProfile, $this->_user->getProfile()); - } else { - // It's a remote subscription. Do something here! - } - } - break; - } - } -} try {