* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-if (!defined('STATUSNET')) {
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* @package OStatusPlugin
- * @maintainer Brion Vibber <brion@status.net>
+ * @author Brion Vibber <brion@status.net>
+ * @maintainer Mikael Nordfeldth <mmn@hethane.se>
*/
class Ostatus_profile extends Managed_DataObject
{
{
return array(
'fields' => array(
- 'uri' => array('type' => 'varchar', 'length' => 255, 'not null' => true),
+ 'uri' => array('type' => 'varchar', 'length' => 191, 'not null' => true),
'profile_id' => array('type' => 'integer'),
'group_id' => array('type' => 'integer'),
'peopletag_id' => array('type' => 'integer'),
- 'feeduri' => array('type' => 'varchar', 'length' => 255),
- 'salmonuri' => array('type' => 'varchar', 'length' => 255),
+ 'feeduri' => array('type' => 'varchar', 'length' => 191),
+ 'salmonuri' => array('type' => 'varchar', 'length' => 191),
'avatar' => array('type' => 'text'),
'created' => array('type' => 'datetime', 'not null' => true),
'modified' => array('type' => 'datetime', 'not null' => true),
* send immediately but won't get the return value.
*
* @param mixed $entry XML string, Notice, or Activity
+ * @param Profile $actor Acting profile
* @return boolean success
*/
- public function notifyDeferred($entry, $actor)
+ public function notifyDeferred($entry, Profile $actor)
{
if ($this->salmonuri) {
$data = array('salmonuri' => $this->salmonuri,
*
* @return Notice Notice representing the new (or existing) activity
*/
- public function processEntry($entry, $feed, $source)
+ public function processEntry(DOMElement $entry, DOMElement $feed, $source)
{
$activity = new Activity($entry, $feed);
return $this->processActivity($activity, $source);
}
// TODO: Make this throw an exception
- public function processActivity($activity, $source)
+ public function processActivity(Activity $activity, $source)
{
$notice = null;
throw new ClientException(_m('Cannot handle that kind of post.'));
}
break;
- case ActivityVerb::SHARE:
- $notice = $this->processShare($activity, $source);
- break;
default:
common_log(LOG_INFO, "Ignoring activity with unrecognized verb $activity->verb");
}
return $notice;
}
- public function processShare($activity, $method)
- {
- $notice = null;
-
- try {
- $profile = ActivityUtils::checkAuthorship($activity, $this->localProfile());
- } catch (ServerException $e) {
- return null;
- }
-
- // The id URI will be used as a unique identifier for the notice,
- // protecting against duplicate saves. It isn't required to be a URL;
- // tag: URIs for instance are found in Google Buzz feeds.
- $dupe = Notice::getKV('uri', $activity->id);
- if ($dupe instanceof Notice) {
- common_log(LOG_INFO, "OStatus: ignoring duplicate post: {$activity->id}");
- return $dupe;
- }
-
- if (count($activity->objects) != 1) {
- // TRANS: Client exception thrown when trying to share multiple activities at once.
- throw new ClientException(_m('Can only handle share activities with exactly one object.'));
- }
-
- $shared = $activity->objects[0];
-
- if (!$shared instanceof Activity) {
- // TRANS: Client exception thrown when trying to share a non-activity object.
- throw new ClientException(_m('Can only handle shared activities.'));
- }
-
- $sharedId = $shared->id;
- if (!empty($shared->objects[0]->id)) {
- // Because StatusNet since commit 8cc4660 sets $shared->id to a TagURI which
- // fucks up federation, because the URI is no longer recognised by the origin.
- // So we set it to the object ID if it exists, otherwise we trust $shared->id
- $sharedId = $shared->objects[0]->id;
- }
- if (empty($sharedId)) {
- throw new ClientException(_m('Shared activity does not have an id'));
- }
-
- // First check if we have the shared activity. This has to be done first, because
- // we can't use these functions to "ensureActivityObjectProfile" of a local user,
- // who might be the creator of the shared activity in question.
- $sharedNotice = Notice::getKV('uri', $sharedId);
- if (!$sharedNotice instanceof Notice) {
- // If no locally stored notice is found, process it!
- // TODO: Remember to check Deleted_notice!
- // TODO: If a post is shared that we can't retrieve - what to do?
- try {
- $other = self::ensureActivityObjectProfile($shared->actor);
- $sharedNotice = $other->processActivity($shared, $method);
- if (!$sharedNotice instanceof Notice) {
- // And if we apparently can't get the shared notice, we'll abort the whole thing.
- // TRANS: Client exception thrown when saving an activity share fails.
- // TRANS: %s is a share ID.
- throw new ClientException(sprintf(_m('Failed to save activity %s.'), $sharedId));
- }
- } catch (FeedSubException $e) {
- // Remote feed could not be found or verified, should we
- // transform this into an "RT @user Blah, blah, blah..."?
- common_log(LOG_INFO, __METHOD__ . ' got a ' . get_class($e) . ': ' . $e->getMessage());
- return null;
- }
- }
-
- // We'll want to save a web link to the original notice, if provided.
-
- $sourceUrl = null;
- if ($activity->link) {
- $sourceUrl = $activity->link;
- } else if ($activity->link) {
- $sourceUrl = $activity->link;
- } else if (preg_match('!^https?://!', $activity->id)) {
- $sourceUrl = $activity->id;
- }
-
- // Use summary as fallback for content
-
- if (!empty($activity->content)) {
- $sourceContent = $activity->content;
- } else if (!empty($activity->summary)) {
- $sourceContent = $activity->summary;
- } else if (!empty($activity->title)) {
- $sourceContent = $activity->title;
- } else {
- // @todo FIXME: Fetch from $sourceUrl?
- // TRANS: Client exception. %s is a source URI.
- throw new ClientException(sprintf(_m('No content for notice %s.'), $activity->id));
- }
-
- // Get (safe!) HTML and text versions of the content
-
- $rendered = common_purify($sourceContent);
- $content = common_strip_html($rendered);
-
- $shortened = common_shorten_links($content);
-
- // If it's too long, try using the summary, and make the
- // HTML an attachment.
-
- $attachment = null;
-
- if (Notice::contentTooLong($shortened)) {
- $attachment = $this->saveHTMLFile($activity->title, $rendered);
- $summary = common_strip_html($activity->summary);
- if (empty($summary)) {
- $summary = $content;
- }
- $shortSummary = common_shorten_links($summary);
- if (Notice::contentTooLong($shortSummary)) {
- $url = common_shorten_url($sourceUrl);
- $shortSummary = substr($shortSummary,
- 0,
- Notice::maxContent() - (mb_strlen($url) + 2));
- $content = $shortSummary . ' ' . $url;
-
- // We mark up the attachment link specially for the HTML output
- // so we can fold-out the full version inline.
-
- // @todo FIXME i18n: This tooltip will be saved with the site's default language
- // TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
- // TRANS: this will usually be replaced with localised text from StatusNet core messages.
- $showMoreText = _m('Show more');
- $attachUrl = common_local_url('attachment',
- array('attachment' => $attachment->id));
- $rendered = common_render_text($shortSummary) .
- '<a href="' . htmlspecialchars($attachUrl) .'"'.
- ' class="attachment more"' .
- ' title="'. htmlspecialchars($showMoreText) . '">' .
- '…' .
- '</a>';
- }
- }
-
- $options = array('is_local' => Notice::REMOTE,
- 'url' => $sourceUrl,
- 'uri' => $activity->id,
- 'rendered' => $rendered,
- 'replies' => array(),
- 'groups' => array(),
- 'peopletags' => array(),
- 'tags' => array(),
- 'urls' => array(),
- 'repeat_of' => $sharedNotice->id,
- 'scope' => $sharedNotice->scope);
-
- // Check for optional attributes...
-
- if (!empty($activity->time)) {
- $options['created'] = common_sql_date($activity->time);
- }
-
- if ($activity->context) {
- // TODO: context->attention
- list($options['groups'], $options['replies'])
- = self::filterAttention($profile, $activity->context->attention);
-
- // Maintain direct reply associations
- // @todo FIXME: What about conversation ID?
- if (!empty($activity->context->replyToID)) {
- $orig = Notice::getKV('uri',
- $activity->context->replyToID);
- if ($orig instanceof Notice) {
- $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;
- }
- }
- }
-
- if ($this->isPeopletag()) {
- $options['peopletags'][] = $this->localPeopletag();
- }
-
- // 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) {
- // @todo FIXME: Save these locally or....?
- $options['urls'][] = $href;
- }
-
- $notice = Notice::saveNew($profile->id,
- $content,
- 'ostatus',
- $options);
-
- return $notice;
- }
-
/**
* Process an incoming post activity from this remote feed.
* @param Activity $activity
* @return mixed saved Notice or false
* @todo FIXME: Break up this function, it's getting nasty long
*/
- public function processPost($activity, $method)
+ public function processPost(Activity $activity, $method)
{
$notice = null;
$options);
if ($saved instanceof Notice) {
Ostatus_source::saveNew($saved, $this, $method);
- if (!empty($attachment)) {
- File_to_post::processNew($attachment->id, $saved->id);
+ if ($attachment instanceof File) {
+ File_to_post::processNew($attachment, $saved);
}
}
} catch (Exception $e) {
*/
static public function filterAttention(Profile $sender, array $attention)
{
- common_log(LOG_DEBUG, "Original reply recipients: " . implode(', ', array_keys($attention)));
+ common_debug("Original reply recipients: " . implode(', ', array_keys($attention)));
$groups = array();
$replies = array();
foreach ($attention as $recipient=>$type) {
if ($sender->isMember($group)) {
$groups[] = $group->id;
} else {
- common_log(LOG_DEBUG, sprintf('Skipping reply to local group %s as sender %d is not a member', $group->getNickname(), $sender->id));
+ common_debug(sprintf('Skipping reply to local group %s as sender %d is not a member', $group->getNickname(), $sender->id));
}
continue;
} else {
- common_log(LOG_DEBUG, "Skipping reply to bogus group $recipient");
+ common_debug("Skipping reply to bogus group $recipient");
}
}
continue;
} catch (Exception $e) {
// Neither a recognizable local nor remote user!
- common_log(LOG_DEBUG, "Skipping reply to unrecognized profile $recipient: " . $e->getMessage());
+ common_debug("Skipping reply to unrecognized profile $recipient: " . $e->getMessage());
}
}
- common_log(LOG_DEBUG, "Local reply recipients: " . implode(', ', $replies));
- common_log(LOG_DEBUG, "Local group recipients: " . implode(', ', $groups));
+ common_debug("Local reply recipients: " . implode(', ', $replies));
+ common_debug("Local group recipients: " . implode(', ', $groups));
return array($groups, $replies);
}
*/
public static function ensureFeedURL($feed_url, array $hints=array())
{
+ $oprofile = Ostatus_profile::getKV('feeduri', $feed_url);
+ if ($oprofile instanceof Ostatus_profile) {
+ return $oprofile;
+ }
+
$discover = new FeedDiscovery();
$feeduri = $discover->discoverFromFeedURL($feed_url);
}
}
+ $obj = ActivityUtils::getFeedAuthor($feedEl);
+
// @todo FIXME: We should check whether this feed has elements
// with different <author> or <dc:creator> elements, and... I dunno.
// Do something about that.
- $obj = ActivityObject::fromRssChannel($feedEl);
+ if(empty($obj)) { $obj = ActivityObject::fromRssChannel($feedEl); }
return self::ensureActivityObjectProfile($obj, $hints);
}
// @todo FIXME: This should be better encapsulated
// ripped from oauthstore.php (for old OMB client)
- $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
+ $temp_filename = tempnam(common_get_temp_dir(), 'listener_avatar');
try {
$imgData = HTTPClient::quickGet($url);
// Make sure it's at least an image file. ImageFile can do the rest.
} else {
$id = $this->profile_id;
}
- // @todo FIXME: Should we be using different ids?
- $imagefile = new ImageFile($id, $temp_filename);
+ $imagefile = new ImageFile(null, $temp_filename);
$filename = Avatar::filename($id,
image_type_to_extension($imagefile->type),
null,
$discover = false;
if (!$homeuri) {
- common_log(LOG_DEBUG, __METHOD__ . " empty actor profile URI: " . var_export($activity, true));
+ common_debug(__METHOD__ . " empty actor profile URI: " . var_export($activity, true));
// TRANS: Exception.
throw new Exception(_m('No profile URI.'));
}
// @todo tags from categories
if ($profile->id) {
- common_log(LOG_DEBUG, "Updating OStatus profile $profile->id from remote info $object->id: " . var_export($object, true) . var_export($hints, true));
+ common_debug("Updating OStatus profile $profile->id from remote info $object->id: " . var_export($object, true) . var_export($hints, true));
$profile->update($orig);
}
}
$group->homepage = self::getActivityObjectHomepage($object, $hints);
if ($group->id) { // If no id, we haven't called insert() yet, so don't run update()
- common_log(LOG_DEBUG, "Updating OStatus group $group->id from remote info $object->id: " . var_export($object, true) . var_export($hints, true));
+ common_debug("Updating OStatus group $group->id from remote info $object->id: " . var_export($object, true) . var_export($hints, true));
$group->update($orig);
}
}
- protected static function updatePeopletag($tag, ActivityObject $object, array $hints=array()) {
+ protected static function updatePeopletag(Peopletag $tag, ActivityObject $object, array $hints=array()) {
$orig = clone($tag);
$tag->tag = $object->title;
$tag->tagger = $tagger->profile_id;
if ($tag->id) {
- common_log(LOG_DEBUG, "Updating OStatus peopletag $tag->id from remote info $object->id: " . var_export($object, true) . var_export($hints, true));
+ common_debug("Updating OStatus peopletag $tag->id from remote info $object->id: " . var_export($object, true) . var_export($hints, true));
$tag->update($orig);
}
}
}
if (!empty($location)) {
- if (mb_strlen($location) > 255) {
- $location = mb_substr($note, 0, 255 - 3) . ' … ';
+ if (mb_strlen($location) > 191) { // not 255 because utf8mb4 takes more space
+ $location = mb_substr($note, 0, 191 - 3) . ' … ';
}
}
throw new Exception(_m('Not a valid webfinger address.'));
}
- $hints = array('webfinger' => $addr);
-
- $dhints = DiscoveryHints::fromXRD($xrd);
-
- $hints = array_merge($hints, $dhints);
+ $hints = array_merge(array('webfinger' => $addr),
+ DiscoveryHints::fromXRD($xrd));
// If there's an Hcard, let's grab its info
if (array_key_exists('hcard', $hints)) {
throw new ServerException(sprintf(_m('Unrecognized URI protocol for profile: %1$s (%2$s).'),
$protocol,
$uri));
- break;
}
} else {
// TRANS: Server exception. %s is a URI.
} else {
$actor = $activity->actor;
- if (empty($actor)) {
+ if (!$actor instanceof Profile) {
// OK here! assume the default
} else if ($actor->id == $this->getUri() || $actor->link == $this->getUri()) {
$this->updateFromActivityObject($actor);
return $oprofile->localProfile();
}
+
+ public function updateUriKeys($profile_uri, array $hints=array())
+ {
+ $orig = clone($this);
+
+ common_debug('URIFIX These identities both say they are each other: "'.$orig->uri.'" and "'.$profile_uri.'"');
+ $this->uri = $profile_uri;
+
+ if (array_key_exists('feedurl', $hints)) {
+ if (!empty($this->feeduri)) {
+ common_debug('URIFIX Changing FeedSub ['.$feedsub->id.'] feeduri "'.$feedsub->uri.'" to "'.$hints['feedurl']);
+ $feedsub = FeedSub::getKV('uri', $this->feeduri);
+ $feedorig = clone($feedsub);
+ $feedsub->uri = $hints['feedurl'];
+ $feedsub->updateWithKeys($feedorig);
+ } else {
+ common_debug('URIFIX Old Ostatus_profile did not have feedurl set, ensuring feed: '.$hints['feedurl']);
+ FeedSub::ensureFeed($hints['feedurl']);
+ }
+ $this->feeduri = $hints['feedurl'];
+ }
+ if (array_key_exists('salmon', $hints)) {
+ common_debug('URIFIX Changing Ostatus_profile salmonuri from "'.$this->salmonuri.'" to "'.$hints['salmon'].'"');
+ $this->salmonuri = $hints['salmon'];
+ }
+
+ common_debug('URIFIX Updating Ostatus_profile URI for '.$orig->uri.' to '.$this->uri);
+ $this->updateWithKeys($orig, 'uri'); // 'uri' is the primary key column
+
+ common_debug('URIFIX Subscribing/renewing feedsub for Ostatus_profile '.$this->uri);
+ $this->subscribe();
+ }
}
/**