Merge branch 'namecase' into 0.9.x
This commit is contained in:
commit
8161bf0797
|
@ -73,7 +73,7 @@ class ApiGroupCreateAction extends ApiAuthAction
|
||||||
|
|
||||||
$this->user = $this->auth_user;
|
$this->user = $this->auth_user;
|
||||||
|
|
||||||
$this->nickname = $this->arg('nickname');
|
$this->nickname = Nickname::normalize($this->arg('nickname'));
|
||||||
$this->fullname = $this->arg('full_name');
|
$this->fullname = $this->arg('full_name');
|
||||||
$this->homepage = $this->arg('homepage');
|
$this->homepage = $this->arg('homepage');
|
||||||
$this->description = $this->arg('description');
|
$this->description = $this->arg('description');
|
||||||
|
@ -150,26 +150,7 @@ class ApiGroupCreateAction extends ApiAuthAction
|
||||||
*/
|
*/
|
||||||
function validateParams()
|
function validateParams()
|
||||||
{
|
{
|
||||||
$valid = Validate::string(
|
if ($this->groupNicknameExists($this->nickname)) {
|
||||||
$this->nickname, array(
|
|
||||||
'min_length' => 1,
|
|
||||||
'max_length' => 64,
|
|
||||||
'format' => NICKNAME_FMT
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!$valid) {
|
|
||||||
$this->clientError(
|
|
||||||
// TRANS: Validation error in form for group creation.
|
|
||||||
_(
|
|
||||||
'Nickname must have only lowercase letters ' .
|
|
||||||
'and numbers and no spaces.'
|
|
||||||
),
|
|
||||||
403,
|
|
||||||
$this->format
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
} elseif ($this->groupNicknameExists($this->nickname)) {
|
|
||||||
$this->clientError(
|
$this->clientError(
|
||||||
// TRANS: Client error trying to create a group with a nickname this is already in use.
|
// TRANS: Client error trying to create a group with a nickname this is already in use.
|
||||||
_('Nickname already in use. Try another one.'),
|
_('Nickname already in use. Try another one.'),
|
||||||
|
@ -265,15 +246,7 @@ class ApiGroupCreateAction extends ApiAuthAction
|
||||||
|
|
||||||
foreach ($this->aliases as $alias) {
|
foreach ($this->aliases as $alias) {
|
||||||
|
|
||||||
$valid = Validate::string(
|
if (!Nickname::isValid($alias)) {
|
||||||
$alias, array(
|
|
||||||
'min_length' => 1,
|
|
||||||
'max_length' => 64,
|
|
||||||
'format' => NICKNAME_FMT
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!$valid) {
|
|
||||||
$this->clientError(
|
$this->clientError(
|
||||||
// TRANS: Client error shown when providing an invalid alias during group creation.
|
// TRANS: Client error shown when providing an invalid alias during group creation.
|
||||||
// TRANS: %s is the invalid alias.
|
// TRANS: %s is the invalid alias.
|
||||||
|
|
|
@ -177,21 +177,14 @@ class EditgroupAction extends GroupDesignAction
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$nickname = common_canonical_nickname($this->trimmed('nickname'));
|
$nickname = Nickname::normalize($this->trimmed('nickname'));
|
||||||
$fullname = $this->trimmed('fullname');
|
$fullname = $this->trimmed('fullname');
|
||||||
$homepage = $this->trimmed('homepage');
|
$homepage = $this->trimmed('homepage');
|
||||||
$description = $this->trimmed('description');
|
$description = $this->trimmed('description');
|
||||||
$location = $this->trimmed('location');
|
$location = $this->trimmed('location');
|
||||||
$aliasstring = $this->trimmed('aliases');
|
$aliasstring = $this->trimmed('aliases');
|
||||||
|
|
||||||
if (!Validate::string($nickname, array('min_length' => 1,
|
if ($this->nicknameExists($nickname)) {
|
||||||
'max_length' => 64,
|
|
||||||
'format' => NICKNAME_FMT))) {
|
|
||||||
// TRANS: Group edit form validation error.
|
|
||||||
$this->showForm(_('Nickname must have only lowercase letters '.
|
|
||||||
'and numbers and no spaces.'));
|
|
||||||
return;
|
|
||||||
} else if ($this->nicknameExists($nickname)) {
|
|
||||||
// TRANS: Group edit form validation error.
|
// TRANS: Group edit form validation error.
|
||||||
$this->showForm(_('Nickname already in use. Try another one.'));
|
$this->showForm(_('Nickname already in use. Try another one.'));
|
||||||
return;
|
return;
|
||||||
|
@ -241,9 +234,7 @@ class EditgroupAction extends GroupDesignAction
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($aliases as $alias) {
|
foreach ($aliases as $alias) {
|
||||||
if (!Validate::string($alias, array('min_length' => 1,
|
if (!Nickname::isValid($alias)) {
|
||||||
'max_length' => 64,
|
|
||||||
'format' => NICKNAME_FMT))) {
|
|
||||||
// TRANS: Group edit form validation error.
|
// TRANS: Group edit form validation error.
|
||||||
$this->showForm(sprintf(_('Invalid alias: "%s"'), $alias));
|
$this->showForm(sprintf(_('Invalid alias: "%s"'), $alias));
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -113,21 +113,18 @@ class NewgroupAction extends Action
|
||||||
|
|
||||||
function trySave()
|
function trySave()
|
||||||
{
|
{
|
||||||
$nickname = $this->trimmed('nickname');
|
try {
|
||||||
|
$nickname = Nickname::normalize($this->trimmed('nickname'));
|
||||||
|
} catch (NicknameException $e) {
|
||||||
|
$this->showForm($e->getMessage());
|
||||||
|
}
|
||||||
$fullname = $this->trimmed('fullname');
|
$fullname = $this->trimmed('fullname');
|
||||||
$homepage = $this->trimmed('homepage');
|
$homepage = $this->trimmed('homepage');
|
||||||
$description = $this->trimmed('description');
|
$description = $this->trimmed('description');
|
||||||
$location = $this->trimmed('location');
|
$location = $this->trimmed('location');
|
||||||
$aliasstring = $this->trimmed('aliases');
|
$aliasstring = $this->trimmed('aliases');
|
||||||
|
|
||||||
if (!Validate::string($nickname, array('min_length' => 1,
|
if ($this->nicknameExists($nickname)) {
|
||||||
'max_length' => 64,
|
|
||||||
'format' => NICKNAME_FMT))) {
|
|
||||||
// TRANS: Group create form validation error.
|
|
||||||
$this->showForm(_('Nickname must have only lowercase letters '.
|
|
||||||
'and numbers and no spaces.'));
|
|
||||||
return;
|
|
||||||
} else if ($this->nicknameExists($nickname)) {
|
|
||||||
// TRANS: Group create form validation error.
|
// TRANS: Group create form validation error.
|
||||||
$this->showForm(_('Nickname already in use. Try another one.'));
|
$this->showForm(_('Nickname already in use. Try another one.'));
|
||||||
return;
|
return;
|
||||||
|
@ -177,9 +174,7 @@ class NewgroupAction extends Action
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($aliases as $alias) {
|
foreach ($aliases as $alias) {
|
||||||
if (!Validate::string($alias, array('min_length' => 1,
|
if (!Nickname::isValid($alias)) {
|
||||||
'max_length' => 64,
|
|
||||||
'format' => NICKNAME_FMT))) {
|
|
||||||
// TRANS: Group create form validation error.
|
// TRANS: Group create form validation error.
|
||||||
$this->showForm(sprintf(_('Invalid alias: "%s"'), $alias));
|
$this->showForm(sprintf(_('Invalid alias: "%s"'), $alias));
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -225,7 +225,13 @@ class ProfilesettingsAction extends AccountSettingsAction
|
||||||
|
|
||||||
if (Event::handle('StartProfileSaveForm', array($this))) {
|
if (Event::handle('StartProfileSaveForm', array($this))) {
|
||||||
|
|
||||||
$nickname = $this->trimmed('nickname');
|
try {
|
||||||
|
$nickname = Nickname::normalize($this->trimmed('nickname'));
|
||||||
|
} catch (NicknameException $e) {
|
||||||
|
$this->showForm($e->getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$fullname = $this->trimmed('fullname');
|
$fullname = $this->trimmed('fullname');
|
||||||
$homepage = $this->trimmed('homepage');
|
$homepage = $this->trimmed('homepage');
|
||||||
$bio = $this->trimmed('bio');
|
$bio = $this->trimmed('bio');
|
||||||
|
@ -236,13 +242,7 @@ class ProfilesettingsAction extends AccountSettingsAction
|
||||||
$tagstring = $this->trimmed('tags');
|
$tagstring = $this->trimmed('tags');
|
||||||
|
|
||||||
// Some validation
|
// Some validation
|
||||||
if (!Validate::string($nickname, array('min_length' => 1,
|
if (!User::allowed_nickname($nickname)) {
|
||||||
'max_length' => 64,
|
|
||||||
'format' => NICKNAME_FMT))) {
|
|
||||||
// TRANS: Validation error in form for profile settings.
|
|
||||||
$this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
|
|
||||||
return;
|
|
||||||
} else if (!User::allowed_nickname($nickname)) {
|
|
||||||
// TRANS: Validation error in form for profile settings.
|
// TRANS: Validation error in form for profile settings.
|
||||||
$this->showForm(_('Not a valid nickname.'));
|
$this->showForm(_('Not a valid nickname.'));
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -198,7 +198,11 @@ class RegisterAction extends Action
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input scrubbing
|
// Input scrubbing
|
||||||
$nickname = common_canonical_nickname($nickname);
|
try {
|
||||||
|
$nickname = Nickname::normalize($nickname);
|
||||||
|
} catch (NicknameException $e) {
|
||||||
|
$this->showForm($e->getMessage());
|
||||||
|
}
|
||||||
$email = common_canonical_email($email);
|
$email = common_canonical_email($email);
|
||||||
|
|
||||||
if (!$this->boolean('license')) {
|
if (!$this->boolean('license')) {
|
||||||
|
@ -206,11 +210,6 @@ class RegisterAction extends Action
|
||||||
'agree to the license.'));
|
'agree to the license.'));
|
||||||
} else if ($email && !Validate::email($email, common_config('email', 'check_domain'))) {
|
} else if ($email && !Validate::email($email, common_config('email', 'check_domain'))) {
|
||||||
$this->showForm(_('Not a valid email address.'));
|
$this->showForm(_('Not a valid email address.'));
|
||||||
} else if (!Validate::string($nickname, array('min_length' => 1,
|
|
||||||
'max_length' => 64,
|
|
||||||
'format' => NICKNAME_FMT))) {
|
|
||||||
$this->showForm(_('Nickname must have only lowercase letters '.
|
|
||||||
'and numbers and no spaces.'));
|
|
||||||
} else if ($this->nicknameExists($nickname)) {
|
} else if ($this->nicknameExists($nickname)) {
|
||||||
$this->showForm(_('Nickname already in use. Try another one.'));
|
$this->showForm(_('Nickname already in use. Try another one.'));
|
||||||
} else if (!User::allowed_nickname($nickname)) {
|
} else if (!User::allowed_nickname($nickname)) {
|
||||||
|
|
|
@ -116,6 +116,16 @@ class User extends Memcached_DataObject
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the given nickname is potentially usable, or if it's
|
||||||
|
* excluded by any blacklists on this system.
|
||||||
|
*
|
||||||
|
* WARNING: INPUT IS NOT VALIDATED OR NORMALIZED. NON-NORMALIZED INPUT
|
||||||
|
* OR INVALID INPUT MAY LEAD TO FALSE RESULTS.
|
||||||
|
*
|
||||||
|
* @param string $nickname
|
||||||
|
* @return boolean true if clear, false if blacklisted
|
||||||
|
*/
|
||||||
static function allowed_nickname($nickname)
|
static function allowed_nickname($nickname)
|
||||||
{
|
{
|
||||||
// XXX: should already be validated for size, content, etc.
|
// XXX: should already be validated for size, content, etc.
|
||||||
|
|
|
@ -139,7 +139,7 @@ class Command
|
||||||
{
|
{
|
||||||
$user = null;
|
$user = null;
|
||||||
if (Event::handle('StartCommandGetUser', array($this, $arg, &$user))) {
|
if (Event::handle('StartCommandGetUser', array($this, $arg, &$user))) {
|
||||||
$user = User::staticGet('nickname', $arg);
|
$user = User::staticGet('nickname', Nickname::normalize($arg));
|
||||||
}
|
}
|
||||||
Event::handle('EndCommandGetUser', array($this, $arg, &$user));
|
Event::handle('EndCommandGetUser', array($this, $arg, &$user));
|
||||||
if (!$user){
|
if (!$user){
|
||||||
|
|
|
@ -117,6 +117,17 @@ require_once 'markdown.php';
|
||||||
|
|
||||||
// XXX: other formats here
|
// XXX: other formats here
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Avoid the NICKNAME_FMT constant; use the Nickname class instead.
|
||||||
|
*
|
||||||
|
* Nickname::DISPLAY_FMT is more suitable for inserting into regexes;
|
||||||
|
* note that it includes the [] and repeating bits, so should be wrapped
|
||||||
|
* directly in a capture paren usually.
|
||||||
|
*
|
||||||
|
* For validation, use Nickname::validate() etc.
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER);
|
define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER);
|
||||||
|
|
||||||
require_once INSTALLDIR.'/lib/util.php';
|
require_once INSTALLDIR.'/lib/util.php';
|
||||||
|
|
176
lib/nickname.php
Normal file
176
lib/nickname.php
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* StatusNet - the 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Nickname
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Regex fragment for pulling an arbitrarily-formated nickname.
|
||||||
|
*
|
||||||
|
* Not guaranteed to be valid after normalization; run the string through
|
||||||
|
* Nickname::normalize() to get the canonical form, or Nickname::validate()
|
||||||
|
* if you just need to check if it's properly formatted.
|
||||||
|
*
|
||||||
|
* This and CANONICAL_FMT replace the old NICKNAME_FMT, but be aware
|
||||||
|
* that these should not be enclosed in []s.
|
||||||
|
*/
|
||||||
|
const DISPLAY_FMT = '[0-9a-zA-Z_]+';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regex fragment for checking a canonical nickname.
|
||||||
|
*
|
||||||
|
* Any non-matching string is not a valid canonical/normalized nickname.
|
||||||
|
* Matching strings are valid and canonical form, but may still be
|
||||||
|
* unavailable for registration due to blacklisting et.
|
||||||
|
*
|
||||||
|
* Only the canonical forms should be stored as keys in the database;
|
||||||
|
* there are multiple possible denormalized forms for each valid
|
||||||
|
* canonical-form name.
|
||||||
|
*
|
||||||
|
* This and DISPLAY_FMT replace the old NICKNAME_FMT, but be aware
|
||||||
|
* that these should not be enclosed in []s.
|
||||||
|
*/
|
||||||
|
const CANONICAL_FMT = '[0-9a-z]{1,64}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of characters in a canonical-form nickname.
|
||||||
|
*/
|
||||||
|
const MAX_LEN = 64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nice simple check of whether the given string is a valid input nickname,
|
||||||
|
* which can be normalized into an internally canonical form.
|
||||||
|
*
|
||||||
|
* Note that valid nicknames may be in use or reserved.
|
||||||
|
*
|
||||||
|
* @param string $str
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public static function validate($str)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
self::normalize($str);
|
||||||
|
return true;
|
||||||
|
} catch (NicknameException $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate an input nickname string, and normalize it to its canonical form.
|
||||||
|
* The canonical form will be returned, or an exception thrown if invalid.
|
||||||
|
*
|
||||||
|
* @param string $str
|
||||||
|
* @return string Normalized canonical form of $str
|
||||||
|
*
|
||||||
|
* @throws NicknameException (base class)
|
||||||
|
* @throws NicknameInvalidException
|
||||||
|
* @throws NicknameEmptyException
|
||||||
|
* @throws NicknameTooLongException
|
||||||
|
*/
|
||||||
|
public static function normalize($str)
|
||||||
|
{
|
||||||
|
$str = trim($str);
|
||||||
|
$str = str_replace('_', '', $str);
|
||||||
|
$str = mb_strtolower($str);
|
||||||
|
|
||||||
|
$len = mb_strlen($str);
|
||||||
|
if ($len < 1) {
|
||||||
|
throw new NicknameEmptyException();
|
||||||
|
} else if ($len > self::MAX_LEN) {
|
||||||
|
throw new NicknameTooLongException();
|
||||||
|
}
|
||||||
|
if (!self::isCanonical($str)) {
|
||||||
|
throw new NicknameInvalidException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the given string a valid canonical nickname form?
|
||||||
|
*
|
||||||
|
* @param string $str
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public static function isCanonical($str)
|
||||||
|
{
|
||||||
|
return preg_match('/^(?:' . self::CANONICAL_FMT . ')$/', $str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NicknameException extends ClientException
|
||||||
|
{
|
||||||
|
function __construct($msg=null, $code=400)
|
||||||
|
{
|
||||||
|
if ($msg === null) {
|
||||||
|
$msg = $this->defaultMessage();
|
||||||
|
}
|
||||||
|
parent::__construct($msg, $code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default localized message for this type of exception.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function defaultMessage()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NicknameInvalidException extends NicknameException {
|
||||||
|
/**
|
||||||
|
* Default localized message for this type of exception.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function defaultMessage()
|
||||||
|
{
|
||||||
|
// TRANS: Validation error in form for registration, profile and group settings, etc.
|
||||||
|
return _('Nickname must have only lowercase letters and numbers and no spaces.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NicknameEmptyException extends NicknameException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Default localized message for this type of exception.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function defaultMessage()
|
||||||
|
{
|
||||||
|
// TRANS: Validation error in form for registration, profile and group settings, etc.
|
||||||
|
return _('Nickname cannot be empty.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NicknameTooLongException extends NicknameInvalidException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Default localized message for this type of exception.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function defaultMessage()
|
||||||
|
{
|
||||||
|
// TRANS: Validation error in form for registration, profile and group settings, etc.
|
||||||
|
return sprintf(_m('Nickname cannot be more than %d character long.',
|
||||||
|
'Nickname cannot be more than %d characters long.',
|
||||||
|
Nickname::MAX_LEN),
|
||||||
|
Nickname::MAX_LEN);
|
||||||
|
}
|
||||||
|
}
|
|
@ -223,10 +223,10 @@ class Router
|
||||||
$m->connect('notice/new', array('action' => 'newnotice'));
|
$m->connect('notice/new', array('action' => 'newnotice'));
|
||||||
$m->connect('notice/new?replyto=:replyto',
|
$m->connect('notice/new?replyto=:replyto',
|
||||||
array('action' => 'newnotice'),
|
array('action' => 'newnotice'),
|
||||||
array('replyto' => '[A-Za-z0-9_-]+'));
|
array('replyto' => Nickname::DISPLAY_FMT));
|
||||||
$m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
|
$m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
|
||||||
array('action' => 'newnotice'),
|
array('action' => 'newnotice'),
|
||||||
array('replyto' => '[A-Za-z0-9_-]+'),
|
array('replyto' => Nickname::DISPLAY_FMT),
|
||||||
array('inreplyto' => '[0-9]+'));
|
array('inreplyto' => '[0-9]+'));
|
||||||
|
|
||||||
$m->connect('notice/:notice/file',
|
$m->connect('notice/:notice/file',
|
||||||
|
@ -250,7 +250,7 @@ class Router
|
||||||
array('id' => '[0-9]+'));
|
array('id' => '[0-9]+'));
|
||||||
|
|
||||||
$m->connect('message/new', array('action' => 'newmessage'));
|
$m->connect('message/new', array('action' => 'newmessage'));
|
||||||
$m->connect('message/new?to=:to', array('action' => 'newmessage'), array('to' => '[A-Za-z0-9_-]+'));
|
$m->connect('message/new?to=:to', array('action' => 'newmessage'), array('to' => Nickname::DISPLAY_FMT));
|
||||||
$m->connect('message/:message',
|
$m->connect('message/:message',
|
||||||
array('action' => 'showmessage'),
|
array('action' => 'showmessage'),
|
||||||
array('message' => '[0-9]+'));
|
array('message' => '[0-9]+'));
|
||||||
|
@ -281,7 +281,7 @@ class Router
|
||||||
foreach (array('edit', 'join', 'leave', 'delete') as $v) {
|
foreach (array('edit', 'join', 'leave', 'delete') as $v) {
|
||||||
$m->connect('group/:nickname/'.$v,
|
$m->connect('group/:nickname/'.$v,
|
||||||
array('action' => $v.'group'),
|
array('action' => $v.'group'),
|
||||||
array('nickname' => '[a-zA-Z0-9]+'));
|
array('nickname' => Nickname::DISPLAY_FMT));
|
||||||
$m->connect('group/:id/id/'.$v,
|
$m->connect('group/:id/id/'.$v,
|
||||||
array('action' => $v.'group'),
|
array('action' => $v.'group'),
|
||||||
array('id' => '[0-9]+'));
|
array('id' => '[0-9]+'));
|
||||||
|
@ -290,20 +290,20 @@ class Router
|
||||||
foreach (array('members', 'logo', 'rss', 'designsettings') as $n) {
|
foreach (array('members', 'logo', 'rss', 'designsettings') as $n) {
|
||||||
$m->connect('group/:nickname/'.$n,
|
$m->connect('group/:nickname/'.$n,
|
||||||
array('action' => 'group'.$n),
|
array('action' => 'group'.$n),
|
||||||
array('nickname' => '[a-zA-Z0-9]+'));
|
array('nickname' => Nickname::DISPLAY_FMT));
|
||||||
}
|
}
|
||||||
|
|
||||||
$m->connect('group/:nickname/foaf',
|
$m->connect('group/:nickname/foaf',
|
||||||
array('action' => 'foafgroup'),
|
array('action' => 'foafgroup'),
|
||||||
array('nickname' => '[a-zA-Z0-9]+'));
|
array('nickname' => Nickname::DISPLAY_FMT));
|
||||||
|
|
||||||
$m->connect('group/:nickname/blocked',
|
$m->connect('group/:nickname/blocked',
|
||||||
array('action' => 'blockedfromgroup'),
|
array('action' => 'blockedfromgroup'),
|
||||||
array('nickname' => '[a-zA-Z0-9]+'));
|
array('nickname' => Nickname::DISPLAY_FMT));
|
||||||
|
|
||||||
$m->connect('group/:nickname/makeadmin',
|
$m->connect('group/:nickname/makeadmin',
|
||||||
array('action' => 'makeadmin'),
|
array('action' => 'makeadmin'),
|
||||||
array('nickname' => '[a-zA-Z0-9]+'));
|
array('nickname' => Nickname::DISPLAY_FMT));
|
||||||
|
|
||||||
$m->connect('group/:id/id',
|
$m->connect('group/:id/id',
|
||||||
array('action' => 'groupbyid'),
|
array('action' => 'groupbyid'),
|
||||||
|
@ -311,7 +311,7 @@ class Router
|
||||||
|
|
||||||
$m->connect('group/:nickname',
|
$m->connect('group/:nickname',
|
||||||
array('action' => 'showgroup'),
|
array('action' => 'showgroup'),
|
||||||
array('nickname' => '[a-zA-Z0-9]+'));
|
array('nickname' => Nickname::DISPLAY_FMT));
|
||||||
|
|
||||||
$m->connect('group/', array('action' => 'groups'));
|
$m->connect('group/', array('action' => 'groups'));
|
||||||
$m->connect('group', array('action' => 'groups'));
|
$m->connect('group', array('action' => 'groups'));
|
||||||
|
@ -332,7 +332,7 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/statuses/friends_timeline/:id.:format',
|
$m->connect('api/statuses/friends_timeline/:id.:format',
|
||||||
array('action' => 'ApiTimelineFriends',
|
array('action' => 'ApiTimelineFriends',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json|rss|atom)'));
|
'format' => '(xml|json|rss|atom)'));
|
||||||
|
|
||||||
$m->connect('api/statuses/home_timeline.:format',
|
$m->connect('api/statuses/home_timeline.:format',
|
||||||
|
@ -341,7 +341,7 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/statuses/home_timeline/:id.:format',
|
$m->connect('api/statuses/home_timeline/:id.:format',
|
||||||
array('action' => 'ApiTimelineHome',
|
array('action' => 'ApiTimelineHome',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json|rss|atom)'));
|
'format' => '(xml|json|rss|atom)'));
|
||||||
|
|
||||||
$m->connect('api/statuses/user_timeline.:format',
|
$m->connect('api/statuses/user_timeline.:format',
|
||||||
|
@ -350,7 +350,7 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/statuses/user_timeline/:id.:format',
|
$m->connect('api/statuses/user_timeline/:id.:format',
|
||||||
array('action' => 'ApiTimelineUser',
|
array('action' => 'ApiTimelineUser',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json|rss|atom)'));
|
'format' => '(xml|json|rss|atom)'));
|
||||||
|
|
||||||
$m->connect('api/statuses/mentions.:format',
|
$m->connect('api/statuses/mentions.:format',
|
||||||
|
@ -359,7 +359,7 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/statuses/mentions/:id.:format',
|
$m->connect('api/statuses/mentions/:id.:format',
|
||||||
array('action' => 'ApiTimelineMentions',
|
array('action' => 'ApiTimelineMentions',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json|rss|atom)'));
|
'format' => '(xml|json|rss|atom)'));
|
||||||
|
|
||||||
$m->connect('api/statuses/replies.:format',
|
$m->connect('api/statuses/replies.:format',
|
||||||
|
@ -368,7 +368,7 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/statuses/replies/:id.:format',
|
$m->connect('api/statuses/replies/:id.:format',
|
||||||
array('action' => 'ApiTimelineMentions',
|
array('action' => 'ApiTimelineMentions',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json|rss|atom)'));
|
'format' => '(xml|json|rss|atom)'));
|
||||||
|
|
||||||
$m->connect('api/statuses/retweeted_by_me.:format',
|
$m->connect('api/statuses/retweeted_by_me.:format',
|
||||||
|
@ -389,7 +389,7 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/statuses/friends/:id.:format',
|
$m->connect('api/statuses/friends/:id.:format',
|
||||||
array('action' => 'ApiUserFriends',
|
array('action' => 'ApiUserFriends',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json)'));
|
'format' => '(xml|json)'));
|
||||||
|
|
||||||
$m->connect('api/statuses/followers.:format',
|
$m->connect('api/statuses/followers.:format',
|
||||||
|
@ -398,7 +398,7 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/statuses/followers/:id.:format',
|
$m->connect('api/statuses/followers/:id.:format',
|
||||||
array('action' => 'ApiUserFollowers',
|
array('action' => 'ApiUserFollowers',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json)'));
|
'format' => '(xml|json)'));
|
||||||
|
|
||||||
$m->connect('api/statuses/show.:format',
|
$m->connect('api/statuses/show.:format',
|
||||||
|
@ -441,7 +441,7 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/users/show/:id.:format',
|
$m->connect('api/users/show/:id.:format',
|
||||||
array('action' => 'ApiUserShow',
|
array('action' => 'ApiUserShow',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json)'));
|
'format' => '(xml|json)'));
|
||||||
|
|
||||||
// direct messages
|
// direct messages
|
||||||
|
@ -479,12 +479,12 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/friendships/create/:id.:format',
|
$m->connect('api/friendships/create/:id.:format',
|
||||||
array('action' => 'ApiFriendshipsCreate',
|
array('action' => 'ApiFriendshipsCreate',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json)'));
|
'format' => '(xml|json)'));
|
||||||
|
|
||||||
$m->connect('api/friendships/destroy/:id.:format',
|
$m->connect('api/friendships/destroy/:id.:format',
|
||||||
array('action' => 'ApiFriendshipsDestroy',
|
array('action' => 'ApiFriendshipsDestroy',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json)'));
|
'format' => '(xml|json)'));
|
||||||
|
|
||||||
// Social graph
|
// Social graph
|
||||||
|
@ -541,17 +541,17 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/favorites/:id.:format',
|
$m->connect('api/favorites/:id.:format',
|
||||||
array('action' => 'ApiTimelineFavorites',
|
array('action' => 'ApiTimelineFavorites',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json|rss|atom)'));
|
'format' => '(xml|json|rss|atom)'));
|
||||||
|
|
||||||
$m->connect('api/favorites/create/:id.:format',
|
$m->connect('api/favorites/create/:id.:format',
|
||||||
array('action' => 'ApiFavoriteCreate',
|
array('action' => 'ApiFavoriteCreate',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json)'));
|
'format' => '(xml|json)'));
|
||||||
|
|
||||||
$m->connect('api/favorites/destroy/:id.:format',
|
$m->connect('api/favorites/destroy/:id.:format',
|
||||||
array('action' => 'ApiFavoriteDestroy',
|
array('action' => 'ApiFavoriteDestroy',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json)'));
|
'format' => '(xml|json)'));
|
||||||
// blocks
|
// blocks
|
||||||
|
|
||||||
|
@ -561,7 +561,7 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/blocks/create/:id.:format',
|
$m->connect('api/blocks/create/:id.:format',
|
||||||
array('action' => 'ApiBlockCreate',
|
array('action' => 'ApiBlockCreate',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json)'));
|
'format' => '(xml|json)'));
|
||||||
|
|
||||||
$m->connect('api/blocks/destroy.:format',
|
$m->connect('api/blocks/destroy.:format',
|
||||||
|
@ -570,7 +570,7 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/blocks/destroy/:id.:format',
|
$m->connect('api/blocks/destroy/:id.:format',
|
||||||
array('action' => 'ApiBlockDestroy',
|
array('action' => 'ApiBlockDestroy',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json)'));
|
'format' => '(xml|json)'));
|
||||||
// help
|
// help
|
||||||
|
|
||||||
|
@ -606,7 +606,7 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/statusnet/groups/timeline/:id.:format',
|
$m->connect('api/statusnet/groups/timeline/:id.:format',
|
||||||
array('action' => 'ApiTimelineGroup',
|
array('action' => 'ApiTimelineGroup',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json|rss|atom)'));
|
'format' => '(xml|json|rss|atom)'));
|
||||||
|
|
||||||
$m->connect('api/statusnet/groups/show.:format',
|
$m->connect('api/statusnet/groups/show.:format',
|
||||||
|
@ -615,12 +615,12 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/statusnet/groups/show/:id.:format',
|
$m->connect('api/statusnet/groups/show/:id.:format',
|
||||||
array('action' => 'ApiGroupShow',
|
array('action' => 'ApiGroupShow',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json)'));
|
'format' => '(xml|json)'));
|
||||||
|
|
||||||
$m->connect('api/statusnet/groups/join.:format',
|
$m->connect('api/statusnet/groups/join.:format',
|
||||||
array('action' => 'ApiGroupJoin',
|
array('action' => 'ApiGroupJoin',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json)'));
|
'format' => '(xml|json)'));
|
||||||
|
|
||||||
$m->connect('api/statusnet/groups/join/:id.:format',
|
$m->connect('api/statusnet/groups/join/:id.:format',
|
||||||
|
@ -629,7 +629,7 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/statusnet/groups/leave.:format',
|
$m->connect('api/statusnet/groups/leave.:format',
|
||||||
array('action' => 'ApiGroupLeave',
|
array('action' => 'ApiGroupLeave',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json)'));
|
'format' => '(xml|json)'));
|
||||||
|
|
||||||
$m->connect('api/statusnet/groups/leave/:id.:format',
|
$m->connect('api/statusnet/groups/leave/:id.:format',
|
||||||
|
@ -646,7 +646,7 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/statusnet/groups/list/:id.:format',
|
$m->connect('api/statusnet/groups/list/:id.:format',
|
||||||
array('action' => 'ApiGroupList',
|
array('action' => 'ApiGroupList',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json|rss|atom)'));
|
'format' => '(xml|json|rss|atom)'));
|
||||||
|
|
||||||
$m->connect('api/statusnet/groups/list_all.:format',
|
$m->connect('api/statusnet/groups/list_all.:format',
|
||||||
|
@ -659,7 +659,7 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/statusnet/groups/membership/:id.:format',
|
$m->connect('api/statusnet/groups/membership/:id.:format',
|
||||||
array('action' => 'ApiGroupMembership',
|
array('action' => 'ApiGroupMembership',
|
||||||
'id' => '[a-zA-Z0-9]+',
|
'id' => Nickname::DISPLAY_FMT,
|
||||||
'format' => '(xml|json)'));
|
'format' => '(xml|json)'));
|
||||||
|
|
||||||
$m->connect('api/statusnet/groups/create.:format',
|
$m->connect('api/statusnet/groups/create.:format',
|
||||||
|
@ -692,7 +692,7 @@ class Router
|
||||||
|
|
||||||
$m->connect('api/statusnet/app/service/:id.xml',
|
$m->connect('api/statusnet/app/service/:id.xml',
|
||||||
array('action' => 'ApiAtomService',
|
array('action' => 'ApiAtomService',
|
||||||
'id' => '[a-zA-Z0-9]+'));
|
'id' => Nickname::DISPLAY_FMT));
|
||||||
|
|
||||||
$m->connect('api/statusnet/app/service.xml',
|
$m->connect('api/statusnet/app/service.xml',
|
||||||
array('action' => 'ApiAtomService'));
|
array('action' => 'ApiAtomService'));
|
||||||
|
@ -789,54 +789,54 @@ class Router
|
||||||
'replies', 'inbox', 'outbox', 'microsummary', 'hcard') as $a) {
|
'replies', 'inbox', 'outbox', 'microsummary', 'hcard') as $a) {
|
||||||
$m->connect(':nickname/'.$a,
|
$m->connect(':nickname/'.$a,
|
||||||
array('action' => $a),
|
array('action' => $a),
|
||||||
array('nickname' => '[a-zA-Z0-9]{1,64}'));
|
array('nickname' => Nickname::DISPLAY_FMT));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (array('subscriptions', 'subscribers') as $a) {
|
foreach (array('subscriptions', 'subscribers') as $a) {
|
||||||
$m->connect(':nickname/'.$a.'/:tag',
|
$m->connect(':nickname/'.$a.'/:tag',
|
||||||
array('action' => $a),
|
array('action' => $a),
|
||||||
array('tag' => '[a-zA-Z0-9]+',
|
array('tag' => '[a-zA-Z0-9]+',
|
||||||
'nickname' => '[a-zA-Z0-9]{1,64}'));
|
'nickname' => Nickname::DISPLAY_FMT));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (array('rss', 'groups') as $a) {
|
foreach (array('rss', 'groups') as $a) {
|
||||||
$m->connect(':nickname/'.$a,
|
$m->connect(':nickname/'.$a,
|
||||||
array('action' => 'user'.$a),
|
array('action' => 'user'.$a),
|
||||||
array('nickname' => '[a-zA-Z0-9]{1,64}'));
|
array('nickname' => Nickname::DISPLAY_FMT));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (array('all', 'replies', 'favorites') as $a) {
|
foreach (array('all', 'replies', 'favorites') as $a) {
|
||||||
$m->connect(':nickname/'.$a.'/rss',
|
$m->connect(':nickname/'.$a.'/rss',
|
||||||
array('action' => $a.'rss'),
|
array('action' => $a.'rss'),
|
||||||
array('nickname' => '[a-zA-Z0-9]{1,64}'));
|
array('nickname' => Nickname::DISPLAY_FMT));
|
||||||
}
|
}
|
||||||
|
|
||||||
$m->connect(':nickname/favorites',
|
$m->connect(':nickname/favorites',
|
||||||
array('action' => 'showfavorites'),
|
array('action' => 'showfavorites'),
|
||||||
array('nickname' => '[a-zA-Z0-9]{1,64}'));
|
array('nickname' => Nickname::DISPLAY_FMT));
|
||||||
|
|
||||||
$m->connect(':nickname/avatar/:size',
|
$m->connect(':nickname/avatar/:size',
|
||||||
array('action' => 'avatarbynickname'),
|
array('action' => 'avatarbynickname'),
|
||||||
array('size' => '(original|96|48|24)',
|
array('size' => '(original|96|48|24)',
|
||||||
'nickname' => '[a-zA-Z0-9]{1,64}'));
|
'nickname' => Nickname::DISPLAY_FMT));
|
||||||
|
|
||||||
$m->connect(':nickname/tag/:tag/rss',
|
$m->connect(':nickname/tag/:tag/rss',
|
||||||
array('action' => 'userrss'),
|
array('action' => 'userrss'),
|
||||||
array('nickname' => '[a-zA-Z0-9]{1,64}'),
|
array('nickname' => Nickname::DISPLAY_FMT),
|
||||||
array('tag' => '[\pL\pN_\-\.]{1,64}'));
|
array('tag' => '[\pL\pN_\-\.]{1,64}'));
|
||||||
|
|
||||||
$m->connect(':nickname/tag/:tag',
|
$m->connect(':nickname/tag/:tag',
|
||||||
array('action' => 'showstream'),
|
array('action' => 'showstream'),
|
||||||
array('nickname' => '[a-zA-Z0-9]{1,64}'),
|
array('nickname' => Nickname::DISPLAY_FMT),
|
||||||
array('tag' => '[\pL\pN_\-\.]{1,64}'));
|
array('tag' => '[\pL\pN_\-\.]{1,64}'));
|
||||||
|
|
||||||
$m->connect(':nickname/rsd.xml',
|
$m->connect(':nickname/rsd.xml',
|
||||||
array('action' => 'rsd'),
|
array('action' => 'rsd'),
|
||||||
array('nickname' => '[a-zA-Z0-9]{1,64}'));
|
array('nickname' => Nickname::DISPLAY_FMT));
|
||||||
|
|
||||||
$m->connect(':nickname',
|
$m->connect(':nickname',
|
||||||
array('action' => 'showstream'),
|
array('action' => 'showstream'),
|
||||||
array('nickname' => '[a-zA-Z0-9]{1,64}'));
|
array('nickname' => Nickname::DISPLAY_FMT));
|
||||||
}
|
}
|
||||||
|
|
||||||
// user stuff
|
// user stuff
|
||||||
|
|
141
lib/util.php
141
lib/util.php
|
@ -517,14 +517,29 @@ function common_user_cache_hash($user=false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get canonical version of nickname for comparison
|
/**
|
||||||
|
* get canonical version of nickname for comparison
|
||||||
|
*
|
||||||
|
* @param string $nickname
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
|
* @throws NicknameException on invalid input
|
||||||
|
* @deprecated call Nickname::normalize() directly.
|
||||||
|
*/
|
||||||
function common_canonical_nickname($nickname)
|
function common_canonical_nickname($nickname)
|
||||||
{
|
{
|
||||||
// XXX: UTF-8 canonicalization (like combining chars)
|
return Nickname::normalize($nickname);
|
||||||
return strtolower($nickname);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get canonical version of email for comparison
|
/**
|
||||||
|
* get canonical version of email for comparison
|
||||||
|
*
|
||||||
|
* @fixme actually normalize
|
||||||
|
* @fixme reject invalid input
|
||||||
|
*
|
||||||
|
* @param string $email
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
function common_canonical_email($email)
|
function common_canonical_email($email)
|
||||||
{
|
{
|
||||||
// XXX: canonicalize UTF-8
|
// XXX: canonicalize UTF-8
|
||||||
|
@ -532,15 +547,33 @@ function common_canonical_email($email)
|
||||||
return $email;
|
return $email;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partial notice markup rendering step: build links to !group references.
|
||||||
|
*
|
||||||
|
* @param string $text partially rendered HTML
|
||||||
|
* @param Notice $notice in whose context we're working
|
||||||
|
* @return string partially rendered HTML
|
||||||
|
*/
|
||||||
function common_render_content($text, $notice)
|
function common_render_content($text, $notice)
|
||||||
{
|
{
|
||||||
$r = common_render_text($text);
|
$r = common_render_text($text);
|
||||||
$id = $notice->profile_id;
|
$id = $notice->profile_id;
|
||||||
$r = common_linkify_mentions($r, $notice);
|
$r = common_linkify_mentions($r, $notice);
|
||||||
$r = preg_replace('/(^|[\s\.\,\:\;]+)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r);
|
$r = preg_replace('/(^|[\s\.\,\:\;]+)!(' . Nickname::DISPLAY_FMT . ')/e',
|
||||||
|
"'\\1!'.common_group_link($id, '\\2')", $r);
|
||||||
return $r;
|
return $r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds @-mentions within the partially-rendered text section and
|
||||||
|
* turns them into live links.
|
||||||
|
*
|
||||||
|
* Should generally not be called except from common_render_content().
|
||||||
|
*
|
||||||
|
* @param string $text partially-rendered HTML
|
||||||
|
* @param Notice $notice in-progress or complete Notice object for context
|
||||||
|
* @return string partially-rendered HTML
|
||||||
|
*/
|
||||||
function common_linkify_mentions($text, $notice)
|
function common_linkify_mentions($text, $notice)
|
||||||
{
|
{
|
||||||
$mentions = common_find_mentions($text, $notice);
|
$mentions = common_find_mentions($text, $notice);
|
||||||
|
@ -597,6 +630,21 @@ function common_linkify_mention($mention)
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find @-mentions in the given text, using the given notice object as context.
|
||||||
|
* References will be resolved with common_relative_profile() against the user
|
||||||
|
* who posted the notice.
|
||||||
|
*
|
||||||
|
* Note the return data format is internal, to be used for building links and
|
||||||
|
* such. Should not be used directly; rather, call common_linkify_mentions().
|
||||||
|
*
|
||||||
|
* @param string $text
|
||||||
|
* @param Notice $notice notice in whose context we're building links
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
function common_find_mentions($text, $notice)
|
function common_find_mentions($text, $notice)
|
||||||
{
|
{
|
||||||
$mentions = array();
|
$mentions = array();
|
||||||
|
@ -631,20 +679,15 @@ function common_find_mentions($text, $notice)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
preg_match_all('/^T ([A-Z0-9]{1,64}) /',
|
$matches = common_find_mentions_raw($text);
|
||||||
$text,
|
|
||||||
$tmatches,
|
|
||||||
PREG_OFFSET_CAPTURE);
|
|
||||||
|
|
||||||
preg_match_all('/(?:^|\s+)@(['.NICKNAME_FMT.']{1,64})/',
|
|
||||||
$text,
|
|
||||||
$atmatches,
|
|
||||||
PREG_OFFSET_CAPTURE);
|
|
||||||
|
|
||||||
$matches = array_merge($tmatches[1], $atmatches[1]);
|
|
||||||
|
|
||||||
foreach ($matches as $match) {
|
foreach ($matches as $match) {
|
||||||
$nickname = common_canonical_nickname($match[0]);
|
try {
|
||||||
|
$nickname = Nickname::normalize($match[0]);
|
||||||
|
} catch (NicknameException $e) {
|
||||||
|
// Bogus match? Drop it.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Try to get a profile for this nickname.
|
// Try to get a profile for this nickname.
|
||||||
// Start with conversation context, then go to
|
// Start with conversation context, then go to
|
||||||
|
@ -710,6 +753,31 @@ function common_find_mentions($text, $notice)
|
||||||
return $mentions;
|
return $mentions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the actual regex pulls to find @-mentions in text.
|
||||||
|
* Should generally not be called directly; for use in common_find_mentions.
|
||||||
|
*
|
||||||
|
* @param string $text
|
||||||
|
* @return array of PCRE match arrays
|
||||||
|
*/
|
||||||
|
function common_find_mentions_raw($text)
|
||||||
|
{
|
||||||
|
$tmatches = array();
|
||||||
|
preg_match_all('/^T (' . Nickname::DISPLAY_FMT . ') /',
|
||||||
|
$text,
|
||||||
|
$tmatches,
|
||||||
|
PREG_OFFSET_CAPTURE);
|
||||||
|
|
||||||
|
$atmatches = array();
|
||||||
|
preg_match_all('/(?:^|\s+)@(' . Nickname::DISPLAY_FMT . ')\b/',
|
||||||
|
$text,
|
||||||
|
$atmatches,
|
||||||
|
PREG_OFFSET_CAPTURE);
|
||||||
|
|
||||||
|
$matches = array_merge($tmatches[1], $atmatches[1]);
|
||||||
|
return $matches;
|
||||||
|
}
|
||||||
|
|
||||||
function common_render_text($text)
|
function common_render_text($text)
|
||||||
{
|
{
|
||||||
$r = htmlspecialchars($text);
|
$r = htmlspecialchars($text);
|
||||||
|
@ -1004,6 +1072,13 @@ function common_valid_profile_tag($str)
|
||||||
return preg_match('/^[A-Za-z0-9_\-\.]{1,64}$/', $str);
|
return preg_match('/^[A-Za-z0-9_\-\.]{1,64}$/', $str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param <type> $sender_id
|
||||||
|
* @param <type> $nickname
|
||||||
|
* @return <type>
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
function common_group_link($sender_id, $nickname)
|
function common_group_link($sender_id, $nickname)
|
||||||
{
|
{
|
||||||
$sender = Profile::staticGet($sender_id);
|
$sender = Profile::staticGet($sender_id);
|
||||||
|
@ -1026,13 +1101,37 @@ function common_group_link($sender_id, $nickname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve an ambiguous profile nickname reference, checking in following order:
|
||||||
|
* - profiles that $sender subscribes to
|
||||||
|
* - profiles that subscribe to $sender
|
||||||
|
* - local user profiles
|
||||||
|
*
|
||||||
|
* WARNING: does not validate or normalize $nickname -- MUST BE PRE-VALIDATED
|
||||||
|
* OR THERE MAY BE A RISK OF SQL INJECTION ATTACKS. THIS FUNCTION DOES NOT
|
||||||
|
* ESCAPE SQL.
|
||||||
|
*
|
||||||
|
* @fixme validate input
|
||||||
|
* @fixme escape SQL
|
||||||
|
* @fixme fix or remove mystery third parameter
|
||||||
|
* @fixme is $sender a User or Profile?
|
||||||
|
*
|
||||||
|
* @param <type> $sender the user or profile in whose context we're looking
|
||||||
|
* @param string $nickname validated nickname of
|
||||||
|
* @param <type> $dt unused mystery parameter; in Notice reply-to handling a timestamp is passed.
|
||||||
|
*
|
||||||
|
* @return Profile or null
|
||||||
|
*/
|
||||||
function common_relative_profile($sender, $nickname, $dt=null)
|
function common_relative_profile($sender, $nickname, $dt=null)
|
||||||
{
|
{
|
||||||
|
// Will throw exception on invalid input.
|
||||||
|
$nickname = Nickname::normalize($nickname);
|
||||||
|
|
||||||
// Try to find profiles this profile is subscribed to that have this nickname
|
// Try to find profiles this profile is subscribed to that have this nickname
|
||||||
$recipient = new Profile();
|
$recipient = new Profile();
|
||||||
// XXX: use a join instead of a subquery
|
// XXX: use a join instead of a subquery
|
||||||
$recipient->whereAdd('EXISTS (SELECT subscribed from subscription where subscriber = '.$sender->id.' and subscribed = id)', 'AND');
|
$recipient->whereAdd('EXISTS (SELECT subscribed from subscription where subscriber = '.intval($sender->id).' and subscribed = id)', 'AND');
|
||||||
$recipient->whereAdd("nickname = '" . trim($nickname) . "'", 'AND');
|
$recipient->whereAdd("nickname = '" . $recipient->escape($nickname) . "'", 'AND');
|
||||||
if ($recipient->find(true)) {
|
if ($recipient->find(true)) {
|
||||||
// XXX: should probably differentiate between profiles with
|
// XXX: should probably differentiate between profiles with
|
||||||
// the same name by date of most recent update
|
// the same name by date of most recent update
|
||||||
|
@ -1041,8 +1140,8 @@ function common_relative_profile($sender, $nickname, $dt=null)
|
||||||
// Try to find profiles that listen to this profile and that have this nickname
|
// Try to find profiles that listen to this profile and that have this nickname
|
||||||
$recipient = new Profile();
|
$recipient = new Profile();
|
||||||
// XXX: use a join instead of a subquery
|
// XXX: use a join instead of a subquery
|
||||||
$recipient->whereAdd('EXISTS (SELECT subscriber from subscription where subscribed = '.$sender->id.' and subscriber = id)', 'AND');
|
$recipient->whereAdd('EXISTS (SELECT subscriber from subscription where subscribed = '.intval($sender->id).' and subscriber = id)', 'AND');
|
||||||
$recipient->whereAdd("nickname = '" . trim($nickname) . "'", 'AND');
|
$recipient->whereAdd("nickname = '" . $recipient->escape($nickname) . "'", 'AND');
|
||||||
if ($recipient->find(true)) {
|
if ($recipient->find(true)) {
|
||||||
// XXX: should probably differentiate between profiles with
|
// XXX: should probably differentiate between profiles with
|
||||||
// the same name by date of most recent update
|
// the same name by date of most recent update
|
||||||
|
|
|
@ -257,13 +257,10 @@ class FBConnectauthAction extends Action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$nickname = $this->trimmed('newname');
|
try {
|
||||||
|
$nickname = Nickname::normalize($this->trimmed('newname'));
|
||||||
if (!Validate::string($nickname, array('min_length' => 1,
|
} catch (NicknameException $e) {
|
||||||
'max_length' => 64,
|
$this->showForm($e->getMessage());
|
||||||
'format' => NICKNAME_FMT))) {
|
|
||||||
$this->showForm(_m('Nickname must have only lowercase letters and numbers and no spaces.'));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!User::allowed_nickname($nickname)) {
|
if (!User::allowed_nickname($nickname)) {
|
||||||
|
@ -447,9 +444,7 @@ class FBConnectauthAction extends Action
|
||||||
|
|
||||||
function isNewNickname($str)
|
function isNewNickname($str)
|
||||||
{
|
{
|
||||||
if (!Validate::string($str, array('min_length' => 1,
|
if (!Nickname::isValid($str)) {
|
||||||
'max_length' => 64,
|
|
||||||
'format' => NICKNAME_FMT))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!User::allowed_nickname($str)) {
|
if (!User::allowed_nickname($str)) {
|
||||||
|
|
|
@ -324,12 +324,10 @@ class FacebookfinishloginAction extends Action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$nickname = $this->trimmed('newname');
|
try {
|
||||||
|
$nickname = Nickname::normalize($this->trimmed('newname'));
|
||||||
if (!Validate::string($nickname, array('min_length' => 1,
|
} catch (NicknameException $e) {
|
||||||
'max_length' => 64,
|
$this->showForm($e->getMessage());
|
||||||
'format' => NICKNAME_FMT))) {
|
|
||||||
$this->showForm(_m('Nickname must have only lowercase letters and numbers and no spaces.'));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -639,16 +637,7 @@ class FacebookfinishloginAction extends Action
|
||||||
*/
|
*/
|
||||||
function isNewNickname($str)
|
function isNewNickname($str)
|
||||||
{
|
{
|
||||||
if (
|
if (!Nickname::isValid($str)) {
|
||||||
!Validate::string(
|
|
||||||
$str,
|
|
||||||
array(
|
|
||||||
'min_length' => 1,
|
|
||||||
'max_length' => 64,
|
|
||||||
'format' => NICKNAME_FMT
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,10 +67,10 @@ class MapstractionPlugin extends Plugin
|
||||||
{
|
{
|
||||||
$m->connect(':nickname/all/map',
|
$m->connect(':nickname/all/map',
|
||||||
array('action' => 'allmap'),
|
array('action' => 'allmap'),
|
||||||
array('nickname' => '['.NICKNAME_FMT.']{1,64}'));
|
array('nickname' => Nickname::DISPLAY_FMT));
|
||||||
$m->connect(':nickname/map',
|
$m->connect(':nickname/map',
|
||||||
array('action' => 'usermap'),
|
array('action' => 'usermap'),
|
||||||
array('nickname' => '['.NICKNAME_FMT.']{1,64}'));
|
array('nickname' => Nickname::DISPLAY_FMT));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ class MapAction extends OwnerDesignAction
|
||||||
parent::prepare($args);
|
parent::prepare($args);
|
||||||
|
|
||||||
$nickname_arg = $this->arg('nickname');
|
$nickname_arg = $this->arg('nickname');
|
||||||
$nickname = common_canonical_nickname($nickname_arg);
|
$nickname = Nickname::normalize($nickname_arg);
|
||||||
|
|
||||||
// Permanent redirect on non-canonical nickname
|
// Permanent redirect on non-canonical nickname
|
||||||
|
|
||||||
|
|
|
@ -272,13 +272,10 @@ class FinishopenidloginAction extends Action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$nickname = $this->trimmed('newname');
|
try {
|
||||||
|
$nickname = Nickname::validate($this->trimmed('newname'));
|
||||||
if (!Validate::string($nickname, array('min_length' => 1,
|
} catch (NicknameException $e) {
|
||||||
'max_length' => 64,
|
$this->showForm($e->getMessage());
|
||||||
'format' => NICKNAME_FMT))) {
|
|
||||||
// TRANS: OpenID plugin message. The entered new user name did not conform to the requirements.
|
|
||||||
$this->showForm(_m('Nickname must have only lowercase letters and numbers and no spaces.'));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,9 +460,7 @@ class FinishopenidloginAction extends Action
|
||||||
|
|
||||||
function isNewNickname($str)
|
function isNewNickname($str)
|
||||||
{
|
{
|
||||||
if (!Validate::string($str, array('min_length' => 1,
|
if (!Nickname::isValid($str)) {
|
||||||
'max_length' => 64,
|
|
||||||
'format' => NICKNAME_FMT))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!User::allowed_nickname($str)) {
|
if (!User::allowed_nickname($str)) {
|
||||||
|
|
|
@ -441,12 +441,10 @@ class TwitterauthorizationAction extends Action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$nickname = $this->trimmed('newname');
|
try {
|
||||||
|
$nickname = Nickname::normalize($this->trimmed('newname'));
|
||||||
if (!Validate::string($nickname, array('min_length' => 1,
|
} catch (NicknameException $e) {
|
||||||
'max_length' => 64,
|
$this->showForm($e->getMessage());
|
||||||
'format' => NICKNAME_FMT))) {
|
|
||||||
$this->showForm(_m('Nickname must have only lowercase letters and numbers and no spaces.'));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -619,9 +617,7 @@ class TwitterauthorizationAction extends Action
|
||||||
|
|
||||||
function isNewNickname($str)
|
function isNewNickname($str)
|
||||||
{
|
{
|
||||||
if (!Validate::string($str, array('min_length' => 1,
|
if (!Nickname::isValid($str)) {
|
||||||
'max_length' => 64,
|
|
||||||
'format' => NICKNAME_FMT))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!User::allowed_nickname($str)) {
|
if (!User::allowed_nickname($str)) {
|
||||||
|
|
111
tests/NicknameTest.php
Normal file
111
tests/NicknameTest.php
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
|
||||||
|
print "This script must be run from the command line\n";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
|
||||||
|
define('STATUSNET', true);
|
||||||
|
define('LACONICA', true);
|
||||||
|
|
||||||
|
require_once INSTALLDIR . '/lib/common.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test cases for nickname validity and normalization.
|
||||||
|
*/
|
||||||
|
class NicknameTest extends PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Basic test using Nickname::normalize()
|
||||||
|
*
|
||||||
|
* @dataProvider provider
|
||||||
|
*/
|
||||||
|
public function testBasic($input, $expected, $expectedException=null)
|
||||||
|
{
|
||||||
|
$exception = null;
|
||||||
|
$normalized = false;
|
||||||
|
try {
|
||||||
|
$normalized = Nickname::normalize($input);
|
||||||
|
} catch (NicknameException $e) {
|
||||||
|
$exception = $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($expected === false) {
|
||||||
|
if ($expectedException) {
|
||||||
|
$this->assertTrue($exception && $exception instanceof $expectedException,
|
||||||
|
"invalid input '$input' expected to fail with $expectedException, " .
|
||||||
|
"got " . get_class($exception) . ': ' . $exception->getMessage());
|
||||||
|
} else {
|
||||||
|
$this->assertTrue($normalized == false,
|
||||||
|
"invalid input '$input' expected to fail");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$msg = "normalized input nickname '$input' expected to normalize to '$expected', got ";
|
||||||
|
if ($exception) {
|
||||||
|
$msg .= get_class($exception) . ': ' . $exception->getMessage();
|
||||||
|
} else {
|
||||||
|
$msg .= "'$normalized'";
|
||||||
|
}
|
||||||
|
$this->assertEquals($expected, $normalized, $msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test on the regex matching used in common_find_mentions
|
||||||
|
* (testing on the full notice rendering is difficult as it needs
|
||||||
|
* to be able to pull from global state)
|
||||||
|
*
|
||||||
|
* @dataProvider provider
|
||||||
|
*/
|
||||||
|
public function testAtReply($input, $expected, $expectedException=null)
|
||||||
|
{
|
||||||
|
if ($expected == false) {
|
||||||
|
// nothing to do
|
||||||
|
} else {
|
||||||
|
$text = "@{$input} awesome! :)";
|
||||||
|
$matches = common_find_mentions_raw($text);
|
||||||
|
$this->assertEquals(1, count($matches));
|
||||||
|
$this->assertEquals($expected, Nickname::normalize($matches[0][0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function provider()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('evan', 'evan'),
|
||||||
|
|
||||||
|
// Case and underscore variants
|
||||||
|
array('Evan', 'evan'),
|
||||||
|
array('EVAN', 'evan'),
|
||||||
|
array('ev_an', 'evan'),
|
||||||
|
array('E__V_an', 'evan'),
|
||||||
|
array('evan1', 'evan1'),
|
||||||
|
array('evan_1', 'evan1'),
|
||||||
|
array('0x20', '0x20'),
|
||||||
|
array('1234', '1234'), // should this be allowed though? :)
|
||||||
|
array('12__34', '1234'),
|
||||||
|
|
||||||
|
// Some (currently) invalid chars...
|
||||||
|
array('^#@&^#@', false, 'NicknameInvalidException'), // all invalid :D
|
||||||
|
array('ev.an', false, 'NicknameInvalidException'),
|
||||||
|
array('ev/an', false, 'NicknameInvalidException'),
|
||||||
|
array('ev an', false, 'NicknameInvalidException'),
|
||||||
|
array('ev-an', false, 'NicknameInvalidException'),
|
||||||
|
|
||||||
|
// Non-ASCII letters; currently not allowed, in future
|
||||||
|
// we'll add them at least with conversion to ASCII.
|
||||||
|
// Not much use until we have storage of display names,
|
||||||
|
// though.
|
||||||
|
array('évan', false, 'NicknameInvalidException'), // so far...
|
||||||
|
array('Évan', false, 'NicknameInvalidException'), // so far...
|
||||||
|
|
||||||
|
// Length checks
|
||||||
|
array('', false, 'NicknameEmptyException'),
|
||||||
|
array('___', false, 'NicknameEmptyException'),
|
||||||
|
array('eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'), // 64 chars
|
||||||
|
array('eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee_', 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'), // the _ will be trimmed off, remaining valid
|
||||||
|
array('eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', false, 'NicknameTooLongException'), // 65 chars -- too long
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user