MRBS 1.12.2 等保2.0二级整改完整提交
包含:登录失败锁定、90天密码有效期、30分钟会话超时、 强制改密、登录审计日志、屏幕水印、企业背景图、 备案信息固定底部、favicon、JS空集合保护、 会话过期体验优化(403 JSON)、display_errors 关闭、 固定 key 根治 Integrity check failed 等全部改动 注意:config.inc.php/.htaccess/.user.ini 含敏感信息, 通过 .gitignore 排除,勿推送到公开仓库。
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace MRBS\Intl;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use MRBS\Language;
|
||||
use MRBS\System;
|
||||
|
||||
/**
|
||||
* A partial emulation of the PHP \Locale class.
|
||||
*
|
||||
* It is necessary because (a) the 'intl' extension isn't always enabled and (b) even if it is some
|
||||
* methods are only available in later versions of PHP, eg isRightToLeft is only available from PHP 8.5
|
||||
*/
|
||||
class LocaleEmulator
|
||||
{
|
||||
/**
|
||||
* A list of languages that use Right to Left text
|
||||
*/
|
||||
private const RTL_LANGUAGES = [
|
||||
// TODO: Expand this list
|
||||
'he'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Tries to find out best available locale based on HTTP "Accept-Language" header.
|
||||
*
|
||||
* @return string|false the corresponding locale identifier, or FALSE if none found
|
||||
*/
|
||||
public static function acceptFromHttp(string $header)
|
||||
{
|
||||
$accept_languages = self::toSortedArray($header);
|
||||
|
||||
foreach($accept_languages as $accept_language => $value)
|
||||
{
|
||||
if (System::isAvailableLocale($accept_language))
|
||||
{
|
||||
return $accept_language;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Returns a correctly ordered and delimited locale ID
|
||||
public static function composeLocale(array $subtags)
|
||||
{
|
||||
if (isset($subtags[Locale::GRANDFATHERED_LANG_TAG]))
|
||||
{
|
||||
return $subtags[Locale::GRANDFATHERED_LANG_TAG];
|
||||
}
|
||||
|
||||
$pieces = array();
|
||||
|
||||
foreach (array(Locale::LANG_TAG, Locale::EXTLANG_TAG, Locale::SCRIPT_TAG, Locale::REGION_TAG) as $subtag_label)
|
||||
{
|
||||
if (isset($subtags[$subtag_label]))
|
||||
{
|
||||
$pieces[] = $subtags[$subtag_label];
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists(Locale::VARIANT_TAG . '0', $subtags))
|
||||
{
|
||||
for ($i=0; $i<15; $i++)
|
||||
{
|
||||
if (isset($subtags[Locale::VARIANT_TAG . $i]))
|
||||
{
|
||||
$pieces[] = $subtags[Locale::VARIANT_TAG . $i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists(Locale::PRIVATE_TAG . '0', $subtags))
|
||||
{
|
||||
$pieces[] = 'x';
|
||||
for ($i=0; $i<15; $i++)
|
||||
{
|
||||
if (isset($subtags[Locale::PRIVATE_TAG . $i]))
|
||||
{
|
||||
$pieces[] = $subtags[Locale::PRIVATE_TAG . $i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return implode('_', $pieces);
|
||||
}
|
||||
|
||||
|
||||
// Returns a key-value array of locale ID subtag elements.
|
||||
// Parses a language tag according to BCP 47
|
||||
// See http://tools.ietf.org/html/bcp47
|
||||
public static function parseLocale(string $locale) : ?array
|
||||
{
|
||||
static $regex = array('extlang' => '/^[[:alpha:]]{3}$/', // 3ALPHA
|
||||
'script' => '/^[[:alpha:]]{4}$/', // 4ALPHA
|
||||
'region' => '/^[[:alpha:]]{2}$|^[[:digit:]]{3}$/', // 2ALPHA or 3DIGIT
|
||||
'variant' => '/^[[:alnum:]]{5,8}$|^[[:digit:]][[:alnum:]]{3}$/'); // 5*8alphanum or (DIGIT 3alphanum)
|
||||
|
||||
static $grandfathered = array('en-GB-oed', // Irregular
|
||||
'i-ami',
|
||||
'i-bnn',
|
||||
'i-default',
|
||||
'i-enochian',
|
||||
'i-hak',
|
||||
'i-klingon',
|
||||
'i-lux',
|
||||
'i-mingo',
|
||||
'i-navajo',
|
||||
'i-pwn',
|
||||
'i-tao',
|
||||
'i-tay',
|
||||
'i-tsu',
|
||||
'sgn-BE-FR',
|
||||
'sgn-BE-NL',
|
||||
'sgn-CH-DE',
|
||||
'art-lojban', // Regular
|
||||
'cel-gaulish',
|
||||
'no-bok',
|
||||
'no-nyn',
|
||||
'zh-guoyu',
|
||||
'zh-hakka',
|
||||
'zh-min',
|
||||
'zh-min-nan',
|
||||
'zh-xiang');
|
||||
|
||||
// First check for a grandfathered tag
|
||||
if (isset($locale) && in_array($locale, $grandfathered))
|
||||
{
|
||||
return array(Locale::GRANDFATHERED_LANG_TAG => $locale);
|
||||
}
|
||||
|
||||
// Otherwise parse the subtags
|
||||
$result = array();
|
||||
|
||||
if (isset($locale))
|
||||
{
|
||||
$subtags = preg_split('/[-_]/', $locale);
|
||||
}
|
||||
else
|
||||
{
|
||||
$subtags = array();
|
||||
}
|
||||
|
||||
while (null !== ($subtag = array_shift($subtags)))
|
||||
{
|
||||
// Tags are case-insensitive, so convert to lowercase before processing and then
|
||||
// later convert as necessary according to convention
|
||||
$subtag = strtolower($subtag);
|
||||
|
||||
if ($subtag == 'x')
|
||||
{
|
||||
// If the subtag is an 'x' then everything else is a private subtag,
|
||||
// even if it occurs as the first subtag:
|
||||
// "The single-character subtag 'x' as the primary subtag indicates
|
||||
// that the language tag consists solely of subtags whose meaning is
|
||||
// defined by private agreement"
|
||||
$i = 0;
|
||||
while (null !== ($subtag = array_shift($subtags)))
|
||||
{
|
||||
$result[Locale::PRIVATE_TAG . $i] = $subtag;
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
// The primary language subtag is the first subtag in a language tag and
|
||||
// cannot be omitted, with two exceptions:
|
||||
//
|
||||
// o The single-character subtag 'x' as the primary subtag ...
|
||||
// o The single-character subtag 'i' is used by some grandfathered tags ...
|
||||
elseif (!isset($result[Locale::LANG_TAG]))
|
||||
{
|
||||
// [ISO639-1] recommends that language codes be written in lowercase ('mn' Mongolian).
|
||||
// As the subtag will already be lowercase there's no need to do anything else
|
||||
$result[Locale::LANG_TAG] = $subtag;
|
||||
// Check if the next subtag looks like a language extension
|
||||
if (count($subtags) > 0)
|
||||
{
|
||||
if (preg_match($regex['extlang'], $subtags[0]))
|
||||
{
|
||||
$result[Locale::EXTLANG_TAG] = strtolower(array_shift($subtags));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Script
|
||||
elseif (preg_match($regex['script'], $subtag))
|
||||
{
|
||||
// [ISO15924] recommends that script codes use lowercase with the
|
||||
// initial letter capitalized ('Cyrl' Cyrillic).
|
||||
$result[Locale::SCRIPT_TAG] = ucfirst($subtag);
|
||||
}
|
||||
|
||||
// Region
|
||||
elseif (preg_match($regex['region'], $subtag))
|
||||
{
|
||||
// [ISO3166-1] recommends that country codes be capitalized ('MN'
|
||||
// Mongolia).
|
||||
$result[Locale::REGION_TAG] = strtoupper($subtag);
|
||||
}
|
||||
|
||||
// Variants
|
||||
elseif (preg_match($regex['variant'], $subtag))
|
||||
{
|
||||
$i = 0;
|
||||
do
|
||||
{
|
||||
// If the subtag doesn't look like a variant then we've got them all
|
||||
// and gone one subtag too far, so put it back
|
||||
if (!preg_match($regex['variant'], $subtag))
|
||||
{
|
||||
array_unshift($subtags, $subtag);
|
||||
break;
|
||||
}
|
||||
$result[Locale::VARIANT_TAG . $i] = $subtag;
|
||||
}
|
||||
while (null !== ($subtag = array_shift($subtags)));
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
trigger_error("parseLocale: could not parse subtag '$subtag'", E_USER_NOTICE);
|
||||
// This is how the PHP version behaves: if it can't parse the locale completely
|
||||
// it returns an empty array.
|
||||
$result = array();
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
public static function isRightToLeft(string $locale): bool
|
||||
{
|
||||
return in_array(mb_strtolower($locale), self::RTL_LANGUAGES);
|
||||
}
|
||||
|
||||
|
||||
// Searches the items in the array $langtag for the best match to the language range
|
||||
// specified in $locale according to RFC 4647's lookup algorithm. The langtags and
|
||||
// locale can have subtags separated by '-' or '_' and the search is case-insensitive.
|
||||
// Charsets (eg '.UTF-8') are stripped off $locale
|
||||
//
|
||||
// Returns the best match, or else an empty string if no match
|
||||
public static function lookup(array $langtag, string $locale, bool $canonicalize=false, ?string $default=null) : ?string
|
||||
{
|
||||
if ($canonicalize)
|
||||
{
|
||||
throw new InvalidArgumentException('MRBS: the MRBS version of Locale::lookup() does not yet support $canonicalize = true');
|
||||
}
|
||||
|
||||
// Get the langtags and locale in the same format, ie separated by '-' and
|
||||
// all lower case
|
||||
$standard_langtags = self::standardise($langtag);
|
||||
// Strip off any charset (eg '.UTF-8');
|
||||
$locale = preg_replace('/\..*$/', '', $locale);
|
||||
$standard_locale = self::standardise($locale);
|
||||
|
||||
// Look for a match. If there isn't one remove the last subtag from the end
|
||||
// of the locale and try again.
|
||||
while (false === ($index = array_search($standard_locale, $standard_langtags)))
|
||||
{
|
||||
if (false === ($pos = strrpos($standard_locale, '-')))
|
||||
{
|
||||
return (isset($default)) ? $default : '';
|
||||
}
|
||||
$standard_locale = substr($standard_locale, 0, $pos);
|
||||
}
|
||||
|
||||
return $langtag[$index]; // Return the match in its original format
|
||||
}
|
||||
|
||||
|
||||
// Converts $langtag, which can be a string or an array, into a standard form with
|
||||
// subtags all in lower case and separated by '-';
|
||||
private static function standardise($langtag)
|
||||
{
|
||||
$glue = ',';
|
||||
$result = (is_array($langtag)) ? implode($glue, $langtag) : $langtag;
|
||||
$result = mb_strtolower(str_replace('_', '-', $result));
|
||||
return (is_array($langtag)) ? explode($glue, $result) : $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts an Accept-Language request-header to an array of acceptable languages
|
||||
* with the language as the key and the quality value as the value, sorted in
|
||||
* decreasing order of quality value. A wildcard in the header is translated.
|
||||
*
|
||||
* @return array<string, float>
|
||||
*/
|
||||
private static function toSortedArray(string $header) : array
|
||||
{
|
||||
return Language::getQualifiers($header, true);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user