// Would be nice to tell if they were a Person or not (e.g. a #person usertag?)
$this->elementStart('Agent', array('rdf:about' => $this->user->getUri()));
- if ($this->user->email) {
+ if (common_config('foaf', 'mbox_sha1sum') && $this->user->email) {
$this->element('mbox_sha1sum', null, sha1('mailto:' . $this->user->email));
}
if ($this->profile->fullname) {
// and maybe even directly save whether they're local or not!
$act->context->attention = common_get_attentions($content, $this->scoped, $parent);
+ // $options gets filled with possible scoping settings
+ ToSelector::fillActivity($this, $act, $options);
+
$actobj = new ActivityObject();
$actobj->type = ActivityObject::NOTE;
$actobj->content = common_render_content($content, $this->scoped, $parent);
$this->elementStart('li');
// TRANS: Field label in form for profile settings.
$this->input('fullname', _('Full name'),
- $this->trimmed('fullname') ?: $this->scoped->getFullname());
+ $this->trimmed('fullname') ?: $this->scoped->getFullname(),
+ // TRANS: Instructions for full name text field on profile settings
+ _('A full name is required, if empty it will be set to your nickname.'),
+ null, true);
$this->elementEnd('li');
$this->elementStart('li');
// TRANS: Field label in form for profile settings.
(empty($user->subscribe_policy)) ? User::SUBSCRIBE_POLICY_OPEN : $user->subscribe_policy);
$this->elementEnd('li');
}
- $this->elementStart('li');
- $this->checkbox('private_stream',
- // TRANS: Checkbox label in profile settings.
- _('Make updates visible only to my followers'),
- ($this->arg('private_stream')) ?
- $this->boolean('private_stream') : $user->private_stream);
- $this->elementEnd('li');
+ if (common_config('profile', 'allowprivate') || $user->private_stream) {
+ $this->elementStart('li');
+ $this->checkbox('private_stream',
+ // TRANS: Checkbox label in profile settings.
+ _('Make updates visible only to my followers'),
+ ($this->arg('private_stream')) ?
+ $this->boolean('private_stream') : $user->private_stream);
+ $this->elementEnd('li');
+ }
$this->elementEnd('ul');
// TRANS: Button to save input in profile settings.
$this->submit('save', _m('BUTTON','Save'));
$location = $this->trimmed('location');
$autosubscribe = $this->booleanintstring('autosubscribe');
$subscribe_policy = $this->trimmed('subscribe_policy');
- $private_stream = $this->booleanintstring('private_stream');
$language = $this->trimmed('language');
$timezone = $this->trimmed('timezone');
$tagstring = $this->trimmed('tags');
$user = $this->scoped->getUser();
$user->query('BEGIN');
+ // Only allow setting private_stream if site policy allows it
+ // (or user already _has_ a private stream, then you can unset it)
+ if (common_config('profile', 'allowprivate') || $user->private_stream) {
+ $private_stream = $this->booleanintstring('private_stream');
+ } else {
+ // if not allowed, we set to the existing value
+ $private_stream = $user->private_stream;
+ }
+
// $user->nickname is updated through Profile->update();
// XXX: XOR
$this->scoped->nickname = $nickname;
$this->scoped->profileurl = common_profile_url($this->scoped->getNickname());
}
- $this->scoped->fullname = $fullname;
+ $this->scoped->fullname = (mb_strlen($fullname)>0 ? $fullname : $this->scoped->nickname);
$this->scoped->homepage = $homepage;
$this->scoped->bio = $bio;
$this->scoped->location = $location;
}
$this->notice = $this->getNotice();
+ $this->target = $this->notice;
if (!$this->notice->inScope($this->scoped)) {
// TRANS: Client exception thrown when trying a view a notice the user has no access to.
{
}
- /**
- * Don't show aside
- *
- * @return void
- */
- function showAside() {
+ function getFeeds()
+ {
+ return array(new Feed(Feed::JSON,
+ common_local_url('ApiStatusesShow',
+ array(
+ 'id' => $this->target->getID(),
+ 'format' => 'json')),
+ // TRANS: Title for link to single notice representation.
+ // TRANS: %s is a user nickname.
+ sprintf(_('Single notice (JSON)'))),
+ new Feed(Feed::ATOM,
+ common_local_url('ApiStatusesShow',
+ array(
+ 'id' => $this->target->getID(),
+ 'format' => 'atom')),
+ // TRANS: Title for link to notice feed.
+ // TRANS: %s is a user nickname.
+ sprintf(_('Single notice (Atom)'))));
}
/**
return intval($this->id);
}
+ /**
+ * WARNING: Only use this on Profile and Notice. We should probably do
+ * this with traits/"implements" or whatever, but that's over the top
+ * right now, I'm just throwing this in here to avoid code duplication
+ * in Profile and Notice classes.
+ */
+ public function getAliases()
+ {
+ return array_keys($this->getAliasesWithIDs());
+ }
+
+ public function getAliasesWithIDs()
+ {
+ $aliases = array();
+ $aliases[$this->getUri()] = $this->getID();
+
+ try {
+ $aliases[$this->getUrl()] = $this->getID();
+ } catch (InvalidUrlException $e) {
+ // getUrl failed because no valid URL could be returned, just ignore it
+ }
+
+ if (common_config('fix', 'fancyurls')) {
+ /**
+ * Here we add some hacky hotfixes for remote lookups that have been taught the
+ * (at least now) wrong URI but it's still obviously the same user. Such as:
+ * - https://site.example/user/1 even if the client requests https://site.example/index.php/user/1
+ * - https://site.example/user/1 even if the client requests https://site.example//index.php/user/1
+ * - https://site.example/index.php/user/1 even if the client requests https://site.example/user/1
+ * - https://site.example/index.php/user/1 even if the client requests https://site.example///index.php/user/1
+ */
+ foreach ($aliases as $alias=>$id) {
+ try {
+ // get a "fancy url" version of the alias, even without index.php/
+ $alt_url = common_fake_local_fancy_url($alias);
+ // store this as well so remote sites can be sure we really are the same profile
+ $aliases[$alt_url] = $id;
+ } catch (Exception $e) {
+ // Apparently we couldn't rewrite that, the $alias was as the function wanted it to be
+ }
+
+ try {
+ // get a non-"fancy url" version of the alias, i.e. add index.php/
+ $alt_url = common_fake_local_nonfancy_url($alias);
+ // store this as well so remote sites can be sure we really are the same profile
+ $aliases[$alt_url] = $id;
+ } catch (Exception $e) {
+ // Apparently we couldn't rewrite that, the $alias was as the function wanted it to be
+ }
+ }
+ }
+ return $aliases;
+ }
+
// 'update' won't write key columns, so we have to do it ourselves.
// This also automatically calls "update" _before_ it sets the keys.
// FIXME: This only works with single-column primary keys so far! Beware!
public function getRendered()
{
- if (is_null($this->rendered) || $this->rendered === '') {
+ // we test $this->id because if it's not inserted yet, we can't update the field
+ if (!empty($this->id) && (is_null($this->rendered) || $this->rendered === '')) {
// update to include rendered content on-the-fly, so we don't have to have a fix-up script in upgrade.php
common_debug('Rendering notice '.$this->getID().' as it had no rendered HTML content.');
$orig = clone($this);
}
// Strip out any bad HTML
$stored->rendered = common_purify($content);
- // yeah, just don't use getRendered() here since it's not inserted yet ;)
- $stored->content = common_strip_html($stored->rendered);
+ $stored->content = common_strip_html($stored->getRendered(), true, true);
if (trim($stored->content) === '') {
// TRANS: Error message when the plain text content of a notice has zero length.
throw new ClientException(_('Empty notice content, will not save this.'));
return $this->uri;
}
+ static function getByUri($uri)
+ {
+ $user = new User();
+ $user->uri = $uri;
+ if (!$user->find(true)) {
+ throw new NoResultException($user);
+ }
+ return $user;
+ }
+
public function getNickname()
{
return $this->getProfile()->getNickname();
// NOTE: $this->scoped and $this->auth_user has to get set in
// prepare(), not handle(), as subclasses use them in prepares.
- // Allow regular login session
- if (common_logged_in()) {
+ // Allow regular login session, but we have to double-check the
+ // HTTP_REFERER value to avoid cross domain POSTing since the API
+ // doesn't use the "token" form field.
+ if (common_logged_in() && common_local_referer()) {
$this->scoped = Profile::current();
$this->auth_user = $this->scoped->getUser();
if (!$this->auth_user->hasRight(Right::API)) {
return 0;
}
+ if ($this->notice->getProfile()->isSilenced()) {
+ // TRANS: Message for inline attachments list in notices when the author has been silenced.
+ $this->element('div', ['class'=>'error'], _('Attachments are hidden because this profile has been silenced.'));
+ return 0;
+ }
+
$this->showListStart();
foreach ($attachments as $att) {
* @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);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Notice stream for a conversation
$notice->limit($offset, $limit);
}
- if (!empty($this->selectVerbs)) {
- $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
- }
+ self::filterVerbs($notice, $this->selectVerbs);
// ORDER BY
// currently imitates the previously used "_reverseChron" sorting
'log_queries' => false, // true to log all DB queries
'log_slow_queries' => 0, // if set, log queries taking over N seconds
'mysql_foreign_keys' => false), // if set, enables experimental foreign key support on MySQL
+ 'fix' =>
+ array('fancyurls' => true, // makes sure aliases in WebFinger etc. are not f'd by index.php/ URLs
+ ),
'syslog' =>
array('appname' => 'statusnet', # for syslog
'priority' => 'debug', # XXX: currently ignored
array('banned' => array(),
'biolimit' => null,
'changenick' => false,
+ 'allowprivate' => false, // whether to allow setting stream to private ("only followers can read")
'backup' => false, // can cause DoS, so should be done via CLI
'restore' => false,
'delete' => false,
'path' => $_path . '/avatar/',
'ssl' => null,
'maxsize' => 300),
+ 'foaf' =>
+ array(
+ 'mbox_sha1sum' => false,
+ ),
'public' =>
array('localonly' => false,
'blacklist' => array(),
),
'notice' =>
array('contentlimit' => null,
+ 'allowprivate' => false, // whether to allow users to "check the padlock" to publish notices available for their subscribers.
'defaultscope' => null, // null means 1 if site/private, 0 otherwise
'hidespam' => true), // Whether to hide silenced users from timelines
'message' =>
--- /dev/null
+<?php
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+/**
+ * Class for notice streams that does not filter anything out.
+ */
+abstract class FullNoticeStream extends NoticeStream
+{
+ protected $selectVerbs = [];
+}
* @link http://status.net/
*/
-if (!defined('GNUSOCIAL') && !defined('STATUSNET')) { exit(1); }
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Stream of notices for a profile's "all" feed
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
-class RawInboxNoticeStream extends NoticeStream
+class RawInboxNoticeStream extends FullNoticeStream
{
protected $target = null;
protected $inbox = null;
*/
function __construct(Profile $target)
{
+ parent::__construct();
$this->target = $target;
- $this->unselectVerbs = array(ActivityVerb::DELETE);
}
/**
if (!empty($max_id)) {
$notice->whereAdd(sprintf('notice.id <= %d', $max_id));
}
- if (!empty($this->selectVerbs)) {
- $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
- }
- if (!empty($this->unselectVerbs)) {
- $notice->whereAddIn('!verb', $this->unselectVerbs, $notice->columnType('verb'));
- }
+
+ self::filterVerbs($notice, $this->selectVerbs);
+
$notice->limit($offset, $limit);
// notice.id will give us even really old posts, which were
// recently imported. For example if a remote instance had
* @link http://status.net/
*/
-class RawNetworkPublicNoticeStream extends NoticeStream
+class RawNetworkPublicNoticeStream extends FullNoticeStream
{
function getNoticeIds($offset, $limit, $since_id, $max_id)
{
Notice::addWhereSinceId($notice, $since_id);
Notice::addWhereMaxId($notice, $max_id);
- if (!empty($this->selectVerbs)) {
- $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
- }
+ self::filterVerbs($notice, $this->selectVerbs);
$ids = array();
* @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);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Class for notice streams
*/
abstract class NoticeStream
{
- protected $selectVerbs = null; // must be set to array
- protected $unselectVerbs = null; // must be set to array
+ protected $selectVerbs = array(ActivityVerb::POST => true,
+ ActivityVerb::SHARE => true);
public function __construct()
{
- if ($this->selectVerbs === null) {
- $this->selectVerbs = array(ActivityVerb::POST, ActivityUtils::resolveUri(ActivityVerb::POST, true));
- }
- if ($this->unselectVerbs === null) {
- $this->unselectVerbs = array(ActivityVerb::DELETE);
+ foreach ($this->selectVerbs as $key=>$val) {
+ // to avoid database inconsistency issues we select both relative and absolute verbs
+ $this->selectVerbs[ActivityUtils::resolveUri($key)] = $val;
+ $this->selectVerbs[ActivityUtils::resolveUri($key, true)] = $val;
}
}
{
return Notice::multiGet('id', $ids);
}
+
+ static function filterVerbs(Notice $notice, array $selectVerbs)
+ {
+ $filter = array_keys(array_filter($selectVerbs));
+ if (!empty($filter)) {
+ // include verbs in selectVerbs with values that equate to true
+ $notice->whereAddIn('verb', $filter, $notice->columnType('verb'));
+ }
+
+ $filter = array_keys(array_filter($selectVerbs, function ($v) { return !$v; }));
+ if (!empty($filter)) {
+ // exclude verbs in selectVerbs with values that equate to false
+ $notice->whereAddIn('!verb', $filter, $notice->columnType('verb'));
+ }
+
+ unset($filter);
+ }
}
* @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);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Stream of notices by a profile
Notice::addWhereSinceId($notice, $since_id);
Notice::addWhereMaxId($notice, $max_id);
- if (!empty($this->selectVerbs)) {
- $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
- }
- if (!empty($this->unselectVerbs)) {
- $notice->whereAddIn('!verb', $this->unselectVerbs, $notice->columnType('verb'));
- }
+ self::filterVerbs($notice, $this->selectVerbs);
$notice->orderBy('created DESC, id DESC');
* @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);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Public stream
* @link http://status.net/
*/
-class RawPublicNoticeStream extends NoticeStream
+class RawPublicNoticeStream extends FullNoticeStream
{
function getNoticeIds($offset, $limit, $since_id, $max_id)
{
Notice::addWhereSinceId($notice, $since_id);
Notice::addWhereMaxId($notice, $max_id);
- if (!empty($this->selectVerbs)) {
- $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
- }
+ self::filterVerbs($notice, $this->selectVerbs);
$ids = array();
* @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);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Stream of mentions of me
Notice::addWhereMaxId($reply, $max_id, 'notice_id', 'reply.modified');
if (!empty($this->selectVerbs)) {
+ // this is a little special since we have to join in Notice
$reply->joinAdd(array('notice_id', 'notice:id'));
- $reply->whereAddIn('notice.verb', $this->selectVerbs, 'string');
+
+ $filter = array_keys(array_filter($this->selectVerbs));
+ if (!empty($filter)) {
+ // include verbs in selectVerbs with values that equate to true
+ $reply->whereAddIn('notice.verb', $filter, 'string');
+ }
+
+ $filter = array_keys(array_filter($this->selectVerbs, function ($v) { return !$v; }));
+ if (!empty($filter)) {
+ // exclude verbs in selectVerbs with values that equate to false
+ $reply->whereAddIn('!notice.verb', $filter, 'string');
+ }
}
$reply->orderBy('reply.modified DESC, reply.notice_id DESC');
* @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);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Stream of notices with a given tag
Notice::addWhereMaxId($nt, $max_id, 'notice_id');
if (!empty($this->selectVerbs)) {
- $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
- }
- if (!empty($this->unselectVerbs)) {
- $notice->whereAddIn('!verb', $this->unselectVerbs, $notice->columnType('verb'));
+ $nt->joinAdd(array('notice_id', 'notice:id'));
+
+ $filter = array_keys(array_filter($this->selectVerbs));
+ if (!empty($filter)) {
+ // include verbs in selectVerbs with values that equate to true
+ $nt->whereAddIn('notice.verb', $filter, 'string');
+ }
+
+ $filter = array_keys(array_filter($this->selectVerbs, function ($v) { return !$v; }));
+ if (!empty($filter)) {
+ // exclude verbs in selectVerbs with values that equate to false
+ $nt->whereAddIn('!notice.verb', $filter, 'string');
+ }
}
- $nt->orderBy('created DESC, notice_id DESC');
+ $nt->orderBy('notice.created DESC, notice_id DESC');
if (!is_null($offset)) {
$nt->limit($offset, $limit);
function show()
{
$choices = array();
- $default = 'public:site';
-
- if (!common_config('site', 'private')) {
- // TRANS: Option in drop-down of potential addressees.
- $choices['public:everyone'] = _m('SENDTO','Everyone');
- $default = 'public:everyone';
- }
- // TRANS: Option in drop-down of potential addressees.
- // TRANS: %s is a StatusNet sitename.
- $choices['public:site'] = sprintf(_('Everyone at %s'), common_config('site', 'name'));
+ $default = common_config('site', 'private') ? 'public:site' : 'public:everyone';
$groups = $this->user->getGroups();
while ($groups instanceof User_group && $groups->fetch()) {
- $value = 'group:'.$groups->id;
+ $value = 'group:'.$groups->getID();
if (($this->to instanceof User_group) && $this->to->id == $groups->id) {
$default = $value;
}
- $choices[$value] = $groups->getBestName();
+ $choices[$value] = "!{$groups->getNickname()} [{$groups->getBestName()}]";
}
// Add subscribed users to dropdown menu
$users = $this->user->getSubscribed();
while ($users->fetch()) {
- $value = 'profile:'.$users->id;
- if ($this->user->streamNicknames()) {
- $choices[$value] = $users->getNickname();
- } else {
- $choices[$value] = $users->getBestName();
+ $value = 'profile:'.$users->getID();
+ try {
+ $choices[$value] = substr($users->getAcctUri(), 5) . " [{$users->getBestName()}]";
+ } catch (ProfileNoAcctUriException $e) {
+ $choices[$value] = "[?@?] " . $e->profile->getBestName();
}
}
if ($this->to instanceof Profile) {
- $value = 'profile:'.$this->to->id;
+ $value = 'profile:'.$this->to->getID();
$default = $value;
- $choices[$value] = $this->to->getBestName();
+ try {
+ $choices[$value] = substr($this->to->getAcctUri(), 5) . " [{$this->to->getBestName()}]";
+ } catch (ProfileNoAcctUriException $e) {
+ $choices[$value] = "[?@?] " . $e->profile->getBestName();
+ }
+ }
+
+ // alphabetical order
+ asort($choices);
+
+ // Reverse so we can add entries at the end (can't unshift with a key)
+ $choices = array_reverse($choices);
+
+ if (common_config('notice', 'allowprivate')) {
+ // TRANS: Option in drop-down of potential addressees.
+ // TRANS: %s is a StatusNet sitename.
+ $choices['public:site'] = sprintf(_('Everyone at %s'), common_config('site', 'name'));
+ }
+
+ if (!common_config('site', 'private')) {
+ // TRANS: Option in drop-down of potential addressees.
+ $choices['public:everyone'] = _m('SENDTO','Everyone');
}
+ // Return the order
+ $choices = array_reverse($choices);
+
$this->out->dropdown($this->id,
// TRANS: Label for drop-down of potential addressees.
_m('LABEL','To:'),
$default);
$this->out->elementStart('span', 'checkbox-wrapper');
- $this->out->checkbox('notice_private',
- // TRANS: Checkbox label in widget for selecting potential addressees to mark the notice private.
- _('Private?'),
- $this->private);
+ if (common_config('notice', 'allowprivate')) {
+ $this->out->checkbox('notice_private',
+ // TRANS: Checkbox label in widget for selecting potential addressees to mark the notice private.
+ _('Private?'),
+ $this->private);
+ }
$this->out->elementEnd('span');
}
+ static function fillActivity(Action $action, Activity $act, array &$options)
+ {
+ if (!$act->context instanceof ActivityContext) {
+ $act->context = new ActivityContext();
+ }
+ self::fillOptions($action, $options);
+ if (isset($options['groups'])) {
+ foreach ($options['groups'] as $group_id) {
+ $group = User_group::getByID($group_id);
+ $act->context->attention[$group->getUri()] = $group->getObjectType();
+ }
+ }
+ if (isset($options['replies'])) {
+ foreach ($options['replies'] as $profile_uri) {
+ $profile = Profile::fromUri($profile_uri);
+ $act->context->attention[$profile->getUri()] = $profile->getObjectType();
+ }
+ }
+ }
+
static function fillOptions($action, &$options)
{
// XXX: make arg name selectable
$toArg = $action->trimmed('notice_to');
- $private = $action->boolean('notice_private');
+ $private = common_config('notice', 'allowprivate') ? $action->boolean('notice_private') : false;
if (empty($toArg)) {
return;
return (!is_null(common_current_user()));
}
+function common_local_referer()
+{
+ return parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) === common_config('site', 'server');
+}
+
function common_have_session()
{
return (0 != strcmp(session_id(), ''));
return $proto.'://'.$serverpart.'/'.$pathpart.$relative;
}
+// FIXME: Maybe this should also be able to handle non-fancy URLs with index.php?p=...
+function common_fake_local_fancy_url($url)
+{
+ /**
+ * This is a hacky fix to make URIs generated with "index.php/" match against
+ * locally stored URIs without that. So for example if the remote site is looking
+ * up the webfinger for some user and for some reason knows about https://some.example/user/1
+ * but we locally store and report only https://some.example/index.php/user/1 then they would
+ * dismiss the profile for not having an identified alias.
+ *
+ * There are various live instances where these issues occur, for various reasons.
+ * Most of them being users fiddling with configuration while already having
+ * started federating (distributing the URI to other servers) or maybe manually
+ * editing the local database.
+ */
+ if (!preg_match(
+ // [1] protocol part, we can only rewrite http/https anyway.
+ '/^(https?:\/\/)' .
+ // [2] site name.
+ // FIXME: Dunno how this acts if we're aliasing ourselves with a .onion domain etc.
+ '('.preg_quote(common_config('site', 'server'), '/').')' .
+ // [3] site path, or if that is empty just '/' (to retain the /)
+ '('.preg_quote(common_config('site', 'path') ?: '/', '/').')' .
+ // [4] + [5] extract index.php (+ possible leading double /) and the rest of the URL separately.
+ '(\/?index\.php\/)(.*)$/', $url, $matches)) {
+ // if preg_match failed to match
+ throw new Exception('No known change could be made to the URL.');
+ }
+
+ // now reconstruct the URL with everything except the "index.php/" part
+ $fancy_url = '';
+ foreach ([1,2,3,5] as $idx) {
+ $fancy_url .= $matches[$idx];
+ }
+ return $fancy_url;
+}
+
+// FIXME: Maybe this should also be able to handle non-fancy URLs with index.php?p=...
+function common_fake_local_nonfancy_url($url)
+{
+ /**
+ * This is a hacky fix to make URIs NOT generated with "index.php/" match against
+ * locally stored URIs WITH that. The reverse from the above.
+ *
+ * It will also "repair" index.php URLs with multiple / prepended. Like https://some.example///index.php/user/1
+ */
+ if (!preg_match(
+ // [1] protocol part, we can only rewrite http/https anyway.
+ '/^(https?:\/\/)' .
+ // [2] site name.
+ // FIXME: Dunno how this acts if we're aliasing ourselves with a .onion domain etc.
+ '('.preg_quote(common_config('site', 'server'), '/').')' .
+ // [3] site path, or if that is empty just '/' (to retain the /)
+ '('.preg_quote(common_config('site', 'path') ?: '/', '/').')' .
+ // [4] should be empty (might contain one or more / and then maybe also index.php). Will be overwritten.
+ // [5] will have the extracted actual URL part (besides site path)
+ '((?!index.php\/)\/*(?:index.php\/)?)(.*)$/', $url, $matches)) {
+ // if preg_match failed to match
+ throw new Exception('No known change could be made to the URL.');
+ }
+
+ $matches[4] = 'index.php/'; // inject the index.php/ rewritethingy
+
+ // remove the first element, which is the full matching string
+ array_shift($matches);
+ return implode($matches);
+}
+
function common_inject_session($url, $serverpart = null)
{
if (!common_have_session()) {
* @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);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Notice stream for favorites
protected $user_id;
protected $own;
+ protected $selectVerbs = array();
+
function __construct($user_id, $own)
{
parent::__construct();
$this->user_id = $user_id;
$this->own = $own;
-
- $this->selectVerbs = array();
}
/**
$other->getBestName());
$act->actor = $profile->asActivityObject();
- $act->object = $other->asActivityObject();
+ $act->objects[] = $other->asActivityObject();
$oprofile->notifyActivity($act, $profile);
$act->actor = $profile->asActivityObject();
$act->verb = ActivityVerb::JOIN;
- $act->object = $oprofile->asActivityObject();
+ $act->objects[] = $oprofile->asActivityObject();
$act->time = time();
// TRANS: Title for joining a remote groep.
$act->actor = $member->asActivityObject();
$act->verb = ActivityVerb::LEAVE;
- $act->object = $oprofile->asActivityObject();
+ $act->objects[] = $oprofile->asActivityObject();
$act->time = time();
// TRANS: Title for leaving a remote group.
$act->actor = $sub->asActivityObject();
$act->verb = ActivityVerb::FOLLOW;
- $act->object = $oprofile->asActivityObject();
+ $act->objects[] = $oprofile->asActivityObject();
$act->time = time();
// TRANS: Title for following a remote list.
$act->actor = $member->asActivityObject();
$act->verb = ActivityVerb::UNFOLLOW;
- $act->object = $oprofile->asActivityObject();
+ $act->objects[] = $oprofile->asActivityObject();
$act->time = time();
// TRANS: Title for unfollowing a remote list.
$notice->getUrl());
$act->actor = $profile->asActivityObject();
- $act->object = $notice->asActivityObject();
+ $act->objects[] = $notice->asActivityObject();
$oprofile->notifyActivity($act, $profile);
$profile->getBestName());
$act->actor = $profile->asActivityObject();
- $act->object = $act->actor;
+ $act->objects[] = $act->actor;
while ($oprofile->fetch()) {
$oprofile->notifyDeferred($act, $profile);
{
parent::prepare($args);
- $id = $this->trimmed('id');
+ $this->user = User::getByID($this->trimmed('id'));
- if (!$id) {
- // TRANS: Client error displayed trying to perform an action without providing an ID.
- $this->clientError(_m('No ID.'));
- }
+ $this->target = $this->user->getProfile();
- $this->user = User::getKV('id', $id);
+ // Notice must either be a) in reply to a notice by this user
+ // or b) in reply to a notice to the attention of this user
+ // or c) to the attention of this user
+ // or d) reference the user as an activity:object
+
+ $notice = null;
- if (!$this->user instanceof User) {
- // TRANS: Client error displayed when referring to a non-existing user.
- $this->clientError(_m('No such user.'));
+ if (!empty($this->activity->context->replyToID)) {
+ try {
+ $notice = Notice::getByUri($this->activity->context->replyToID);
+ } catch (NoResultException $e) {
+ $notice = false;
+ }
}
- $this->target = $this->user->getProfile();
+ if ($notice instanceof Notice &&
+ ($this->target->sameAs($notice->getProfile())
+ || in_array($this->target->getID(), $notice->getAttentionProfileIDs())
+ )) {
+ // In reply to a notice either from or mentioning this user.
+ common_debug('User is the owner or was in the attention list of thr:in-reply-to activity.');
+ } elseif (!empty($this->activity->context->attention) &&
+ array_key_exists($this->target->getUri(), $this->activity->context->attention)) {
+ // To the attention of this user.
+ common_debug('User was in attention list of salmon slap.');
+ } elseif (!empty($this->activity->objects) && $this->activity->objects[0]->id === $this->target->getUri()) {
+ // The user is the object of this slap (unfollow for example)
+ common_debug('User URI was the id of the salmon slap object.');
+ } else {
+ common_debug('User was NOT found in salmon slap context.');
+ // TRANS: Client exception.
+ throw new ClientException(_m('The owner of this salmon endpoint was not in the context of the carried slap.'));
+ }
return true;
}
throw new ClientException(_m('Cannot handle that kind of post.'));
}
- // Notice must either be a) in reply to a notice by this user
- // or b) in reply to a notice to the attention of this user
- // or c) to the attention of this user
-
- $context = $this->activity->context;
- $notice = false;
-
- if (!empty($context->replyToID)) {
- $notice = Notice::getKV('uri', $context->replyToID);
- }
-
- if ($notice instanceof Notice &&
- ($notice->profile_id == $this->target->id ||
- array_key_exists($this->target->id, $notice->getReplies())))
- {
- // In reply to a notice either from or mentioning this user.
- } elseif (!empty($context->attention) &&
- array_key_exists($this->target->getUri(), $context->attention)) {
- // To the attention of this user.
- } else {
- // TRANS: Client exception.
- throw new ClientException(_m('Not to anyone in reply to anything.'));
- }
-
try {
$this->saveNotice();
} catch (AlreadyFulfilledException $e) {
try {
$this->updateAvatar($avatar);
} catch (Exception $ex) {
- common_log(LOG_WARNING, "Exception saving OStatus profile avatar: " . $ex->getMessage());
+ common_log(LOG_WARNING, "Exception updating OStatus profile avatar: " . $ex->getMessage());
}
}
}
assert($this->target instanceof Profile);
common_debug('Got a ' . $this->activity->verb);
+
try {
$options = [ 'source' => 'ostatus' ];
common_debug('Save salmon slap directly with Notice::saveActivity for actor=='.$this->actor->getID());
$metadata = OpenGraphHelper::ogFromHtml($dom);
}
- // sometimes sites serve the path, not the full URL, for images
- // let's "be liberal in what you accept from others"!
- // add protocol and host if the thumbnail_url starts with /
- if(substr($metadata->thumbnail_url,0,1) == '/') {
- $thumbnail_url_parsed = parse_url($metadata->url);
- $metadata->thumbnail_url = $thumbnail_url_parsed['scheme']."://".$thumbnail_url_parsed['host'].$metadata->thumbnail_url;
- }
+ if (isset($metadata->thumbnail_url)) {
+ // sometimes sites serve the path, not the full URL, for images
+ // let's "be liberal in what you accept from others"!
+ // add protocol and host if the thumbnail_url starts with /
+ if(substr($metadata->thumbnail_url,0,1) == '/') {
+ $thumbnail_url_parsed = parse_url($metadata->url);
+ $metadata->thumbnail_url = $thumbnail_url_parsed['scheme']."://".$thumbnail_url_parsed['host'].$metadata->thumbnail_url;
+ }
- // some wordpress opengraph implementations sometimes return a white blank image
- // no need for us to save that!
- if($metadata->thumbnail_url == 'https://s0.wp.com/i/blank.jpg') {
- unset($metadata->thumbnail_url);
+ // some wordpress opengraph implementations sometimes return a white blank image
+ // no need for us to save that!
+ if($metadata->thumbnail_url == 'https://s0.wp.com/i/blank.jpg') {
+ unset($metadata->thumbnail_url);
+ }
}
}
$request = $this->oserver->decodeRequest();
if (in_array($request->mode, array('checkid_immediate',
'checkid_setup'))) {
- $user = common_current_user();
- if(!$user){
+ if (!$this->scoped instanceof Profile) {
if($request->immediate){
//cannot prompt the user to login in immediate mode, so answer false
$response = $this->generateDenyResponse($request);
common_set_returnto($_SERVER['REQUEST_URI']);
common_redirect(common_local_url('login'), 303);
}
- }else if(common_profile_url($user->nickname) == $request->identity || $request->idSelect()){
+ } elseif (in_array($request->identity, $this->scoped->getAliases()) || $request->idSelect()) {
$user_openid_trustroot = User_openid_trustroot::pkeyGet(
- array('user_id'=>$user->id, 'trustroot'=>$request->trust_root));
+ array('user_id'=>$this->scoped->getID(), 'trustroot'=>$request->trust_root));
if(empty($user_openid_trustroot)){
if($request->immediate){
//cannot prompt the user to trust this trust root in immediate mode, so answer false
}else{
common_ensure_session();
$_SESSION['openid_trust_root'] = $request->trust_root;
- $allowResponse = $this->generateAllowResponse($request, $user);
+ $allowResponse = $this->generateAllowResponse($request, $this->scoped);
$this->oserver->encodeResponse($allowResponse); //sign the response
$denyResponse = $this->generateDenyResponse($request);
$this->oserver->encodeResponse($denyResponse); //sign the response
// were POSTed here.
common_redirect(common_local_url('openidtrust'), 303);
}
- }else{
+ } else {
//user has previously authorized this trust root
- $response = $this->generateAllowResponse($request, $user);
- //$response = $request->answer(true, null, common_profile_url($user->nickname));
+ $response = $this->generateAllowResponse($request, $this->scoped);
}
- } else if ($request->immediate) {
+ } elseif ($request->immediate) {
$response = $this->generateDenyResponse($request);
} else {
//invalid
}
}
- function generateAllowResponse($request, $user){
- $response = $request->answer(true, null, common_profile_url($user->nickname));
+ function generateAllowResponse($request, Profile $profile){
+ $response = $request->answer(true, null, $profile->getUrl());
+ $user = $profile->getUser();
- $profile = $user->getProfile();
$sreg_data = array(
- 'fullname' => $profile->fullname,
- 'nickname' => $user->nickname,
- 'email' => $user->email,
+ 'fullname' => $profile->getFullname(),
+ 'nickname' => $profile->getNickname(),
+ 'email' => $user->email, // FIXME: Should we make the email optional?
'language' => $user->language,
'timezone' => $user->timezone);
$sreg_request = Auth_OpenID_SRegRequest::fromOpenIDRequest($request);
return true;
}
+ public function onRouterInitialized(URLMapper $m)
+ {
+ $m->connect('main/ipregistrations/:ipaddress',
+ array('action' => 'ipregistrations'),
+ array('ipaddress' => '[0-9a-f\.\:]+'));
+ }
+
/**
* Called when someone tries to register.
*
return true;
}
+ function onEndShowSections(Action $action)
+ {
+ if (!$action instanceof ShowstreamAction) {
+ // early return for actions we're not interested in
+ return true;
+ }
+
+ $target = $action->getTarget();
+ if (!$target->isSilenced()) {
+ // Only show the IP of users who are not silenced.
+ return true;
+ }
+
+ $scoped = $action->getScoped();
+ if (!$scoped->hasRight(Right::SILENCEUSER)) {
+ // only show registration IP if we have the right to silence users
+ return true;
+ }
+
+ $ri = Registration_ip::getKV('user_id', $target->getID());
+ $ipaddress = null;
+ if ($ri instanceof Registration_ip) {
+ $ipaddress = $ri->ipaddress;
+ unset($ri);
+ }
+
+ $action->elementStart('div', array('id' => 'entity_mod_log',
+ 'class' => 'section'));
+
+ $action->element('h2', null, _('Registration IP'));
+
+ // TRANS: Label for the information about which IP a users registered from.
+ $action->element('strong', null, _('Registered from:'));
+ $el = 'span';
+ $attrs = ['class'=>'ipaddress'];
+ if (!is_null($ipaddress)) {
+ $el = 'a';
+ $attrs['href'] = common_local_url('ipregistrations', array('ipaddress'=>$ipaddress));
+ }
+ $action->element($el, $attrs,
+ // TRANS: Unknown IP address.
+ $ipaddress ?: _('unknown'));
+
+ $action->elementEnd('div');
+ }
+
/**
* Called after someone registers, by any means.
*
$reg = new Registration_ip();
- $reg->user_id = $profile->id;
- $reg->ipaddress = $ipaddress;
+ $reg->user_id = $profile->getID();
+ $reg->ipaddress = mb_strtolower($ipaddress);
$reg->created = common_sql_now();
$result = $reg->insert();
--- /dev/null
+<?php
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class IpregistrationsAction extends ManagedAction
+{
+ protected $needLogin = true;
+
+ protected $ipaddress = null;
+
+ function title()
+ {
+ return sprintf(_('Registrations from IP %s'), $this->ipaddress);
+ }
+
+ protected function doPreparation()
+ {
+ if (!$this->scoped->hasRight(Right::SILENCEUSER) && !$this->scoped->hasRole(Profile_role::ADMINISTRATOR)) {
+ throw new AuthorizationException(_('You are not authorized to view this page.'));
+ }
+
+ $this->ipaddress = $this->trimmed('ipaddress');
+ $this->profile_ids = Registration_ip::usersByIP($this->ipaddress);
+ }
+
+ public function showContent()
+ {
+ $this->elementStart('ul');
+ $profile = Profile::multiGet('id', $this->profile_ids);
+ while ($profile->fetch()) {
+ $this->elementStart('li');
+ try {
+ $this->element('a', ['href'=>$profile->getUrl()], $profile->getFancyName());
+ } catch (InvalidUrlException $e) {
+ $this->element('span', null, $profile->getFancyName());
+ }
+ $this->elementEnd('li');
+ }
+ $this->elementEnd('ul');
+ }
+}
}
}
} else {
- $user = User::getKV('uri', $resource);
- if ($user instanceof User) {
+ try {
+ $user = User::getByUri($resource);
$profile = $user->getProfile();
- } else {
- // try and get it by profile url
- $profile = Profile::getKV('profileurl', $resource);
+ } catch (NoResultException $e) {
+ if (common_config('fix', 'fancyurls')) {
+ try {
+ try { // if it's a /index.php/ url
+ // common_fake_local_fancy_url can throw an exception
+ $alt_url = common_fake_local_fancy_url($resource);
+ } catch (Exception $e) { // let's try to create a fake local /index.php/ url
+ // this too if it can't do anything about the URL
+ $alt_url = common_fake_local_nonfancy_url($resource);
+ }
+
+ // and this will throw a NoResultException if not found
+ $user = User::getByUri($alt_url);
+ $profile = $user->getProfile();
+ } catch (Exception $e) {
+ // apparently we didn't get any matches with that, so continue...
+ }
+ }
}
}
+ // if we still haven't found a match...
+ if (!$profile instanceof Profile) {
+ // if our rewrite hack didn't work, try to get something by profile URL
+ $profile = Profile::getKV('profileurl', $resource);
+ }
+
if ($profile instanceof Profile) {
$target = new WebFingerResource_Profile($profile);
return false; // We got our target, stop handler execution
public function onStartShowHTML(Action $action)
{
if ($action instanceof ShowstreamAction) {
- $acct = 'acct:'. $action->getTarget()->getNickname() .'@'. common_config('site', 'server');
- $url = common_local_url('webfinger') . '?resource='.$acct;
+ $resource = $action->getTarget()->getUri();
+ $url = common_local_url('webfinger') . '?resource='.urlencode($resource);
foreach (array(Discovery::JRD_MIMETYPE, Discovery::XRD_MIMETYPE) as $type) {
header('Link: <'.$url.'>; rel="'. Discovery::LRDD_REL.'"; type="'.$type.'"', false);
public function getAliases()
{
- $aliases = array();
-
- // Add the URI as an identity, this is _not_ necessarily an HTTP url
- $uri = $this->object->getUri();
- $aliases[] = $uri;
- if (common_config('webfinger', 'http_alias')
- && strtolower(parse_url($uri, PHP_URL_SCHEME)) === 'https') {
- $aliases[] = preg_replace('/^https:/', 'http:', $uri, 1);
+ $aliases = $this->object->getAliasesWithIDs();
+
+ // Some sites have changed from http to https and still want
+ // (because remote sites look for it) verify that they are still
+ // the same identity as they were on HTTP. Should NOT be used if
+ // you've run HTTPS all the time!
+ if (common_config('webfinger', 'http_alias')) {
+ foreach ($aliases as $alias=>$id) {
+ if (!strtolower(parse_url($alias, PHP_URL_SCHEME)) === 'https') {
+ continue;
+ }
+ $aliases[preg_replace('/^https:/', 'http:', $alias, 1)] = $id;
+ }
}
- try {
- $aliases[] = $this->object->getUrl();
- } catch (InvalidUrlException $e) {
- // getUrl failed because no valid URL could be returned, just ignore it
- }
-
- return $aliases;
+ // return a unique set of aliases by extracting only the keys
+ return array_keys($aliases);
}
abstract public function updateXRD(XML_XRD $xrd);
--- /dev/null
+Initial simple way to Webfinger enable your domain -- needs PHP.
+================================================================
+
+This guide needs some updating, since it will only guide you to present
+XML data (while the curl command likely gives you JSON). The workaround
+is to simply make curl get 'webfinger.xml' instead, and/or have another
+file that contains JSON, but that requires editing the PHP file as well.
+
+Step 1
+======
+
+Put the 'dot-well-known' on your website, so it loads at:
+
+ https://example.com/.well-known/
+
+(Remember the . at the beginning of this one, which is common practice
+for "hidden" files and why we have renamed it "dot-")
+
+Step 2
+======
+
+Edit the .well-known/host-meta file and replace "example.com" with the
+domain name you're hosting the .well-known directory on.
+
+Using vim you can do this as a quick method:
+ $ vim .well-known/host-meta [ENTER]
+ :%s/example.com/domain.com/ [ENTER]
+ :wq [ENTER]
+
+Step 3
+======
+
+For each user on your site, and this might only be you...
+
+In the webfinger directory, make a copy of the example@example.com.xml file
+so that it's called (replace username and example.com with appropriate
+values, the domain name should be the same as you're "socialifying"):
+
+ username@example.com.xml
+
+Then edit the file contents, replacing "social.example.com" with your
+GNU social instance's base path, and change the user ID number (and
+nickname for the FOAF link) to that of your account on your social
+site. If you don't know your user ID number, you can see this on your
+GNU social profile page by looking at the destination URLs in the
+Feeds links.
+
+PROTIP: You can get the bulk of the contents (note the <Subject> element though)
+ from curling down your real webfinger data:
+$ curl https://social.example.com/.well-known/webfinger?resource=acct:username@social.example.com
+
+Finally
+=======
+
+Using this method, though fiddly, you can now be @user@domain without
+the need for any prefixes for subdomains, etc.
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0" xmlns:hm="http://host-meta.net/xrd/1.0">
+ <Link rel="lrdd" type="application/xrd+xml"
+ template="https://example.com/.well-known/webfinger?resource={uri}"/>
+</XRD>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
+ <Subject>acct:username@example.com</Subject>
+ <Alias>acct:username@social.example.com</Alias>
+ <Alias>https://social.example.com/user/1</Alias>
+ <Link rel="http://webfinger.net/rel/profile-page"
+ type="text/html"
+ href="https://social.example.com/user/1"/>
+
+ <Link rel="http://schemas.google.com/g/2010#updates-from"
+ type="application/atom+xml"
+ href="https://social.example.com/api/statuses/user_timeline/1.atom"/>
+
+ <!-- Is this/was this ever supported?
+ <Link rel="http://microformats.org/profile/hcard"
+ type="text/html"
+ href="https://social.example.com/hcard"/> -->
+
+ <Link rel="http://gmpg.org/xfn/11"
+ type="text/html"
+ href="https://social.example.com/user/1"/>
+
+ <Link rel="describedby"
+ type="application/rdf+xml"
+ href="https://social.example.com/username/foaf"/>
+
+ <Link rel="http://salmon-protocol.org/ns/salmon-replies"
+ href="https://social.example.com/main/salmon/user/1"/>
+
+ <Link rel="http://salmon-protocol.org/ns/salmon-mention"
+ href="https://social.example.com/main/salmon/user/1"/>
+
+ <Link rel="http://ostatus.org/schema/1.0/subscribe"
+ template="https://social.example.com/main/ostatussub?profile={uri}"/>
+</XRD>
--- /dev/null
+<?php
+
+/*
+ * GNU social
+ * Copyright (C) 2010, Free Software Foundation, Inc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+// basename should make sure we can't escape this directory
+$u = basename($_GET['resource']);
+
+if (!strpos($u, '@')) {
+ throw new Exception('Bad resource');
+ exit(1);
+}
+
+if (mb_strpos($u, 'acct:')===0) {
+ $u = substr($u, 5);
+}
+
+// Just to be a little bit safer, you know, with all the unicode stuff going on
+$u = filter_var($u, FILTER_SANITIZE_EMAIL);
+
+$f = $u . ".xml";
+
+if (file_exists($f)) {
+ header('Content-Disposition: attachment; filename="'.urlencode($f).'"');
+ header('Content-type: application/xrd+xml');
+ echo file_get_contents($f);
+}
+++ /dev/null
-Initial simple way to Webfinger enable your domain -- needs PHP.
-================================================================
-
-Step 1
-======
-
-First, put the folders 'xrd' and 'dot-well-known' on your website, so
-they load at:
-
- http://yourname.com/xrd/
-
- and
-
- http://yourname.com/.well-known/
-
- (Remember the . at the beginning of this one)
-
-NOTE: If you're using https, make sure each instance of http:// for
- your own domain ("example.com") is replaced with https://
-
-Step 2
-======
-
-Next, edit xrd/index.php and enter a secret in this line:
-
-$s = "";
-
-This can be anything you like...
-
-$s = "johnny5";
-
-or
-
-$s = "12345";
-
-It really doesn't matter too much.
-
-
-Step 3
-======
-
-Edit the .well-known/host-meta file and replace all occurrences of
-"example.com" with your domain name.
-
-Step 4
-======
-
-For each user on your site, and this might only be you...
-
-In the xrd directory, make a copy of the example@example.com.xml file
-so that it's called...
-
- yoursecretusername@domain.com.xml
-
-So, if your secret from step 2 is 'johnny5' and your name is 'ben' and
-your domain is 'titanictoycorp.biz', your file should be called
-johnny5ben@titanictoycorp.biz.xml
-
-Then edit the file, replacing "social.example.com" with your GNU
-social instance's base path, and change the user ID number (and
-nickname for the FOAF link) to that of your account on your social
-site. If you don't know your user ID number, you can see this on your
-GNU social profile page by looking at the destination URLs in the
-Feeds links.
-
-Finally
-=======
-
-Using this method, though fiddly, you can now be @user@domain without
-the need for any prefixes for subdomains, etc.
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"
- xmlns:hm="http://host-meta.net/xrd/1.0">
- <hm:Host>example.com</hm:Host>
- <Link rel="lrdd" template="http://example.com/.well-known/xrd?uri={uri}">
- <Title>WebFinger resource descriptor</Title>
- </Link>
-</XRD>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
- <Subject>acct:example@example.com</Subject>
- <Alias>acct:example@social.example.com</Alias>
- <Alias>http://social.example.com/user/1</Alias>
- <Link rel="http://webfinger.net/rel/profile-page"
- type="text/html"
- href="http://social.example.com/user/1"/>
-
- <Link rel="http://schemas.google.com/g/2010#updates-from"
- type="application/atom+xml"
- href="http://social.example.com/api/statuses/user_timeline/1.atom"/>
-
- <!-- Is this/was this ever supported?
- <Link rel="http://microformats.org/profile/hcard"
- type="text/html"
- href="http://social.example.com/hcard"/> -->
-
- <Link rel="http://gmpg.org/xfn/11"
- type="text/html"
- href="http://social.example.com/user/1"/>
-
- <Link rel="describedby"
- type="application/rdf+xml"
- href="http://social.example.com/username/foaf"/>
-
- <Link rel="http://salmon-protocol.org/ns/salmon-replies"
- href="http://social.example.com/main/salmon/user/1"/>
-
- <Link rel="http://salmon-protocol.org/ns/salmon-mention"
- href="http://social.example.com/main/salmon/user/1"/>
-
- <Link rel="http://ostatus.org/schema/1.0/subscribe"
- template="http://social.example.com/main/ostatussub?profile={uri}"/>
-</XRD>
+++ /dev/null
-<?php
-
-/*
- * GNU social
- * Copyright (C) 2010, Free Software Foundation, Inc
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
-$s = "";
-
-/* this should be a secret */
-
-$u = $_GET['uri'];
-
-$u = substr($u, 5);
-
-$f = $s . $u . ".xml";
-
-if (file_exists($f)) {
- $fh = fopen($f, 'r');
- $c = fread($fh, filesize($f));
- fclose($fh);
- header('Content-type: text/xml');
- echo $c;
-}
-
-
-?>
\ No newline at end of file
z-index: 99;
}
+.form_notice .to-selector > select {
+ max-width: 300px;
+}
+
.form_settings label[for=notice_to] {
left: 5px;
margin-left: 0px;