包含:登录失败锁定、90天密码有效期、30分钟会话超时、 强制改密、登录审计日志、屏幕水印、企业背景图、 备案信息固定底部、favicon、JS空集合保护、 会话过期体验优化(403 JSON)、display_errors 关闭、 固定 key 根治 Integrity check failed 等全部改动 注意:config.inc.php/.htaccess/.user.ini 含敏感信息, 通过 .gitignore 排除,勿推送到公开仓库。
312 lines
8.5 KiB
PHP
312 lines
8.5 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
namespace MRBS\Intl;
|
|
|
|
|
|
use Exception;
|
|
|
|
/**
|
|
* A partial, very basic, emulation of the PHP \Collator class. Some methods are not implemented, and
|
|
* some attributes are not supported. This class is only intended as a very basic fallback if the intl
|
|
* extension is not available. For best results use the intl extension.
|
|
* @see \Collator
|
|
*/
|
|
class Collator
|
|
{
|
|
public const DEFAULT_VALUE = -1;
|
|
public const PRIMARY = 0;
|
|
public const SECONDARY = 1;
|
|
public const TERTIARY = 2;
|
|
public const DEFAULT_STRENGTH = 2;
|
|
public const QUATERNARY = 3;
|
|
public const IDENTICAL = 15;
|
|
public const OFF = 16;
|
|
public const ON = 17;
|
|
public const SHIFTED = 20;
|
|
public const NON_IGNORABLE = 21;
|
|
public const LOWER_FIRST = 24;
|
|
public const UPPER_FIRST = 25;
|
|
public const FRENCH_COLLATION = 0;
|
|
public const ALTERNATE_HANDLING = 1;
|
|
public const CASE_FIRST = 2;
|
|
public const CASE_LEVEL = 3;
|
|
public const NORMALIZATION_MODE = 4;
|
|
public const STRENGTH = 5;
|
|
public const HIRAGANA_QUATERNARY_MODE = 6;
|
|
public const NUMERIC_COLLATION = 7;
|
|
public const SORT_REGULAR = 0;
|
|
public const SORT_STRING = 1;
|
|
public const SORT_NUMERIC = 2;
|
|
|
|
/**
|
|
* Default values for attributes.
|
|
* @see \Collator
|
|
*/
|
|
private const ATTRIBUTES_DEFAULT_VALUES = [
|
|
self::FRENCH_COLLATION => self::OFF,
|
|
self::ALTERNATE_HANDLING => self::NON_IGNORABLE,
|
|
self::CASE_FIRST => self::OFF,
|
|
self::CASE_LEVEL => self::OFF,
|
|
self::NORMALIZATION_MODE => self::OFF,
|
|
self::STRENGTH => self::DEFAULT_STRENGTH,
|
|
self::HIRAGANA_QUATERNARY_MODE => self::OFF,
|
|
self::NUMERIC_COLLATION => self::OFF
|
|
];
|
|
|
|
/**
|
|
* Possible values for attributes.
|
|
* @see \Collator
|
|
*/
|
|
private const ATTRIBUTES_POSSIBLE_VALUES = [
|
|
self::FRENCH_COLLATION => [self::ON, self::OFF, self::DEFAULT_VALUE],
|
|
self::ALTERNATE_HANDLING => [self::NON_IGNORABLE, self::SHIFTED, self::DEFAULT_VALUE],
|
|
self::CASE_FIRST => [self::OFF, self::LOWER_FIRST, self::UPPER_FIRST, self::DEFAULT_VALUE],
|
|
self::CASE_LEVEL => [self::OFF, self::ON, self::DEFAULT_VALUE],
|
|
self::NORMALIZATION_MODE => [self::OFF, self::ON, self::DEFAULT_VALUE],
|
|
self::STRENGTH => [self::PRIMARY, self::SECONDARY, self::TERTIARY, self::QUATERNARY, self::IDENTICAL, self::DEFAULT_STRENGTH],
|
|
self::HIRAGANA_QUATERNARY_MODE => [self::OFF, self::ON, self::DEFAULT_VALUE],
|
|
self::NUMERIC_COLLATION => [self::OFF, self::ON, self::DEFAULT_VALUE],
|
|
];
|
|
|
|
private $attributes = [];
|
|
private $locale;
|
|
|
|
|
|
/**
|
|
* @see \Collator::__construct()
|
|
*/
|
|
public function __construct(string $locale)
|
|
{
|
|
$this->locale = $locale;
|
|
// Set the default values for the attributes
|
|
foreach(self::ATTRIBUTES_DEFAULT_VALUES as $attribute => $default_value)
|
|
{
|
|
$this->setAttribute($attribute, $default_value);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @see \Collator::asort()
|
|
*/
|
|
public function asort(array &$array, int $flags = self::SORT_REGULAR): bool
|
|
{
|
|
return $this->genericSort(true, $array, $flags);
|
|
}
|
|
|
|
|
|
/**
|
|
* @see \Collator::sort()
|
|
*/
|
|
public function sort(array &$array, int $flags = self::SORT_REGULAR): bool
|
|
{
|
|
return $this->genericSort(false, $array, $flags);
|
|
}
|
|
|
|
|
|
/**
|
|
* @return int<-1,1>|false
|
|
* @see \Collator::compare()
|
|
*/
|
|
public function compare(string $string1, string $string2)
|
|
{
|
|
// Primary and secondary strengths are case-insensitive. The sort() method in this class
|
|
// cannot perform a locale aware, case-insensitive sort, so make the two strings the same
|
|
// case here, before trying the sort.
|
|
if (in_array($this->getStrength(), [self::PRIMARY, self::SECONDARY], true))
|
|
{
|
|
$string1 = mb_strtolower($string1);
|
|
$string2 = mb_strtolower($string2);
|
|
}
|
|
|
|
// Trivial case
|
|
if ($string1 === $string2)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Sort the array. If the order is reversed, then $string1 > $string2.
|
|
// (When this function is being used as a callback for usort, and if the original array
|
|
// is sorted in ascending order - which it well might be if it's the result of
|
|
// an SQL query with an ORDER BY - then it's fastest to test for $string1 > $string2
|
|
// first, as below.)
|
|
$original_array = [$string1, $string2];
|
|
$array = $original_array;
|
|
$this->sort($array);
|
|
if ($array !== $original_array)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
// Otherwise, flip the array and try again. If the order is reversed, then $string2 > $string1.
|
|
$original_array = [$string2, $string1];
|
|
$array = $original_array;
|
|
$this->sort($array);
|
|
if ($array !== $original_array)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// Otherwise they must be equal
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* @see \Collator::create()
|
|
*/
|
|
public static function create(string $locale): ?self
|
|
{
|
|
return new self($locale);
|
|
}
|
|
|
|
|
|
/**
|
|
* @return int|false
|
|
* @see \Collator::getAttribute()
|
|
*/
|
|
public function getAttribute(int $attribute)
|
|
{
|
|
if (!array_key_exists($attribute, $this->attributes))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return $this->attributes[$attribute];
|
|
}
|
|
|
|
|
|
/**
|
|
* @return int|false
|
|
* @see \Collator::getErrorCode()
|
|
*/
|
|
public function getErrorCode()
|
|
{
|
|
throw new Exception("Not yet implemented");
|
|
}
|
|
|
|
|
|
/**
|
|
* @return string|false
|
|
* @see \Collator::getErrorMessage()
|
|
*/
|
|
public function getErrorMessage()
|
|
{
|
|
throw new Exception("Not yet implemented");
|
|
}
|
|
|
|
|
|
/**
|
|
* @return string|false
|
|
* @see \Collator::getLocale()
|
|
*/
|
|
public function getLocale()
|
|
{
|
|
throw new Exception("Not yet implemented");
|
|
}
|
|
|
|
|
|
/**
|
|
* @return string|false
|
|
* @see \Collator::getSortKey()
|
|
*/
|
|
public function getSortKey(string $string)
|
|
{
|
|
throw new Exception("Not yet implemented");
|
|
}
|
|
|
|
|
|
/**
|
|
* @see \Collator::getStrength()
|
|
*/
|
|
public function getStrength(): int
|
|
{
|
|
return $this->getAttribute(self::STRENGTH);
|
|
}
|
|
|
|
|
|
/**
|
|
* @see \Collator::setAttribute()
|
|
*/
|
|
public function setAttribute(int $attribute, int $value): bool
|
|
{
|
|
if (!in_array($value, self::ATTRIBUTES_POSSIBLE_VALUES[$attribute]))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// TODO: The manual (https://www.php.net/manual/en/class.collator.php#collator.constants.french-collation)
|
|
// TODO: says that FRENCH_COLLATION "is automatically set to On for the French locales and a few others".
|
|
// TODO: However, this doesn't seem to be the case in testing: it's always Off. Probably doesn't matter
|
|
// TODO: in practice though as this emulator won't be able to do anything about it anyway.
|
|
$this->attributes[$attribute] = ($value === self::DEFAULT_VALUE) ? self::ATTRIBUTES_DEFAULT_VALUES[$attribute] : $value;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* @see \Collator::setStrength()
|
|
* @return true
|
|
*/
|
|
public function setStrength(int $strength)
|
|
{
|
|
$this->setAttribute(self::STRENGTH, $strength);
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* @see \Collator::sortWithSortKeys()
|
|
*/
|
|
public function sortWithSortKeys(array &$array): bool
|
|
{
|
|
throw new Exception("Not yet implemented");
|
|
}
|
|
|
|
|
|
private function genericSort(bool $maintain_index_association, array &$array, int $flags = self::SORT_REGULAR): bool
|
|
{
|
|
$locale_switcher = new LocaleSwitcher(LC_COLLATE, $this->locale);
|
|
$locale_switcher->switch();
|
|
|
|
// Convert the flags to the equivalent value for the ordinary functions sort()/asort().
|
|
// Note that the sort()/asort() flags can be just one of SORT_REGULAR, SORT_NUMERIC, SORT_STRING, SORT_LOCALE_STRING or SORT_NATURAL.
|
|
// Then SORT_FLAG_CASE can be combined with SORT_STRING or SORT_NATURAL.
|
|
// This means that the ordinary PHP sort() functions do not support locale-aware natural or case-insensitive sorting.
|
|
switch ($flags)
|
|
{
|
|
case self::SORT_STRING:
|
|
case self::SORT_REGULAR:
|
|
// If NUMERIC_COLLATION is on, then use SORT_NATURAL, otherwise use SORT_LOCALE_STRING
|
|
$ordinary_flags = ($this->getAttribute(self::NUMERIC_COLLATION) === self::ON) ? SORT_NATURAL : SORT_LOCALE_STRING;
|
|
break;
|
|
case self::SORT_NUMERIC:
|
|
$ordinary_flags = SORT_NUMERIC;
|
|
break;
|
|
default:
|
|
throw new \InvalidArgumentException("Invalid flags value '$flags'");
|
|
break;
|
|
}
|
|
|
|
// Primary and secondary strengths are case-insensitive.
|
|
// SORT_FLAG_CASE can only be used with SORT_STRING or SORT_NATURAL.
|
|
if (in_array($ordinary_flags, [SORT_STRING, SORT_NATURAL]) && in_array($this->getStrength(), [self::PRIMARY, self::SECONDARY], true))
|
|
{
|
|
$ordinary_flags |= SORT_FLAG_CASE;
|
|
}
|
|
|
|
if ($maintain_index_association)
|
|
{
|
|
asort($array, $ordinary_flags);
|
|
}
|
|
else
|
|
{
|
|
sort($array, $ordinary_flags);
|
|
}
|
|
|
|
$locale_switcher->restore();
|
|
return true;
|
|
}
|
|
}
|