includes/AntiSpoof.php
public static function checkUnicodeStringStatus( $testName ) {
		global $wgAntiSpoofBlacklist;

		// Start with some sanity checking
		if ( !is_array( $wgAntiSpoofBlacklist ) ) {
			throw new MWException( '$wgAntiSpoofBlacklist should be an array!' );
		}
		if ( !is_string( $testName ) ) {
			return Status::newFatal( 'antispoof-badtype' );
		}

		if ( strlen( $testName ) == 0 ) {
			return Status::newFatal( 'antispoof-empty' );
		}

		foreach ( self::stringToList( $testName ) as $char ) {
			if ( in_array( $char, $wgAntiSpoofBlacklist ) ) {
				return self::badCharErr( 'antispoof-blacklisted', $char );
			}
		}

		// Perform Unicode _compatibility_ decomposition
		$testName = Validator::toNFKD( $testName );
		$testChars = self::stringToList( $testName );

		// Be paranoid: check again, just in case Unicode normalization code changes...
		foreach ( $testChars as $char ) {
			if ( in_array( $char, $wgAntiSpoofBlacklist ) ) {
				return self::badCharErr( 'antispoof-blacklisted', $char );
			}
		}

		// Check for this: should not happen in any valid Unicode string
		if ( self::getScriptCode( $testChars[0] ) == "SCRIPT_COMBINING_MARKS" ) {
			return self::badCharErr( 'antispoof-combining', $testChars[0] );
		}

		// Strip all combining characters in order to crudely strip accents
		// Note: NFKD normalization should have decomposed all accented chars earlier
		$testChars = self::stripScript( $testChars, "SCRIPT_COMBINING_MARKS" );

		$testScripts = array_map( [ 'AntiSpoof', 'getScriptCode' ], $testChars );
		$unassigned = array_search( "SCRIPT_UNASSIGNED", $testScripts );
		if ( $unassigned !== false ) {
			return self::badCharErr( 'antispoof-unassigned', $testChars[$unassigned] );
		}
		$deprecated = array_search( "SCRIPT_DEPRECATED", $testScripts );
		if ( $deprecated !== false ) {
			return self::badCharErr( 'antispoof-deprecated', $testChars[$deprecated] );
		}
		$testScripts = array_unique( $testScripts );

		// We don't mind ASCII punctuation or digits
		$testScripts = array_diff( $testScripts,
			[ "SCRIPT_ASCII_PUNCTUATION", "SCRIPT_ASCII_DIGITS" ] );

		if ( !$testScripts ) {
			return Status::newFatal( 'antispoof-noletters' );
		}

		if ( count( $testScripts ) > 1 && !self::isAllowedScriptCombination( $testScripts ) ) {
			return Status::newFatal( 'antispoof-mixedscripts' );
		}

		// At this point, we should probably check for BiDi violations if they aren't
		// caught above...

		// Squeeze out all punctuation chars
		// TODO: almost the same code occurs twice, refactor into own routine
		$testChars = self::stripScript( $testChars, "SCRIPT_ASCII_PUNCTUATION" );

		$testName = self::listToString( $testChars );

		// Replace characters in confusables set with equivalence chars
		$testName = self::normalizeString( $testName );

		// Do very simple sequence processing: "vv" -> "w", "rn" -> "m"...
		// Not exhaustive, but ups the ante...
		// Do this _after_ canonicalization: looks weird, but needed for consistency
		$testName = str_replace( 'VV', 'W', $testName );
		$testName = str_replace( 'RN', 'M', $testName );

		// Remove all remaining spaces, just in case any have snuck through...
		$testName = self::hardjoin( explode( " ", $testName ) );

		// Reduce repeated char sequences to single character
		// BUG: TODO: implement this

		if ( strlen( $testName ) < 1 ) {
			return Status::newFatal( 'antispoof-tooshort' );
		}

		// Don't ASCIIfy: we assume we are UTF-8 capable on output

		// Prepend version string, for futureproofing if this algorithm changes
		$testName = "v2:" . $testName;

		// And return the canonical version of the name
		return Status::newGood( $testName );
	}
