Nickname class to encapsulate validation & common regexes for nickname formats.

This provides initial infrastructure for decoupling display names from internal canonical names, but continues to have us storing and using the canonical forms.

It should be/become possible to provide mixed-case and underscore-containing names in links, @-mention, !-group, etc, but we don't store those alternate forms generally.
This commit is contained in:
Brion Vibber 2010-11-29 14:46:10 -08:00
parent dc350b5463
commit fffc10a230
2 changed files with 180 additions and 4 deletions

176
lib/nickname.php Normal file
View 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);
}
}

View File

@ -26,18 +26,18 @@ class NicknameTest extends PHPUnit_Framework_TestCase
$exception = null;
$normalized = false;
try {
$normalized = Nickname::normalize($normalized);
$normalized = Nickname::normalize($input);
} catch (NicknameException $e) {
$exception = $e;
}
if ($expected === false) {
if ($expectedException) {
$this->assert($exception && $exception instanceof $expectedException,
$this->assertTrue($exception && $exception instanceof $expectedException,
"invalid input '$input' expected to fail with $expectedException, " .
"got " . get_class($exception) . ': ' . $exception->getMessage());
} else {
$this->assert($normalized == false,
$this->assertTrue($normalized == false,
"invalid input '$input' expected to fail");
}
} else {
@ -47,7 +47,7 @@ class NicknameTest extends PHPUnit_Framework_TestCase
} else {
$msg .= "'$normalized'";
}
$this->assertEquals($expected, $norm, $msg);
$this->assertEquals($expected, $normalized, $msg);
}
}