public static function fromUrl($url, $depth = 0) { common_debug('MentionURL: trying to find a profile for ' . $url); $url = preg_replace('#https?://#', 'https://', $url); try { $profile = Profile::fromUri($url); } catch (UnknownUriException $ex) { } if (!$profile instanceof Profile) { $profile = self::findProfileByProfileURL($url); } $url = str_replace('https://', 'http://', $url); if (!$profile instanceof Profile) { try { $profile = Profile::fromUri($url); } catch (UnknownUriException $ex) { } } if (!$profile instanceof Profile) { $profile = self::findProfileByProfileURL($url); } if (!$profile instanceof Profile) { $hcard = mention_url_representative_hcard($url); if (!$hcard) { return null; } $mention_profile = new Mention_url_profile(); $mention_profile->query('BEGIN'); $profile = new Profile(); $profile->profileurl = $hcard['url'][0]; $profile->fullname = $hcard['name'][0]; preg_match('/\\/([^\\/]+)\\/*$/', $profile->profileurl, $matches); if (!$hcard['nickname']) { $hcard['nickname'] = array($matches[1]); } $profile->nickname = $hcard['nickname'][0]; $profile->created = common_sql_now(); $mention_profile->profile_id = $profile->insert(); if (!$mention_profile->profile_id) { $mention_profile->query('ROLLBACK'); return null; } $mention_profile->profileurl = $profile->profileurl; if (!$mention_profile->insert()) { $mention_profile->query('ROLLBACK'); if ($depth > 0) { return null; } else { return self::fromUrl($url, $depth + 1); } } else { $mention_profile->query('COMMIT'); } } return $profile; }
protected function doPreparation() { $id = $this->trimmed('id'); $uri = $this->trimmed('uri'); if (!empty($id)) { $this->target = Profile::getKV('id', $id); if (!$this->target instanceof Profile) { // TRANS: Client error displayed when referring to non-existing profile ID. $this->clientError(_('No profile with that ID.')); } } elseif (!empty($uri)) { $this->target = Profile::fromUri($uri); } else { // TRANS: Client error displayed when trying to tag a user but no ID or profile is provided. $this->clientError(_('No profile identifier provided.')); } if (!$this->scoped->canTag($this->target)) { // TRANS: Client error displayed when trying to tag a user that cannot be tagged. $this->clientError(_('You cannot tag this user.')); } $this->formOpts = $this->target; return true; }
function subscribeProfile($user, $author, $activity) { $profile = $user->getProfile(); if ($activity->objects[0]->id == $author->id) { if (!$this->trusted) { // TRANS: Client exception thrown when trying to force a subscription for an untrusted user. throw new ClientException(_('Cannot force subscription for untrusted user.')); } $other = $activity->actor; $otherUser = User::getKV('uri', $other->id); if (!$otherUser instanceof User) { // TRANS: Client exception thrown when trying to force a remote user to subscribe. throw new Exception(_('Cannot force remote user to subscribe.')); } $otherProfile = $otherUser->getProfile(); // XXX: don't do this for untrusted input! Subscription::ensureStart($otherProfile, $profile); } else { if (empty($activity->actor) || $activity->actor->id == $author->id) { $other = $activity->objects[0]; try { $otherProfile = Profile::fromUri($other->id); // TRANS: Client exception thrown when trying to subscribe to an unknown profile. } catch (UnknownUriException $e) { // Let's convert it to a client exception instead of server. throw new ClientException(_('Unknown profile.')); } Subscription::ensureStart($profile, $otherProfile); } else { // TRANS: Client exception thrown when trying to import an event not related to the importing user. throw new Exception(_('This activity seems unrelated to our user.')); } } }
/** * Add a new subscription * * Handling the POST method for AtomPub * * @return void */ function addSubscription() { if (empty($this->auth_user) || $this->auth_user->id != $this->_profile->id) { // TRANS: Client exception thrown when trying to subscribe another user. throw new ClientException(_("Cannot add someone else's" . " subscription."), 403); } $xml = file_get_contents('php://input'); $dom = DOMDocument::loadXML($xml); if ($dom->documentElement->namespaceURI != Activity::ATOM || $dom->documentElement->localName != 'entry') { // TRANS: Client error displayed when not using an Atom entry. $this->clientError(_('Atom post must be an Atom entry.')); } $activity = new Activity($dom->documentElement); $sub = null; if (Event::handle('StartAtomPubNewActivity', array(&$activity))) { if ($activity->verb != ActivityVerb::FOLLOW) { // TRANS: Client error displayed when not using the follow verb. $this->clientError(_('Can only handle Follow activities.')); } $person = $activity->objects[0]; if ($person->type != ActivityObject::PERSON) { // TRANS: Client exception thrown when subscribing to an object that is not a person. $this->clientError(_('Can only follow people.')); } // XXX: OStatus discovery (maybe) try { $profile = Profile::fromUri($person->id); } catch (UnknownUriException $e) { // TRANS: Client exception thrown when subscribing to a non-existing profile. // TRANS: %s is the unknown profile ID. $this->clientError(sprintf(_('Unknown profile %s.'), $person->id)); } try { $sub = Subscription::start($this->_profile, $profile); } catch (AlreadyFulfilledException $e) { // 409 Conflict $this->clientError($e->getMessage(), 409); } Event::handle('EndAtomPubNewActivity', array($activity, $sub)); } if (!empty($sub)) { $act = $sub->asActivity(); header('Content-Type: application/atom+xml; charset=utf-8'); header('Content-Location: ' . $act->selfLink); $this->startXML(); $this->raw($act->asString(true, true, true)); $this->endXML(); } }
$notice_id = get_option_value('--notice'); $notice = Notice::getKV('id', $notice_id); $profile = $notice->getProfile(); $entry = $notice->asAtomEntry(true); echo "== Original entry ==\n\n"; print $entry; print "\n\n"; $magic_env = MagicEnvelope::signAsUser($entry, $profile->getUser()); $envxml = $magic_env->toXML(); echo "== Signed envelope ==\n\n"; print $envxml; print "\n\n"; echo "== Testing local verification ==\n\n"; $magic_env = new MagicEnvelope($envxml); $activity = new Activity($magic_env->getPayload()->documentElement); $actprofile = Profile::fromUri($activity->actor->id); $ok = $magic_env->verify($actprofile); if ($ok) { print "OK\n\n"; } else { print "FAIL\n\n"; } if (have_option('--verify')) { $url = 'http://www.madebymonsieur.com/ostatus_discovery/magic_env/validate/'; echo "== Testing remote verification ==\n\n"; print "Sending for verification to {$url} ...\n"; $client = new HTTPClient(); $response = $client->post($url, array(), array('magic_env' => $envxml)); print $response->getStatus() . "\n\n"; print $response->getBody() . "\n\n"; }
function postNote($activity) { $note = $activity->objects[0]; // Use summary as fallback for content if (!empty($note->content)) { $sourceContent = $note->content; } else { if (!empty($note->summary)) { $sourceContent = $note->summary; } else { if (!empty($note->title)) { $sourceContent = $note->title; } else { // @fixme fetch from $sourceUrl? // TRANS: Client error displayed when posting a notice without content through the API. // TRANS: %d is the notice ID (number). $this->clientError(sprintf(_('No content for notice %d.'), $note->id)); } } } // Get (safe!) HTML and text versions of the content $rendered = common_purify($sourceContent); $content = common_strip_html($rendered); $shortened = $this->auth_user->shortenLinks($content); $options = array('is_local' => Notice::LOCAL_PUBLIC, 'rendered' => $rendered, 'replies' => array(), 'groups' => array(), 'tags' => array(), 'urls' => array()); // accept remote URI (not necessarily a good idea) common_debug("Note ID is {$note->id}"); if (!empty($note->id)) { $notice = Notice::getKV('uri', trim($note->id)); if (!empty($notice)) { // TRANS: Client error displayed when using another format than AtomPub. // TRANS: %s is the notice URI. $this->clientError(sprintf(_('Notice with URI "%s" already exists.'), $note->id)); } common_log(LOG_NOTICE, "Saving client-supplied notice URI '{$note->id}'"); $options['uri'] = $note->id; } // accept remote create time (also maybe not such a good idea) if (!empty($activity->time)) { common_log(LOG_NOTICE, "Saving client-supplied create time {$activity->time}"); $options['created'] = common_sql_date($activity->time); } // Check for optional attributes... if ($activity->context instanceof ActivityContext) { foreach ($activity->context->attention as $uri => $type) { try { $profile = Profile::fromUri($uri); if ($profile->isGroup()) { $options['groups'][] = $profile->id; } else { $options['replies'][] = $uri; } } catch (UnknownUriException $e) { common_log(LOG_WARNING, sprintf('AtomPub post with unknown attention URI %s', $uri)); } } // Maintain direct reply associations // @fixme what about conversation ID? if (!empty($activity->context->replyToID)) { $orig = Notice::getKV('uri', $activity->context->replyToID); if (!empty($orig)) { $options['reply_to'] = $orig->id; } } $location = $activity->context->location; if ($location) { $options['lat'] = $location->lat; $options['lon'] = $location->lon; if ($location->location_id) { $options['location_ns'] = $location->location_ns; $options['location_id'] = $location->location_id; } } } // Atom categories <-> hashtags foreach ($activity->categories as $cat) { if ($cat->term) { $term = common_canonical_tag($cat->term); if ($term) { $options['tags'][] = $term; } } } // Atom enclosures -> attachment URLs foreach ($activity->enclosures as $href) { // @fixme save these locally or....? $options['urls'][] = $href; } $saved = Notice::saveNew($this->target->id, $content, 'atompub', $options); return $saved; }
/** * 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. * * Mail notifications etc will be handled later. * * @param array $uris Array of unique identifier URIs for recipients */ function saveKnownReplies(array $uris) { if (empty($uris)) { return; } $sender = $this->getProfile(); foreach (array_unique($uris) as $uri) { try { $profile = Profile::fromUri($uri); } catch (UnknownUriException $e) { common_log(LOG_WARNING, "Unable to determine profile for URI '{$uri}'"); continue; } if ($profile->hasBlocked($sender)) { common_log(LOG_INFO, "Not saving reply to profile {$profile->id} ({$uri}) from sender {$sender->id} because of a block."); continue; } $this->saveReply($profile->getID()); self::blow('reply:stream:%d', $profile->getID()); } }
function subscribeProfile($user, $author, $activity) { $profile = $user->getProfile(); if ($activity->objects[0]->id == $author->id) { if (!$this->trusted) { // TRANS: Client exception thrown when trying to force a subscription for an untrusted user. throw new ClientException(_("Cannot force subscription for untrusted user.")); } $other = $activity->actor; $otherUser = User::staticGet('uri', $other->id); if (!empty($otherUser)) { $otherProfile = $otherUser->getProfile(); } else { // TRANS: Client exception thrown when trying to for a remote user to subscribe. throw new Exception(_("Cannot force remote user to subscribe.")); } // XXX: don't do this for untrusted input! Subscription::start($otherProfile, $profile); } else { if (empty($activity->actor) || $activity->actor->id == $author->id) { $other = $activity->objects[0]; $otherProfile = Profile::fromUri($other->id); if (empty($otherProfile)) { // TRANS: Client exception thrown when trying to subscribe to an unknown profile. throw new ClientException(_("Unknown profile.")); } Subscription::start($profile, $otherProfile); } else { // TRANS: Client exception thrown when trying to import an event not related to the importing user. throw new Exception(_("This activity seems unrelated to our user.")); } } }
function moveActivity($act, $sink, $user, $remote) { if (empty($user)) { // TRANS: Exception thrown if a non-existing user is provided. %s is a user ID. throw new Exception(sprintf(_('No such user "%s".'), $act->actor->id)); } switch ($act->verb) { /* case ActivityVerb::FAVORITE: $this->log(LOG_INFO, "Moving favorite of {$act->objects[0]->id} by ". "{$act->actor->id} to {$remote->nickname}."); // push it, then delete local $sink->postActivity($act); $notice = Notice::getKV('uri', $act->objects[0]->id); if (!empty($notice)) { $fave = Fave::pkeyGet(array('user_id' => $user->id, 'notice_id' => $notice->id)); $fave->delete(); } break;*/ case ActivityVerb::POST: $this->log(LOG_INFO, "Moving notice {$act->objects[0]->id} by " . "{$act->actor->id} to {$remote->nickname}."); // XXX: send a reshare, not a post $sink->postActivity($act); $notice = Notice::getKV('uri', $act->objects[0]->id); if (!empty($notice)) { $notice->delete(); } break; case ActivityVerb::JOIN: $this->log(LOG_INFO, "Moving group join of {$act->objects[0]->id} by " . "{$act->actor->id} to {$remote->nickname}."); $sink->postActivity($act); $group = User_group::getKV('uri', $act->objects[0]->id); if (!empty($group)) { $user->leaveGroup($group); } break; case ActivityVerb::FOLLOW: if ($act->actor->id === $user->getUri()) { $this->log(LOG_INFO, "Moving subscription to {$act->objects[0]->id} by " . "{$act->actor->id} to {$remote->nickname}."); $sink->postActivity($act); try { $other = Profile::fromUri($act->objects[0]->id); Subscription::cancel($user->getProfile(), $other); } catch (UnknownUriException $e) { // Can't cancel subscription if we don't know who to alert } } else { $otherUser = User::getKV('uri', $act->actor->id); if (!empty($otherUser)) { $this->log(LOG_INFO, "Changing sub to {$act->objects[0]->id}" . "by {$act->actor->id} to {$remote->nickname}."); $otherProfile = $otherUser->getProfile(); Subscription::ensureStart($otherProfile, $remote); Subscription::cancel($otherProfile, $user->getProfile()); } else { $this->log(LOG_NOTICE, "Not changing sub to {$act->objects[0]->id}" . "by remote {$act->actor->id} " . "to {$remote->nickname}."); } } break; } }
/** * Save a bookmark from an activity * * @param Activity $activity Activity to save * @param Profile $actor Profile to use as author * @param array $options Options to pass to bookmark-saving code * * @return Notice resulting notice */ function saveNoticeFromActivity(Activity $activity, Profile $actor, array $options = array()) { $bookmark = $activity->objects[0]; $relLinkEls = ActivityUtils::getLinks($bookmark->element, 'related'); if (count($relLinkEls) < 1) { // TRANS: Client exception thrown when a bookmark is formatted incorrectly. throw new ClientException(_m('Expected exactly 1 link ' . 'rel=related in a Bookmark.')); } if (count($relLinkEls) > 1) { common_log(LOG_WARNING, "Got too many link rel=related in a Bookmark."); } $linkEl = $relLinkEls[0]; $url = $linkEl->getAttribute('href'); $tags = array(); foreach ($activity->categories as $category) { $tags[] = common_canonical_tag($category->term); } if (!empty($activity->time)) { $options['created'] = common_sql_date($activity->time); } // Fill in location if available $location = $activity->context->location; if ($location) { $options['lat'] = $location->lat; $options['lon'] = $location->lon; if ($location->location_id) { $options['location_ns'] = $location->location_ns; $options['location_id'] = $location->location_id; } } $options['groups'] = array(); $options['replies'] = array(); // TODO: context->attention foreach ($activity->context->attention as $attnUrl => $type) { try { $other = Profile::fromUri($attnUrl); if ($other->isGroup()) { $options['groups'][] = $other->id; } else { $options['replies'][] = $attnUrl; } } catch (UnknownUriException $e) { // We simply don't know this URI, despite lookup attempts. } } // Maintain direct reply associations // @fixme what about conversation ID? if (!empty($activity->context->replyToID)) { $orig = Notice::getKV('uri', $activity->context->replyToID); if (!empty($orig)) { $options['reply_to'] = $orig->id; } } return Bookmark::saveNew($actor, $bookmark->title, $url, $tags, $bookmark->summary, $options); }
function linkback_profile($entry, $mf2, $response, $target) { if (isset($entry['properties']['author']) && isset($entry['properties']['author'][0]['properties'])) { $author = $entry['properties']['author'][0]['properties']; } else { $author = linkback_hcard($mf2, $response->getEffectiveUrl()); } if (!$author) { $author = array('name' => array($entry['name'])); } if (!$author['url']) { $author['url'] = array($response->getEffectiveUrl()); } $user = User::getKV('uri', $author['url'][0]); if ($user instanceof User) { common_log(LOG_INFO, "Linkback: ignoring linkback from local user: {$url}"); return true; } try { $profile = Profile::fromUri($author['url'][0]); } catch (UnknownUriException $ex) { } if (!$profile instanceof Profile) { $profile = Profile::getKV('profileurl', $author['url'][0]); } if (!$profile instanceof Profile) { $profile = new Profile(); $profile->profileurl = $author['url'][0]; $profile->fullname = $author['name'][0]; $profile->nickname = $author['nickname'] ? $author['nickname'][0] : str_replace(' ', '', $author['name'][0]); $profile->created = common_sql_now(); $profile->insert(); } return array($profile, $author); }