public $feeduri;
public $salmonuri;
+ public $avatar; // remote URL of the last avatar we saved
public $created;
public $modified;
'group_id' => DB_DATAOBJECT_INT,
'feeduri' => DB_DATAOBJECT_STR,
'salmonuri' => DB_DATAOBJECT_STR,
+ 'avatar' => DB_DATAOBJECT_STR,
'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
'modified' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
}
255, true, 'UNI'),
new ColumnDef('salmonuri', 'text',
null, true),
+ new ColumnDef('avatar', 'text',
+ null, true),
new ColumnDef('created', 'datetime',
null, false),
new ColumnDef('modified', 'datetime',
throw new ServerException("Remote groups can't subscribe to local users");
}
- // @fixme use regular channels for subbing, once they accept remote profiles
- $sub = new Subscription();
- $sub->subscriber = $this->profile_id;
- $sub->subscribed = $user->id;
- $sub->created = common_sql_now(); // current time
+ Subscription::start($this->localProfile(), $user->getProfile());
- if ($sub->insert()) {
- // @fixme use subs_notify() if refactored to take profiles?
- mail_subscribe_notify_profile($user, $this->localProfile());
- return true;
- }
- return false;
+ return true;
}
/**
return false;
}
- public function notifyActivity($activity)
+ /**
+ * Send a Salmon notification ping immediately, and confirm that we got
+ * an acceptable response from the remote site.
+ *
+ * @param mixed $entry XML string, Notice, or Activity
+ * @return boolean success
+ */
+ public function notifyActivity($entry)
{
if ($this->salmonuri) {
+ $salmon = new Salmon();
+ return $salmon->post($this->salmonuri, $this->notifyPrepXml($entry));
+ }
- $xml = '<?xml version="1.0" encoding="UTF-8" ?' . '>' .
- $activity->asString(true);
+ return false;
+ }
- $salmon = new Salmon(); // ?
+ /**
+ * Queue a Salmon notification for later. If queues are disabled we'll
+ * send immediately but won't get the return value.
+ *
+ * @param mixed $entry XML string, Notice, or Activity
+ * @return boolean success
+ */
+ public function notifyDeferred($entry)
+ {
+ if ($this->salmonuri) {
+ $data = array('salmonuri' => $this->salmonuri,
+ 'entry' => $this->notifyPrepXml($entry));
- return $salmon->post($this->salmonuri, $xml);
+ $qm = QueueManager::get();
+ return $qm->enqueue($data, 'salmon');
}
return false;
}
+ protected function notifyPrepXml($entry)
+ {
+ $preamble = '<?xml version="1.0" encoding="UTF-8" ?' . '>';
+ if (is_string($entry)) {
+ return $entry;
+ } else if ($entry instanceof Activity) {
+ return $preamble . $entry->asString(true);
+ } else if ($entry instanceof Notice) {
+ return $preamble . $entry->asAtomEntry(true, true);
+ } else {
+ throw new ServerException("Invalid type passed to Ostatus_profile::notify; must be XML string or Activity entry");
+ }
+ }
+
function getBestName()
{
if ($this->isGroup()) {
// through PuSH setup or Salmon signature checks.
$actorUri = self::getActorProfileURI($activity);
if ($actorUri == $this->uri) {
- // @fixme check if profile info has changed and update it
+ // Check if profile info has changed and update it
+ $this->updateFromActivityObject($activity->actor);
} else {
common_log(LOG_WARNING, "OStatus: skipping post with bad author: got $actorUri expected $this->uri");
return false;
*/
protected function purify($html)
{
- // @fixme disable caching or set a sane temp dir
require_once(INSTALLDIR.'/extlib/HTMLPurifier/HTMLPurifier.auto.php');
- $purifier = new HTMLPurifier();
+
+ // By default Purifier wants to cache data to its own code directories,
+ // and spews error messages if they're not writable.
+ $config = HTMLPurifier_Config::createDefault();
+ if (common_config('ostatus', 'purify_cache')) {
+ $config->set('Cache.SerializerPath', common_config('ostatus', 'purify_cache'));
+ } else {
+ // Although recommended in the documentation, this produces a notice:
+ // "Core.DefinitionCache is an alias, preferred directive name is Cache.DefinitionImpl"
+ // If I then follow *those* directions, I get a warning and it doesn't work:
+ // "Cannot set undefined directive Core.DefinitionImpl"
+ // So... lesser of two evils. Suppressing the notice from output,
+ // though it'll still be seen and logged by StatusNet's error handler.
+ $old = error_reporting();
+ error_reporting($old & ~E_NOTICE);
+ $config->set('Core.DefinitionCache', null);
+ error_reporting($old);
+ }
+
+ $purifier = new HTMLPurifier($config);
return $purifier->purify($html);
}
*/
protected function updateAvatar($url)
{
+ if ($url == $this->avatar) {
+ // We've already got this one.
+ return;
+ }
+
if ($this->isGroup()) {
$self = $this->localGroup();
} else {
common_timestamp());
rename($temp_filename, Avatar::path($filename));
$self->setOriginal($filename);
+
+ $orig = clone($this);
+ $this->avatar = $url;
+ $this->update($orig);
}
- protected static function getActivityObjectAvatar($object)
+ /**
+ * Pull avatar URL from ActivityObject or profile hints
+ *
+ * @param ActivityObject $object
+ * @param array $hints
+ * @return mixed URL string or false
+ */
+
+ protected static function getActivityObjectAvatar($object, $hints=array())
{
- // XXX: go poke around in the feed
- return $object->avatar;
+ if ($object->avatar) {
+ return $object->avatar;
+ } else if (array_key_exists('avatar', $hints)) {
+ return $hints['avatar'];
+ }
+ return false;
}
/**
public static function ensureActivityObjectProfile($object, $feeduri=null, $salmonuri=null, $hints=array())
{
$profile = self::getActivityObjectProfile($object);
- if (!$profile) {
+ if ($profile) {
+ $profile->updateFromActivityObject($object, $hints);
+ } else {
$profile = self::createActivityObjectProfile($object, $feeduri, $salmonuri, $hints);
}
return $profile;
* @param Activity $activity
* @return mixed matching Ostatus_profile or false if none known
*/
- protected static function getActorProfile($activity)
+ public static function getActorProfile($activity)
{
return self::getActivityObjectProfile($activity->actor);
}
protected static function createActivityObjectProfile($object, $feeduri=null, $salmonuri=null, $hints=array())
{
$homeuri = $object->id;
- $nickname = self::getActivityObjectNickname($object, $hints);
- $avatar = self::getActivityObjectAvatar($object);
if (!$homeuri) {
common_log(LOG_DEBUG, __METHOD__ . " empty actor profile URI: " . var_export($activity, true));
if ($object->type == ActivityObject::PERSON) {
$profile = new Profile();
- $profile->nickname = $nickname;
- $profile->fullname = $object->title;
- if (!empty($object->link)) {
- $profile->profileurl = $object->link;
- } else if (array_key_exists('profileurl', $hints)) {
- $profile->profileurl = $hints['profileurl'];
- }
- $profile->created = common_sql_now();
-
- // @fixme bio
- // @fixme tags/categories
- // @fixme location?
- // @todo tags from categories
- // @todo lat/lon/location?
-
+ self::updateProfile($profile, $object, $hints);
+ $profile->created = common_sql_now();
+
$oprofile->profile_id = $profile->insert();
-
if (!$oprofile->profile_id) {
throw new ServerException("Can't save local profile");
}
} else {
$group = new User_group();
- $group->nickname = $nickname;
- $group->fullname = $object->title;
- // @fixme no canonical profileurl; using homepage instead for now
- $group->homepage = $homeuri;
$group->created = common_sql_now();
-
- // @fixme homepage
- // @fixme bio
- // @fixme tags/categories
- // @fixme location?
- // @todo tags from categories
- // @todo lat/lon/location?
+ self::updateGroup($group, $object, $hints);
$oprofile->group_id = $group->insert();
-
if (!$oprofile->group_id) {
throw new ServerException("Can't save local profile");
}
$ok = $oprofile->insert();
if ($ok) {
+ $avatar = self::getActivityObjectAvatar($object, $hints);
if ($avatar) {
$oprofile->updateAvatar($avatar);
}
}
}
+ /**
+ * Save any updated profile information to our local copy.
+ * @param ActivityObject $object
+ * @param array $hints
+ */
+ public function updateFromActivityObject($object, $hints=array())
+ {
+ if ($this->isGroup()) {
+ $group = $this->localGroup();
+ self::updateGroup($group, $object, $hints);
+ } else {
+ $profile = $this->localProfile();
+ self::updateProfile($profile, $object, $hints);
+ }
+ $avatar = self::getActivityObjectAvatar($object, $hints);
+ if ($avatar) {
+ $this->updateAvatar($avatar);
+ }
+ }
+
+ protected static function updateProfile($profile, $object, $hints=array())
+ {
+ $orig = clone($profile);
+
+ $profile->nickname = self::getActivityObjectNickname($object, $hints);
+ $profile->fullname = $object->title;
+ if (!empty($object->link)) {
+ $profile->profileurl = $object->link;
+ } else if (array_key_exists('profileurl', $hints)) {
+ $profile->profileurl = $hints['profileurl'];
+ }
+
+ // @fixme bio
+ // @fixme tags/categories
+ // @fixme location?
+ // @todo tags from categories
+ // @todo lat/lon/location?
+
+ 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));
+ $profile->update($orig);
+ }
+ }
+
+ protected static function updateGroup($group, $object, $hints=array())
+ {
+ $orig = clone($group);
+
+ // @fixme need to make nick unique etc *hack hack*
+ $group->nickname = self::getActivityObjectNickname($object, $hints);
+ $group->fullname = $object->title;
+
+ // @fixme no canonical profileurl; using homepage instead for now
+ $group->homepage = $object->id;
+
+ // @fixme homepage
+ // @fixme bio
+ // @fixme tags/categories
+ // @fixme location?
+ // @todo tags from categories
+ // @todo lat/lon/location?
+
+ if ($group->id) {
+ common_log(LOG_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 getActivityObjectNickname($object, $hints=array())
{
+ if ($object->poco) {
+ if (!empty($object->poco->preferredUsername)) {
+ return common_nicknamize($object->poco->preferredUsername);
+ }
+ }
if (!empty($object->nickname)) {
return common_nicknamize($object->nickname);
}