public static function checkUnicodeStringStatus( $testName ) {
		global $wgAntiSpoofBlacklist;

		// Start with some sanity checking
		if ( !is_array( $wgAntiSpoofBlacklist ) ) {
			throw new MWException( '$wgAntiSpoofBlacklist should be an array!' );
		}
		if ( !is_string( $testName ) ) {
			return Status::newFatal( 'antispoof-badtype' );
		}

		if ( strlen( $testName ) == 0 ) {
			return Status::newFatal( 'antispoof-empty' );
		}

		foreach ( self::stringToList( $testName ) as $char ) {
			if ( in_array( $char, $wgAntiSpoofBlacklist ) ) {
				return self::badCharErr( 'antispoof-blacklisted', $char );
			}
		}

		// Perform Unicode _compatibility_ decomposition
		$testName = Validator::toNFKD( $testName );
		$testChars = self::stringToList( $testName );

		// Be paranoid: check again, just in case Unicode normalization code changes...
		foreach ( $testChars as $char ) {
			if ( in_array( $char, $wgAntiSpoofBlacklist ) ) {
				return self::badCharErr( 'antispoof-blacklisted', $char );
			}
		}

		// Check for this: should not happen in any valid Unicode string
		if ( self::getScriptCode( $testChars[0] ) == "SCRIPT_COMBINING_MARKS" ) {
			return self::badCharErr( 'antispoof-combining', $testChars[0] );
		}

		// Strip all combining characters in order to crudely strip accents
		// Note: NFKD normalization should have decomposed all accented chars earlier
		$testChars = self::stripScript( $testChars, "SCRIPT_COMBINING_MARKS" );

		$testScripts = array_map( [ 'AntiSpoof', 'getScriptCode' ], $testChars );
		$unassigned = array_search( "SCRIPT_UNASSIGNED", $testScripts );
		if ( $unassigned !== false ) {
			return self::badCharErr( 'antispoof-unassigned', $testChars[$unassigned] );
		}
		$deprecated = array_search( "SCRIPT_DEPRECATED", $testScripts );
		if ( $deprecated !== false ) {
			return self::badCharErr( 'antispoof-deprecated', $testChars[$deprecated] );
		}
		$testScripts = array_unique( $testScripts );

		// We don't mind ASCII punctuation or digits
		$testScripts = array_diff( $testScripts,
			[ "SCRIPT_ASCII_PUNCTUATION", "SCRIPT_ASCII_DIGITS" ] );

		if ( !$testScripts ) {
			return Status::newFatal( 'antispoof-noletters' );
		}

		if ( count( $testScripts ) > 1 && !self::isAllowedScriptCombination( $testScripts ) ) {
			return Status::newFatal( 'antispoof-mixedscripts' );
		}

		// At this point, we should probably check for BiDi violations if they aren't
		// caught above...

		// Squeeze out all punctuation chars
		// TODO: almost the same code occurs twice, refactor into own routine
		$testChars = self::stripScript( $testChars, "SCRIPT_ASCII_PUNCTUATION" );

		$testName = self::listToString( $testChars );

		// Replace characters in confusables set with equivalence chars
		$testName = self::normalizeString( $testName );

		// Do very simple sequence processing: "vv" -> "w", "rn" -> "m"...
		// Not exhaustive, but ups the ante...
		// Do this _after_ canonicalization: looks weird, but needed for consistency
		$testName = str_replace( 'VV', 'W', $testName );
		$testName = str_replace( 'RN', 'M', $testName );

		// Remove all remaining spaces, just in case any have snuck through...
		$testName = self::hardjoin( explode( " ", $testName ) );

		// Reduce repeated char sequences to single character
		// BUG: TODO: implement this

		if ( strlen( $testName ) < 1 ) {
			return Status::newFatal( 'antispoof-tooshort' );
		}

		// Don't ASCIIfy: we assume we are UTF-8 capable on output

		// Prepend version string, for futureproofing if this algorithm changes
		$testName = "v2:" . $testName;

		// And return the canonical version of the name
		return Status::newGood( $testName );
	}
TODO: does too much in one routine, refactor...
@param string $testName
@return Status
@since 1.32