forked from GNUsocial/gnu-social
Merge branch 'nightly' of git.gnu.io:gnu/gnu-social into nightly
This commit is contained in:
commit
6bcfc73175
@ -27,9 +27,7 @@
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET') && !defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
/**
|
||||
* Delete a user
|
||||
@ -44,33 +42,30 @@ class DeleteuserAction extends ProfileFormAction
|
||||
{
|
||||
var $user = null;
|
||||
|
||||
/**
|
||||
* Take arguments for running
|
||||
*
|
||||
* @param array $args $_REQUEST args
|
||||
*
|
||||
* @return boolean success flag
|
||||
*/
|
||||
function prepare($args)
|
||||
function prepare(array $args=array())
|
||||
{
|
||||
if (!parent::prepare($args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cur = common_current_user();
|
||||
assert($this->scoped instanceof Profile);
|
||||
|
||||
assert(!empty($cur)); // checked by parent
|
||||
|
||||
if (!$cur->hasRight(Right::DELETEUSER)) {
|
||||
if (!$this->scoped->hasRight(Right::DELETEUSER)) {
|
||||
// TRANS: Client error displayed when trying to delete a user without having the right to delete users.
|
||||
$this->clientError(_('You cannot delete users.'));
|
||||
throw new AuthorizationException(_('You cannot delete users.'));
|
||||
}
|
||||
|
||||
$this->user = User::getKV('id', $this->profile->id);
|
||||
|
||||
if (empty($this->user)) {
|
||||
try {
|
||||
$this->user = $this->profile->getUser();
|
||||
} catch (NoSuchUserException $e) {
|
||||
// TRANS: Client error displayed when trying to delete a non-local user.
|
||||
$this->clientError(_('You can only delete local users.'));
|
||||
throw new ClientException(_('You can only delete local users.'));
|
||||
}
|
||||
|
||||
// Only administrators can delete other privileged users (such as others who have the right to silence).
|
||||
if ($this->profile->isPrivileged() && !$this->scoped->hasRole(Profile_role::ADMINISTRATOR)) {
|
||||
// TRANS: Client error displayed when trying to delete a user that has been granted moderation privileges
|
||||
throw new AuthorizationException(_('You cannot delete other privileged users.'));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -90,7 +90,7 @@ class FoafAction extends ManagedAction
|
||||
|
||||
// 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) {
|
||||
|
@ -190,6 +190,9 @@ class NewnoticeAction extends FormAction
|
||||
// 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);
|
||||
|
@ -110,7 +110,10 @@ class ProfilesettingsAction extends SettingsAction
|
||||
$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.
|
||||
@ -204,13 +207,15 @@ class ProfilesettingsAction extends SettingsAction
|
||||
(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'));
|
||||
@ -252,7 +257,6 @@ class ProfilesettingsAction extends SettingsAction
|
||||
$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');
|
||||
@ -307,6 +311,15 @@ class ProfilesettingsAction extends SettingsAction
|
||||
$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
|
||||
@ -345,7 +358,7 @@ class ProfilesettingsAction extends SettingsAction
|
||||
$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;
|
||||
|
@ -74,6 +74,7 @@ class ShownoticeAction extends ManagedAction
|
||||
}
|
||||
|
||||
$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.
|
||||
@ -213,12 +214,24 @@ class ShownoticeAction extends ManagedAction
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)'))));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,9 +27,7 @@
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
/**
|
||||
* Silence a user.
|
||||
@ -42,45 +40,11 @@ if (!defined('STATUSNET')) {
|
||||
*/
|
||||
class SilenceAction extends ProfileFormAction
|
||||
{
|
||||
/**
|
||||
* Check parameters
|
||||
*
|
||||
* @param array $args action arguments (URL, GET, POST)
|
||||
*
|
||||
* @return boolean success flag
|
||||
*/
|
||||
function prepare($args)
|
||||
{
|
||||
if (!parent::prepare($args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cur = common_current_user();
|
||||
|
||||
assert(!empty($cur)); // checked by parent
|
||||
|
||||
if (!$cur->hasRight(Right::SILENCEUSER)) {
|
||||
// TRANS: Client error displayed trying to silence a user on a site where the feature is not enabled.
|
||||
$this->clientError(_('You cannot silence users on this site.'));
|
||||
}
|
||||
|
||||
assert(!empty($this->profile)); // checked by parent
|
||||
|
||||
if ($this->profile->isSilenced()) {
|
||||
// TRANS: Client error displayed trying to silence an already silenced user.
|
||||
$this->clientError(_('User is already silenced.'));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Silence a user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function handlePost()
|
||||
{
|
||||
$this->profile->silence();
|
||||
assert($this->scoped instanceof Profile);
|
||||
assert($this->profile instanceof Profile);
|
||||
|
||||
$this->profile->silenceAs($this->scoped);
|
||||
}
|
||||
}
|
||||
|
@ -27,12 +27,10 @@
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
/**
|
||||
* Silence a user.
|
||||
* Unsilence a user.
|
||||
*
|
||||
* @category Action
|
||||
* @package StatusNet
|
||||
@ -42,45 +40,11 @@ if (!defined('STATUSNET')) {
|
||||
*/
|
||||
class UnsilenceAction extends ProfileFormAction
|
||||
{
|
||||
/**
|
||||
* Check parameters
|
||||
*
|
||||
* @param array $args action arguments (URL, GET, POST)
|
||||
*
|
||||
* @return boolean success flag
|
||||
*/
|
||||
function prepare($args)
|
||||
{
|
||||
if (!parent::prepare($args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cur = common_current_user();
|
||||
|
||||
assert(!empty($cur)); // checked by parent
|
||||
|
||||
if (!$cur->hasRight(Right::SILENCEUSER)) {
|
||||
// TRANS: Client error on page to unsilence a user when the feature is not enabled.
|
||||
$this->clientError(_('You cannot silence users on this site.'));
|
||||
}
|
||||
|
||||
assert(!empty($this->profile)); // checked by parent
|
||||
|
||||
if (!$this->profile->isSilenced()) {
|
||||
// TRANS: Client error on page to unsilence a user when the to be unsilenced user has not been silenced.
|
||||
$this->clientError(_('User is not silenced.'));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Silence a user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function handlePost()
|
||||
{
|
||||
$this->profile->unsilence();
|
||||
assert($this->scoped instanceof Profile);
|
||||
assert($this->profile instanceof Profile);
|
||||
|
||||
$this->profile->unsilenceAs($this->scoped);
|
||||
}
|
||||
}
|
||||
|
@ -412,6 +412,60 @@ abstract class Managed_DataObject extends Memcached_DataObject
|
||||
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!
|
||||
|
@ -1174,6 +1174,22 @@ class Profile extends Managed_DataObject
|
||||
}
|
||||
}
|
||||
|
||||
function silenceAs(Profile $actor)
|
||||
{
|
||||
if (!$actor->hasRight(Right::SILENCEUSER)) {
|
||||
throw new AuthorizationException(_('You cannot silence users on this site.'));
|
||||
}
|
||||
// Only administrators can silence other privileged users (such as others who have the right to silence).
|
||||
if ($this->isPrivileged() && !$actor->hasRole(Profile_role::ADMINISTRATOR)) {
|
||||
throw new AuthorizationException(_('You cannot silence other privileged users.'));
|
||||
}
|
||||
if ($this->isSilenced()) {
|
||||
// TRANS: Client error displayed trying to silence an already silenced user.
|
||||
throw new AlreadyFulfilledException(_('User is already silenced.'));
|
||||
}
|
||||
return $this->silence();
|
||||
}
|
||||
|
||||
function unsilence()
|
||||
{
|
||||
$this->revokeRole(Profile_role::SILENCED);
|
||||
@ -1182,6 +1198,19 @@ class Profile extends Managed_DataObject
|
||||
}
|
||||
}
|
||||
|
||||
function unsilenceAs(Profile $actor)
|
||||
{
|
||||
if (!$actor->hasRight(Right::SILENCEUSER)) {
|
||||
// TRANS: Client error displayed trying to unsilence a user when the user does not have the right.
|
||||
throw new AuthorizationException(_('You cannot unsilence users on this site.'));
|
||||
}
|
||||
if (!$this->isSilenced()) {
|
||||
// TRANS: Client error displayed trying to unsilence a user when the target user has not been silenced.
|
||||
throw new AlreadyFulfilledException(_('User is not silenced.'));
|
||||
}
|
||||
return $this->unsilence();
|
||||
}
|
||||
|
||||
function flushVisibility()
|
||||
{
|
||||
// Get all notices
|
||||
@ -1192,6 +1221,22 @@ class Profile extends Managed_DataObject
|
||||
}
|
||||
}
|
||||
|
||||
public function isPrivileged()
|
||||
{
|
||||
// TODO: An Event::handle so plugins can report if users are privileged.
|
||||
// The ModHelper is the only one I care about when coding this, and that
|
||||
// can be tested with Right::SILENCEUSER which I do below:
|
||||
switch (true) {
|
||||
case $this->hasRight(Right::SILENCEUSER):
|
||||
case $this->hasRole(Profile_role::MODERATOR):
|
||||
case $this->hasRole(Profile_role::ADMINISTRATOR):
|
||||
case $this->hasRole(Profile_role::OWNER):
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this user have the right to do X?
|
||||
*
|
||||
@ -1628,6 +1673,15 @@ class Profile extends Managed_DataObject
|
||||
return $profile;
|
||||
}
|
||||
|
||||
static function ensureCurrent()
|
||||
{
|
||||
$profile = self::current();
|
||||
if (!$profile instanceof Profile) {
|
||||
throw new AuthorizationException('A currently scoped profile is required.');
|
||||
}
|
||||
return $profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic function called at serialize() time.
|
||||
*
|
||||
|
@ -140,6 +140,16 @@ class User extends Managed_DataObject
|
||||
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();
|
||||
|
@ -85,8 +85,10 @@ class ApiAuthAction extends ApiAction
|
||||
// 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)) {
|
||||
|
@ -85,6 +85,12 @@ class AttachmentList extends Widget
|
||||
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) {
|
||||
|
@ -28,11 +28,7 @@
|
||||
* @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
|
||||
@ -96,9 +92,7 @@ class RawConversationNoticeStream extends NoticeStream
|
||||
$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
|
||||
|
@ -81,6 +81,9 @@ $default =
|
||||
'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
|
||||
@ -129,6 +132,7 @@ $default =
|
||||
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,
|
||||
@ -141,6 +145,10 @@ $default =
|
||||
'path' => $_path . '/avatar/',
|
||||
'ssl' => null,
|
||||
'maxsize' => 300),
|
||||
'foaf' =>
|
||||
array(
|
||||
'mbox_sha1sum' => false,
|
||||
),
|
||||
'public' =>
|
||||
array('localonly' => false,
|
||||
'blacklist' => array(),
|
||||
@ -233,6 +241,7 @@ $default =
|
||||
'application/vnd.oasis.opendocument.text-web' => 'oth',
|
||||
'application/pdf' => 'pdf',
|
||||
'application/zip' => 'zip',
|
||||
'application/xml' => 'xml',
|
||||
'image/png' => 'png',
|
||||
'image/jpeg' => 'jpg',
|
||||
'image/gif' => 'gif',
|
||||
@ -289,6 +298,7 @@ $default =
|
||||
),
|
||||
'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' =>
|
||||
|
11
lib/fullnoticestream.php
Normal file
11
lib/fullnoticestream.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
/**
|
||||
* Class for notice streams that does not filter anything out.
|
||||
*/
|
||||
abstract class FullNoticeStream extends NoticeStream
|
||||
{
|
||||
protected $selectVerbs = [];
|
||||
}
|
@ -30,7 +30,7 @@
|
||||
* @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
|
||||
@ -72,7 +72,7 @@ class InboxNoticeStream extends ScopingNoticeStream
|
||||
* @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;
|
||||
@ -84,8 +84,8 @@ class RawInboxNoticeStream extends NoticeStream
|
||||
*/
|
||||
function __construct(Profile $target)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->target = $target;
|
||||
$this->unselectVerbs = array(ActivityVerb::DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -119,12 +119,9 @@ class RawInboxNoticeStream extends NoticeStream
|
||||
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
|
||||
|
@ -23,7 +23,7 @@ class NetworkPublicNoticeStream extends ScopingNoticeStream
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
class RawNetworkPublicNoticeStream extends NoticeStream
|
||||
class RawNetworkPublicNoticeStream extends FullNoticeStream
|
||||
{
|
||||
function getNoticeIds($offset, $limit, $since_id, $max_id)
|
||||
{
|
||||
@ -46,9 +46,7 @@ class RawNetworkPublicNoticeStream extends NoticeStream
|
||||
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();
|
||||
|
||||
|
@ -180,18 +180,24 @@ class Nickname
|
||||
// All directory and file names in site root should be blacklisted
|
||||
$d = dir(INSTALLDIR);
|
||||
while (false !== ($entry = $d->read())) {
|
||||
$paths[] = $entry;
|
||||
$paths[$entry] = true;
|
||||
}
|
||||
$d->close();
|
||||
|
||||
// All top level names in the router should be blacklisted
|
||||
$router = Router::get();
|
||||
foreach (array_keys($router->m->getPaths()) as $path) {
|
||||
if (preg_match('/^\/(.*?)[\/\?]/',$path,$matches)) {
|
||||
$paths[] = $matches[1];
|
||||
foreach ($router->m->getPaths() as $path) {
|
||||
if (preg_match('/^([^\/\?]+)[\/\?]/',$path,$matches) && isset($matches[1])) {
|
||||
$paths[$matches[1]] = true;
|
||||
}
|
||||
}
|
||||
return in_array($str, $paths);
|
||||
|
||||
// FIXME: this assumes the 'path' is in the first-level directory, though common it's not certain
|
||||
foreach (['avatar', 'attachments'] as $cat) {
|
||||
$paths[basename(common_config($cat, 'path'))] = true;
|
||||
}
|
||||
|
||||
return in_array($str, array_keys($paths));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -28,11 +28,7 @@
|
||||
* @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
|
||||
@ -46,16 +42,15 @@ if (!defined('STATUSNET')) {
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,4 +69,21 @@ abstract class NoticeStream
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +101,11 @@ class ProfileFormAction extends RedirectingAction
|
||||
parent::handle($args);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
$this->handlePost();
|
||||
try {
|
||||
$this->handlePost();
|
||||
} catch (AlreadyFulfilledException $e) {
|
||||
// 'tis alright
|
||||
}
|
||||
$this->returnToPrevious();
|
||||
}
|
||||
}
|
||||
|
@ -28,11 +28,7 @@
|
||||
* @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
|
||||
@ -134,12 +130,7 @@ class RawProfileNoticeStream extends NoticeStream
|
||||
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');
|
||||
|
||||
|
@ -28,11 +28,7 @@
|
||||
* @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
|
||||
@ -66,7 +62,7 @@ class PublicNoticeStream extends ScopingNoticeStream
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
class RawPublicNoticeStream extends NoticeStream
|
||||
class RawPublicNoticeStream extends FullNoticeStream
|
||||
{
|
||||
function getNoticeIds($offset, $limit, $since_id, $max_id)
|
||||
{
|
||||
@ -87,9 +83,7 @@ class RawPublicNoticeStream extends NoticeStream
|
||||
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();
|
||||
|
||||
|
@ -28,11 +28,7 @@
|
||||
* @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
|
||||
@ -92,8 +88,20 @@ class RawReplyNoticeStream extends NoticeStream
|
||||
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');
|
||||
|
@ -28,11 +28,7 @@
|
||||
* @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
|
||||
@ -90,13 +86,22 @@ class RawTagNoticeStream extends NoticeStream
|
||||
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);
|
||||
|
@ -80,44 +80,59 @@ class ToSelector extends Widget
|
||||
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:'),
|
||||
@ -127,18 +142,40 @@ class ToSelector extends Widget
|
||||
$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;
|
||||
|
@ -66,7 +66,7 @@ class URLMapper
|
||||
throw new Exception(sprintf("Can't connect %s; path has no action.", $path));
|
||||
}
|
||||
|
||||
$allpaths[] = $path;
|
||||
$this->allpaths[] = $path;
|
||||
|
||||
$action = $args[self::ACTION];
|
||||
|
||||
|
73
lib/util.php
73
lib/util.php
@ -264,6 +264,11 @@ function common_logged_in()
|
||||
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(), ''));
|
||||
@ -1391,6 +1396,74 @@ function common_path($relative, $ssl=false, $addSession=true)
|
||||
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()) {
|
||||
|
@ -28,11 +28,7 @@
|
||||
* @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
|
||||
@ -77,14 +73,14 @@ class RawFaveNoticeStream extends NoticeStream
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Validates xmpp (for XMPP, so called JIDs)
|
||||
* @todo Validate the xmpp address
|
||||
*/
|
||||
|
||||
class HTMLPurifier_URIScheme_xmpp extends HTMLPurifier_URIScheme
|
||||
{
|
||||
/**
|
||||
* @type bool
|
||||
*/
|
||||
public $browsable = false;
|
||||
|
||||
/**
|
||||
* @type bool
|
||||
*/
|
||||
public $may_omit_host = true;
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_URI $uri
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool
|
||||
*/
|
||||
public function doValidate(&$uri, $config, $context)
|
||||
{
|
||||
$uri->userinfo = null;
|
||||
$uri->host = null;
|
||||
$uri->port = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
@ -17,9 +17,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
/**
|
||||
* @package ModHelperPlugin
|
||||
@ -45,7 +43,9 @@ class ModHelperPlugin extends Plugin
|
||||
function onUserRightsCheck($profile, $right, &$result)
|
||||
{
|
||||
if (in_array($right, self::$rights)) {
|
||||
// Hrm.... really we should confirm that the *other* user isn't privleged. :)
|
||||
// To silence a profile without accidentally silencing other
|
||||
// privileged users, always call Profile->silenceAs($actor)
|
||||
// since it checks target's privileges too.
|
||||
if ($profile->hasRole('modhelper')) {
|
||||
$result = true;
|
||||
return false;
|
||||
|
@ -1297,7 +1297,7 @@ class Ostatus_profile extends Managed_DataObject
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,8 +63,7 @@ class OpenidserverAction extends Action
|
||||
$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);
|
||||
@ -77,9 +76,9 @@ class OpenidserverAction extends Action
|
||||
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
|
||||
@ -87,7 +86,7 @@ class OpenidserverAction extends Action
|
||||
}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
|
||||
@ -101,12 +100,11 @@ class OpenidserverAction extends Action
|
||||
// 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
|
||||
@ -137,14 +135,14 @@ class OpenidserverAction extends Action
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -81,6 +81,13 @@ class RegisterThrottlePlugin extends Plugin
|
||||
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.
|
||||
*
|
||||
@ -134,6 +141,52 @@ class RegisterThrottlePlugin extends Plugin
|
||||
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.
|
||||
*
|
||||
@ -154,8 +207,8 @@ class RegisterThrottlePlugin extends Plugin
|
||||
|
||||
$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();
|
||||
|
41
plugins/RegisterThrottle/actions/ipregistrations.php
Normal file
41
plugins/RegisterThrottle/actions/ipregistrations.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
@ -100,15 +100,36 @@ class WebFingerPlugin extends Plugin
|
||||
}
|
||||
}
|
||||
} 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
|
||||
@ -144,8 +165,8 @@ class WebFingerPlugin extends Plugin
|
||||
public function onStartShowHTML($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);
|
||||
|
@ -31,23 +31,23 @@ abstract class WebFingerResource
|
||||
|
||||
public function getAliases()
|
||||
{
|
||||
$aliases = array();
|
||||
$aliases = $this->object->getAliasesWithIDs();
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
|
83
scripts/delete_orphan_files.php
Executable file
83
scripts/delete_orphan_files.php
Executable file
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/*
|
||||
* StatusNet - a distributed open-source microblogging tool
|
||||
* Copyright (C) 2008, 2009, StatusNet, 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/>.
|
||||
*/
|
||||
|
||||
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
|
||||
|
||||
$shortoptions = 'y';
|
||||
$longoptions = array('yes');
|
||||
|
||||
$helptext = <<<END_OF_HELP
|
||||
delete_orphan_files.php [options]
|
||||
Deletes all files and their File entries where there is no link to a
|
||||
Notice entry. Good for cleaning up after user deletion or so where the
|
||||
attached files weren't removed as well.
|
||||
|
||||
-y --yes do not wait for confirmation
|
||||
|
||||
Will print '.' for each deleted File entry and 'x' if it also had a locally stored file.
|
||||
|
||||
WARNING WARNING WARNING, this will also delete Qvitter files such as background etc. since
|
||||
they are not linked to notices (yet anyway).
|
||||
|
||||
END_OF_HELP;
|
||||
|
||||
require_once INSTALLDIR.'/scripts/commandline.inc';
|
||||
|
||||
print "Finding File entries that are not related to a Notice (or the notice has been deleted)...";
|
||||
$file = new File();
|
||||
$sql = 'SELECT file.* FROM file'.
|
||||
' LEFT JOIN file_to_post ON file_to_post.file_id=file.id'.
|
||||
' WHERE'.
|
||||
' NOT EXISTS (SELECT file_to_post.file_id FROM file_to_post WHERE file.id=file_to_post.file_id)'.
|
||||
' OR NOT EXISTS (SELECT notice.id FROM notice WHERE notice.id=file_to_post.post_id)'.
|
||||
' GROUP BY file.id;';
|
||||
|
||||
if ($file->query($sql) !== false) {
|
||||
print " {$file->N} found.\n";
|
||||
if ($file->N == 0) {
|
||||
exit(0);
|
||||
}
|
||||
} else {
|
||||
print "FAILED";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!have_option('y', 'yes')) {
|
||||
print "About to delete the entries along with locally stored files. Are you sure? [y/N] ";
|
||||
$response = fgets(STDIN);
|
||||
if (strtolower(trim($response)) != 'y') {
|
||||
print "Aborting.\n";
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
print "\nDeleting: ";
|
||||
while ($file->fetch()) {
|
||||
try {
|
||||
$file->getPath();
|
||||
$file->delete();
|
||||
print 'x';
|
||||
} catch (Exception $e) {
|
||||
// either FileNotFound exception or ClientException
|
||||
$file->delete();
|
||||
print '.';
|
||||
}
|
||||
}
|
||||
print "\nDONE.\n";
|
@ -4,65 +4,46 @@ 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:
|
||||
Put the 'dot-well-known' on your website, so it loads at:
|
||||
|
||||
http://yourname.com/xrd/
|
||||
https://example.com/.well-known/
|
||||
|
||||
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://
|
||||
(Remember the . at the beginning of this one, which is common practice
|
||||
for "hidden" files and why we have renamed it "dot-")
|
||||
|
||||
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.
|
||||
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
|
||||
======
|
||||
|
||||
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...
|
||||
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"):
|
||||
|
||||
yoursecretusername@domain.com.xml
|
||||
username@example.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
|
||||
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
|
||||
=======
|
||||
|
||||
|
@ -1,8 +1,5 @@
|
||||
<?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 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>
|
||||
|
@ -1,35 +1,35 @@
|
||||
<?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>
|
||||
<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="http://social.example.com/user/1"/>
|
||||
href="https://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"/>
|
||||
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="http://social.example.com/hcard"/> -->
|
||||
href="https://social.example.com/hcard"/> -->
|
||||
|
||||
<Link rel="http://gmpg.org/xfn/11"
|
||||
type="text/html"
|
||||
href="http://social.example.com/user/1"/>
|
||||
href="https://social.example.com/user/1"/>
|
||||
|
||||
<Link rel="describedby"
|
||||
type="application/rdf+xml"
|
||||
href="http://social.example.com/username/foaf"/>
|
||||
href="https://social.example.com/username/foaf"/>
|
||||
|
||||
<Link rel="http://salmon-protocol.org/ns/salmon-replies"
|
||||
href="http://social.example.com/main/salmon/user/1"/>
|
||||
href="https://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"/>
|
||||
href="https://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}"/>
|
||||
template="https://social.example.com/main/ostatussub?profile={uri}"/>
|
||||
</XRD>
|
@ -19,23 +19,25 @@
|
||||
*/
|
||||
|
||||
|
||||
$s = "";
|
||||
// basename should make sure we can't escape this directory
|
||||
$u = basename($_GET['resource']);
|
||||
|
||||
/* 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;
|
||||
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);
|
||||
}
|
@ -503,6 +503,10 @@ address .poweredby {
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.form_notice .to-selector > select {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.form_settings label[for=notice_to] {
|
||||
left: 5px;
|
||||
margin-left: 0px;
|
||||
|
Loading…
Reference in New Issue
Block a user