Switch to Sasae.

This commit is contained in:
flash 2023-08-31 21:33:34 +00:00
parent 9682fa595a
commit 1da6470928
15 changed files with 503 additions and 410 deletions

View File

@ -3,7 +3,7 @@
"prefer-stable": true,
"require": {
"flashwave/index": "dev-master",
"twig/twig": "^3.0",
"flashwave/sasae": "dev-master",
"erusev/parsedown": "~1.6",
"chillerlan/php-qrcode": "^4.3",
"symfony/mailer": "^6.0",

202
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "c76eb19b0eb02b6a9fccb4b63f50d165",
"content-hash": "313710f128265bd0f0f9e7687058324b",
"packages": [
{
"name": "chillerlan/php-qrcode",
@ -348,7 +348,7 @@
"source": {
"type": "git",
"url": "https://git.flash.moe/flash/index.git",
"reference": "6a38f803f4b3e49296f7472743e7c683c496ec19"
"reference": "1172115e699acf44580ffcdcf86c9e3987d2f969"
},
"require": {
"ext-mbstring": "*",
@ -386,7 +386,48 @@
],
"description": "Composer package for the common library for my projects.",
"homepage": "https://railgun.sh/index",
"time": "2023-08-22T00:04:20+00:00"
"time": "2023-08-28T13:58:51+00:00"
},
{
"name": "flashwave/sasae",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://git.flash.moe/flash/sasae.git",
"reference": "739669fc8ce7ea862ed2129cb24976ceebd0f4c7"
},
"require": {
"flashwave/index": "dev-master",
"php": ">=8.2",
"twig/html-extra": "^3.7",
"twig/twig": "^3.7"
},
"require-dev": {
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.2"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
"Sasae\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"bsd-3-clause-clear"
],
"authors": [
{
"name": "flashwave",
"email": "packagist@flash.moe",
"homepage": "https://flash.moe",
"role": "mom"
}
],
"description": "A wrapper for Twig with added common functionality.",
"homepage": "https://railgun.sh/sasae",
"time": "2023-08-24T23:24:45+00:00"
},
{
"name": "matomo/device-detector",
@ -1049,16 +1090,16 @@
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.27.0",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"shasum": ""
},
"require": {
@ -1073,7 +1114,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@ -1111,7 +1152,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
},
"funding": [
{
@ -1127,20 +1168,20 @@
"type": "tidelift"
}
],
"time": "2022-11-03T14:55:06+00:00"
"time": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.27.0",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
"reference": "639084e360537a19f9ee352433b84ce831f3d2da"
"reference": "ecaafce9f77234a6a449d29e49267ba10499116d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da",
"reference": "639084e360537a19f9ee352433b84ce831f3d2da",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/ecaafce9f77234a6a449d29e49267ba10499116d",
"reference": "ecaafce9f77234a6a449d29e49267ba10499116d",
"shasum": ""
},
"require": {
@ -1154,7 +1195,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@ -1198,7 +1239,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.27.0"
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.28.0"
},
"funding": [
{
@ -1214,20 +1255,20 @@
"type": "tidelift"
}
],
"time": "2022-11-03T14:55:06+00:00"
"time": "2023-01-26T09:30:37+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.27.0",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6"
"reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6",
"reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
"reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
"shasum": ""
},
"require": {
@ -1239,7 +1280,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@ -1282,7 +1323,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0"
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0"
},
"funding": [
{
@ -1298,20 +1339,20 @@
"type": "tidelift"
}
],
"time": "2022-11-03T14:55:06+00:00"
"time": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.27.0",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
"reference": "42292d99c55abe617799667f454222c54c60e229"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
"reference": "42292d99c55abe617799667f454222c54c60e229",
"shasum": ""
},
"require": {
@ -1326,7 +1367,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@ -1365,7 +1406,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
},
"funding": [
{
@ -1381,20 +1422,20 @@
"type": "tidelift"
}
],
"time": "2022-11-03T14:55:06+00:00"
"time": "2023-07-28T09:04:16+00:00"
},
{
"name": "symfony/polyfill-php72",
"version": "v1.27.0",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php72.git",
"reference": "869329b1e9894268a8a61dabb69153029b7a8c97"
"reference": "70f4aebd92afca2f865444d30a4d2151c13c3179"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97",
"reference": "869329b1e9894268a8a61dabb69153029b7a8c97",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/70f4aebd92afca2f865444d30a4d2151c13c3179",
"reference": "70f4aebd92afca2f865444d30a4d2151c13c3179",
"shasum": ""
},
"require": {
@ -1403,7 +1444,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@ -1441,7 +1482,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0"
"source": "https://github.com/symfony/polyfill-php72/tree/v1.28.0"
},
"funding": [
{
@ -1457,7 +1498,7 @@
"type": "tidelift"
}
],
"time": "2022-11-03T14:55:06+00:00"
"time": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/service-contracts",
@ -1542,17 +1583,81 @@
"time": "2023-05-23T14:45:45+00:00"
},
{
"name": "twig/twig",
"version": "v3.7.0",
"name": "twig/html-extra",
"version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "5cf942bbab3df42afa918caeba947f1b690af64b"
"url": "https://github.com/twigphp/html-extra.git",
"reference": "95ceb36e70fa8d07af08cf5135ecbf5e0bd8f386"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/5cf942bbab3df42afa918caeba947f1b690af64b",
"reference": "5cf942bbab3df42afa918caeba947f1b690af64b",
"url": "https://api.github.com/repos/twigphp/html-extra/zipball/95ceb36e70fa8d07af08cf5135ecbf5e0bd8f386",
"reference": "95ceb36e70fa8d07af08cf5135ecbf5e0bd8f386",
"shasum": ""
},
"require": {
"php": ">=7.1.3",
"symfony/mime": "^5.4|^6.0",
"twig/twig": "^2.7|^3.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^5.4|^6.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Twig\\Extra\\Html\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
}
],
"description": "A Twig extension for HTML",
"homepage": "https://twig.symfony.com",
"keywords": [
"html",
"twig"
],
"support": {
"source": "https://github.com/twigphp/html-extra/tree/v3.7.1"
},
"funding": [
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/twig/twig",
"type": "tidelift"
}
],
"time": "2023-07-29T15:34:56+00:00"
},
{
"name": "twig/twig",
"version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554",
"reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554",
"shasum": ""
},
"require": {
@ -1562,7 +1667,7 @@
},
"require-dev": {
"psr/container": "^1.0|^2.0",
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0"
"symfony/phpunit-bridge": "^5.4.9|^6.3"
},
"type": "library",
"autoload": {
@ -1598,7 +1703,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.7.0"
"source": "https://github.com/twigphp/Twig/tree/v3.7.1"
},
"funding": [
{
@ -1610,7 +1715,7 @@
"type": "tidelift"
}
],
"time": "2023-07-26T07:16:09+00:00"
"time": "2023-08-28T11:09:02+00:00"
}
],
"packages-dev": [
@ -1680,7 +1785,8 @@
"aliases": [],
"minimum-stability": "dev",
"stability-flags": {
"flashwave/index": 20
"flashwave/index": 20,
"flashwave/sasae": 20
},
"prefer-stable": true,
"prefer-lowest": false,

View File

@ -5,6 +5,7 @@ use RuntimeException;
use Misuzu\Auth\AuthTokenBuilder;
use Misuzu\Auth\AuthTokenCookie;
use Misuzu\Auth\AuthTokenInfo;
use Sasae\SasaeEnvironment;
require_once __DIR__ . '/../misuzu.php';
@ -38,44 +39,6 @@ if(file_exists(MSZ_ROOT . '/.migrating')) {
exit;
}
if(!MSZ_DEBUG) {
$twigCacheDirSfx = GitInfo::hash(true);
if(empty($twigCacheDirSfx))
$twigCacheDirSfx = md5(MSZ_ROOT);
$twigCache = sys_get_temp_dir() . '/msz-tpl-' . $twigCacheDirSfx;
if(!is_dir($twigCache))
mkdir($twigCache, 0775, true);
}
$globals = $cfg->getValues([
['site.name:s', 'Misuzu'],
'site.desc:s',
'site.url:s',
'eeprom.path:s',
'eeprom.app:s',
['csrf.secret:s', 'soup'],
]);
Template::init($msz, $twigCache ?? null, MSZ_DEBUG);
Template::set('globals', [
'site_name' => $globals['site.name'],
'site_description' => $globals['site.desc'],
'site_url' => $globals['site.url'],
'eeprom' => [
'path' => $globals['eeprom.path'],
'app' => $globals['eeprom.app'],
],
]);
$mszAssetsInfo = json_decode(file_get_contents(MSZ_ASSETS . '/current.json'));
if(!empty($mszAssetsInfo))
Template::set('assets', $mszAssetsInfo);
unset($mszAssetsInfo);
Template::addPath(MSZ_TEMPLATES);
$tokenPacker = $msz->createAuthTokenPacker();
if(filter_has_var(INPUT_COOKIE, 'msz_auth'))
@ -156,95 +119,21 @@ if($tokenInfo->hasUserId() && $tokenInfo->hasSessionToken()) {
$msz->getAuthInfo()->setInfo($tokenInfo, $userInfo, $sessionInfo, $userInfoReal);
if(!empty($userInfo))
$userInfo = $users->getUser((string)$userInfo->getId(), 'id');
if(!empty($userInfoReal))
$userInfoReal = $users->getUser((string)$userInfoReal->getId(), 'id');
CSRF::init(
$globals['csrf.secret'],
$cfg->getString('csrf.secret', 'soup'),
($msz->isLoggedIn() ? $sessionInfo->getToken() : $_SERVER['REMOTE_ADDR'])
);
if(!empty($userInfo)) {
Template::set('current_user', $userInfo);
Template::set('current_user_ban_info', $msz->tryGetActiveBan());
}
if(!empty($userInfoReal)) {
Template::set('current_user_real', $userInfoReal);
Template::set('current_user_real_colour', $users->getUserColour($userInfoReal));
}
$inManageMode = str_starts_with($_SERVER['REQUEST_URI'], '/manage');
Template::set('header_menu', $msz->getHeaderMenu($userInfo ?? null));
Template::set('user_menu', $msz->getUserMenu($userInfo ?? null, $inManageMode));
Template::set('display_timings_info', MSZ_DEBUG || $msz->getAuthInfo()->getPerms('global')->check(Perm::G_TIMINGS_VIEW));
if($inManageMode) {
$hasManageAccess = false;
if($msz->isLoggedIn() && !$msz->hasActiveBan()) {
$manageUser = $msz->getActiveUser();
$manageUserId = $manageUser->getId();
$manageGlobalPerms = $msz->getAuthInfo()->getPerms('global');
if($manageGlobalPerms->check(Perm::G_IS_JANITOR)) {
$hasManageAccess = true;
$manageMenu = [
'General' => [
'Overview' => url('manage-general-overview'),
],
];
if($manageGlobalPerms->check(Perm::G_LOGS_VIEW))
$manageMenu['General']['Logs'] = url('manage-general-logs');
if($manageGlobalPerms->check(Perm::G_EMOTES_MANAGE))
$manageMenu['General']['Emoticons'] = url('manage-general-emoticons');
if($manageGlobalPerms->check(Perm::G_CONFIG_MANAGE))
$manageMenu['General']['Settings'] = url('manage-general-settings');
$manageUserPerms = $msz->getAuthInfo()->getPerms('user');
if($manageUserPerms->check(Perm::U_USERS_MANAGE))
$manageMenu['Users & Roles']['Users'] = url('manage-users');
if($manageUserPerms->check(Perm::U_ROLES_MANAGE))
$manageMenu['Users & Roles']['Roles'] = url('manage-roles');
if($manageUserPerms->check(Perm::U_NOTES_MANAGE))
$manageMenu['Users & Roles']['Notes'] = url('manage-users-notes');
if($manageUserPerms->check(Perm::U_WARNINGS_MANAGE))
$manageMenu['Users & Roles']['Warnings'] = url('manage-users-warnings');
if($manageUserPerms->check(Perm::U_BANS_MANAGE))
$manageMenu['Users & Roles']['Bans'] = url('manage-users-bans');
if($manageGlobalPerms->check(Perm::G_NEWS_POSTS_MANAGE))
$manageMenu['News']['Posts'] = url('manage-news-posts');
if($manageGlobalPerms->check(Perm::G_NEWS_CATEGORIES_MANAGE))
$manageMenu['News']['Categories'] = url('manage-news-categories');
if($manageGlobalPerms->check(Perm::G_FORUM_CATEGORIES_MANAGE))
$manageMenu['Forum']['Permission Calculator'] = url('manage-forum-categories');
if($manageGlobalPerms->check(Perm::G_FORUM_TOPIC_REDIRS_MANAGE))
$manageMenu['Forum']['Topic Redirects'] = url('manage-forum-topic-redirs');
if($manageGlobalPerms->check(Perm::G_CL_CHANGES_MANAGE))
$manageMenu['Changelog']['Changes'] = url('manage-changelog-changes');
if($manageGlobalPerms->check(Perm::G_CL_TAGS_MANAGE))
$manageMenu['Changelog']['Tags'] = url('manage-changelog-tags');
Template::set('manage_menu', $manageMenu);
}
}
if(!$hasManageAccess)
Template::throwError(403);
}
$msz->startTemplating();
$mszRequestPath = $request->getPath();
$mszLegacyPathPrefix = MSZ_PUBLIC . '-legacy/';
$mszLegacyPath = realpath($mszLegacyPathPrefix . $mszRequestPath);
if(!empty($mszLegacyPath) && str_starts_with($mszLegacyPath, $mszLegacyPathPrefix)) {
if(str_starts_with($mszRequestPath, '/manage') && !$msz->hasManageAccess())
Template::throwError(403);
if(is_dir($mszLegacyPath))
$mszLegacyPath .= '/index.php';
@ -254,5 +143,5 @@ if(!empty($mszLegacyPath) && str_starts_with($mszLegacyPath, $mszLegacyPathPrefi
}
}
$msz->setUpHttp();
$msz->dispatchHttp($request);
$msz->startRouter();
$msz->dispatchRouter($request);

View File

@ -172,9 +172,11 @@ class DbConfig implements IConfig {
if(is_string($spec)) {
$name = $spec;
$default = null;
$alias = null;
} elseif(is_array($spec) && !empty($spec)) {
$name = $spec[0];
$default = $spec[1] ?? null;
$alias = $spec[2] ?? null;
} else
throw new InvalidArgumentException('$specs array contains an invalid entry.');
@ -188,6 +190,7 @@ class DbConfig implements IConfig {
'name' => $name,
'type' => $type,
'default' => $default,
'alias' => $alias,
];
}
@ -201,6 +204,8 @@ class DbConfig implements IConfig {
break;
}
$resultName = $spec['alias'] ?? $spec['name'];
if(!isset($info)) {
$defaultValue = $spec['default'] ?? null;
if($spec['type'] !== '')
@ -214,11 +219,11 @@ class DbConfig implements IConfig {
default => throw new RuntimeException('Invalid type letter encountered.'),
});
$results[$spec['name']] = $defaultValue;
$results[$resultName] = $defaultValue;
continue;
}
$results[$spec['name']] = match($spec['type']) {
$results[$resultName] = match($spec['type']) {
's' => $info->getString(),
'a' => $info->getArray(),
'i' => $info->getInteger(),

View File

@ -8,6 +8,7 @@ use Index\Data\Migration\FsDbMigrationRepo;
use Index\Http\HttpFx;
use Index\Http\HttpRequest;
use Index\Routing\Router;
use Sasae\SasaeEnvironment;
use Misuzu\Template;
use Misuzu\Auth\AuthInfo;
use Misuzu\Auth\AuthTokenPacker;
@ -69,6 +70,7 @@ class MisuzuContext {
private Forum $forum;
private Permissions $perms;
private AuthInfo $authInfo;
private SasaeEnvironment $templating;
public function __construct(IDbConnection $dbConn, IConfig $config) {
$this->dbConn = $dbConn;
@ -218,10 +220,10 @@ class MisuzuContext {
}
$userId = (string)$userInfo->getId();
if(array_key_exists($userId, $this->activeBansCache))
return $this->activeBansCache[$userId];
if(!array_key_exists($userId, $this->activeBansCache))
$this->activeBansCache[$userId] = $this->bans->tryGetActiveBan($userId);
return $this->activeBansCache[$userId] = $this->bans->tryGetActiveBan($userId);
return $this->activeBansCache[$userId];
}
public function hasActiveBan(UserInfo|string|null $userInfo = null): bool {
@ -241,125 +243,51 @@ class MisuzuContext {
);
}
public function getHeaderMenu(?UserInfo $userInfo): array {
$hasUserInfo = $userInfo?->isDeleted() === false;
$menu = [];
$home = [
'title' => 'Home',
'url' => url('index'),
'menu' => [],
];
if($hasUserInfo)
$home['menu'][] = [
'title' => 'Members',
'url' => url('user-list'),
];
$home['menu'][] = [
'title' => 'Changelog',
'url' => url('changelog-index'),
];
$home['menu'][] = [
'title' => 'Contact',
'url' => url('info', ['title' => 'contact']),
];
$home['menu'][] = [
'title' => 'Rules',
'url' => url('info', ['title' => 'rules']),
];
$menu[] = $home;
$menu[] = [
'title' => 'News',
'url' => url('news-index'),
];
$forum = [
'title' => 'Forum',
'url' => url('forum-index'),
'menu' => [],
];
if($this->authInfo->getPerms('global')->check(Perm::G_FORUM_LEADERBOARD_VIEW))
$forum['menu'][] = [
'title' => 'Leaderboard',
'url' => url('forum-leaderboard'),
];
$menu[] = $forum;
$chatPath = $this->config->getString('sockChat.chatPath.normal');
if(!empty($chatPath))
$menu[] = [
'title' => 'Chat',
'url' => $chatPath,
];
return $menu;
private ?bool $hasManageAccess = null;
public function hasManageAccess(): bool {
$this->hasManageAccess ??= $this->authInfo->isLoggedIn() && !$this->hasActiveBan()
&& $this->getAuthInfo()->getPerms('global')->check(Perm::G_IS_JANITOR);
return $this->hasManageAccess;
}
public function getUserMenu(?UserInfo $userInfo, bool $inBroomCloset): array {
$menu = [];
if($userInfo === null) {
$menu[] = [
'title' => 'Register',
'url' => url('auth-register'),
'icon' => 'fas fa-user-plus fa-fw',
];
$menu[] = [
'title' => 'Log in',
'url' => url('auth-login'),
'icon' => 'fas fa-sign-in-alt fa-fw',
];
} else {
$menu[] = [
'title' => 'Profile',
'url' => url('user-profile', ['user' => $userInfo->getId()]),
'icon' => 'fas fa-user fa-fw',
];
$menu[] = [
'title' => 'Settings',
'url' => url('settings-index'),
'icon' => 'fas fa-cog fa-fw',
];
$menu[] = [
'title' => 'Search',
'url' => url('search-index'),
'icon' => 'fas fa-search fa-fw',
];
if(!$this->hasActiveBan($userInfo) && $this->authInfo->getPerms('global')->check(Perm::G_IS_JANITOR)) {
// restore behaviour where clicking this button switches between
// site version and broom version
if($inBroomCloset)
$menu[] = [
'title' => 'Exit Broom Closet',
'url' => url('index'),
'icon' => 'fas fa-door-open fa-fw',
];
else
$menu[] = [
'title' => 'Enter Broom Closet',
'url' => url('manage-index'),
'icon' => 'fas fa-door-closed fa-fw',
];
}
$menu[] = [
'title' => 'Log out',
'url' => url('auth-logout'),
'icon' => 'fas fa-sign-out-alt fa-fw',
];
}
return $menu;
public function getWebAssetInfo(): ?object {
return json_decode(file_get_contents(MSZ_ASSETS . '/current.json'));
}
public function setUpHttp(): void {
private ?string $chatUrl = null;
public function getChatURL(): string {
$this->chatUrl ??= $this->config->getString('sockChat.chatPath.normal');
return $this->chatUrl;
}
public function startTemplating(): void {
$globals = $this->config->getValues([
['site.name:s', 'Misuzu', 'site_name'],
['site.desc:s', '', 'site_description'],
['site.url:s', '', 'site_url'],
['eeprom.path:s', '', 'eeprom_path'],
['eeprom.app:s', '', 'eeprom_app'],
]);
$authInfo = $this->getAuthInfo();
$globals['assets'] = $this->getWebAssetInfo();
$globals['auth_info'] = $authInfo;
$globals['active_ban_info'] = $this->tryGetActiveBan();
$globals['display_timings_info'] = MSZ_DEBUG
|| $authInfo->getPerms('global')->check(Perm::G_TIMINGS_VIEW);
$templating = new SasaeEnvironment(
MSZ_TEMPLATES,
cache: MSZ_DEBUG ? null : ['Misuzu', GitInfo::hash(true)],
debug: MSZ_DEBUG
);
$templating->addExtension(new MisuzuSasaeExtension($this));
$templating->addGlobal('globals', $globals);
Template::init($templating);
}
public function startRouter(): void {
$this->router = new HttpFx;
$this->router->use('/', function($response) {
$response->setPoweredBy('Misuzu');
@ -369,7 +297,7 @@ class MisuzuContext {
$this->registerHttpRoutes();
}
public function dispatchHttp(?HttpRequest $request = null): void {
public function dispatchRouter(?HttpRequest $request = null): void {
$this->router->dispatch($request);
}

View File

@ -0,0 +1,238 @@
<?php
namespace Misuzu;
use Index\DateTime;
use Misuzu\MisuzuContext;
use Misuzu\Tools;
use Misuzu\Parsers\Parser;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
final class MisuzuSasaeExtension extends AbstractExtension {
private MisuzuContext $ctx;
public function __construct(MisuzuContext $ctx) {
$this->ctx = $ctx;
}
public function getFilters() {
return [
new TwigFilter('country_name', Tools::countryName(...)),
new TwigFilter('parse_text', fn(string $text, int $parser): string => Parser::instance($parser)->parseText($text)),
new TwigFilter('time_format', $this->timeFormat(...)),
];
}
public function getFunctions() {
return [
new TwigFunction('url_construct', 'url_construct'),
new TwigFunction('url', 'url'),
new TwigFunction('csrf_token', CSRF::token(...)),
new TwigFunction('git_commit_hash', GitInfo::hash(...)),
new TwigFunction('git_tag', GitInfo::tag(...)),
new TwigFunction('git_branch', GitInfo::branch(...)),
new TwigFunction('startup_time', fn(float $time = MSZ_STARTUP) => microtime(true) - $time),
new TwigFunction('sql_query_count', $this->ctx->getDbQueryCount(...)),
new TwigFunction('msz_header_menu', $this->getHeaderMenu(...)),
new TwigFunction('msz_user_menu', $this->getUserMenu(...)),
new TwigFunction('msz_manage_menu', $this->getManageMenu(...)),
];
}
public function timeFormat(DateTime|string|int|null $dateTime): string {
if($dateTime === null)
return 'never';
if(is_string($dateTime))
$dateTime = new DateTime($dateTime);
elseif(is_int($dateTime))
$dateTime = DateTime::fromUnixTimeSeconds($dateTime);
$string = '';
$now = DateTime::now();
$isDiffYear = $now->getYear() !== $dateTime->getYear();
if($isDiffYear || $now->getMonth() !== $dateTime->getMonth() || $now->getDay() !== $dateTime->getDay()) {
$string .= $dateTime->format('M jS');
if($isDiffYear)
$string .= $dateTime->format(' Y');
$string .= ', ';
}
$string .= $dateTime->format('G:i ');
$string .= $dateTime->isUTC() ? 'UTC' : $dateTime->format('T');
return $string;
}
public function getHeaderMenu(): array {
$menu = [];
$authInfo = $this->ctx->getAuthInfo();
$home = [
'title' => 'Home',
'url' => url('index'),
'menu' => [],
];
if($authInfo->isLoggedIn())
$home['menu'][] = [
'title' => 'Members',
'url' => url('user-list'),
];
$home['menu'][] = [
'title' => 'Changelog',
'url' => url('changelog-index'),
];
$home['menu'][] = [
'title' => 'Contact',
'url' => url('info', ['title' => 'contact']),
];
$home['menu'][] = [
'title' => 'Rules',
'url' => url('info', ['title' => 'rules']),
];
$menu[] = $home;
$menu[] = [
'title' => 'News',
'url' => url('news-index'),
];
$forum = [
'title' => 'Forum',
'url' => url('forum-index'),
'menu' => [],
];
if($authInfo->getPerms('global')->check(Perm::G_FORUM_LEADERBOARD_VIEW))
$forum['menu'][] = [
'title' => 'Leaderboard',
'url' => url('forum-leaderboard'),
];
$menu[] = $forum;
$chatPath = $this->ctx->getChatURL();
if(!empty($chatPath))
$menu[] = [
'title' => 'Chat',
'url' => $chatPath,
];
return $menu;
}
public function getUserMenu(bool $inBroomCloset, string $manageUrl = ''): array {
$menu = [];
$authInfo = $this->ctx->getAuthInfo();
if($authInfo->isLoggedIn()) {
$userInfo = $authInfo->getUserInfo();
$menu[] = [
'title' => 'Profile',
'url' => url('user-profile', ['user' => $userInfo->getId()]),
'icon' => 'fas fa-user fa-fw',
];
$menu[] = [
'title' => 'Settings',
'url' => url('settings-index'),
'icon' => 'fas fa-cog fa-fw',
];
$menu[] = [
'title' => 'Search',
'url' => url('search-index'),
'icon' => 'fas fa-search fa-fw',
];
if(!$this->ctx->hasActiveBan($userInfo) && $authInfo->getPerms('global')->check(Perm::G_IS_JANITOR)) {
// restore behaviour where clicking this button switches between
// site version and broom version
if($inBroomCloset)
$menu[] = [
'title' => 'Exit Broom Closet',
'url' => $manageUrl === '' ? url('index') : $manageUrl,
'icon' => 'fas fa-door-open fa-fw',
];
else
$menu[] = [
'title' => 'Enter Broom Closet',
'url' => $manageUrl === '' ? url('manage-index') : $manageUrl,
'icon' => 'fas fa-door-closed fa-fw',
];
}
$menu[] = [
'title' => 'Log out',
'url' => url('auth-logout'),
'icon' => 'fas fa-sign-out-alt fa-fw',
];
} else {
$menu[] = [
'title' => 'Register',
'url' => url('auth-register'),
'icon' => 'fas fa-user-plus fa-fw',
];
$menu[] = [
'title' => 'Log in',
'url' => url('auth-login'),
'icon' => 'fas fa-sign-in-alt fa-fw',
];
}
return $menu;
}
public function getManageMenu(): array {
$authInfo = $this->ctx->getAuthInfo();
$globalPerms = $authInfo->getPerms('global');
if(!$authInfo->isLoggedIn() || !$globalPerms->check(Perm::G_IS_JANITOR))
return [];
$menu = [
'General' => [
'Overview' => url('manage-general-overview'),
],
];
if($globalPerms->check(Perm::G_LOGS_VIEW))
$menu['General']['Logs'] = url('manage-general-logs');
if($globalPerms->check(Perm::G_EMOTES_MANAGE))
$menu['General']['Emoticons'] = url('manage-general-emoticons');
if($globalPerms->check(Perm::G_CONFIG_MANAGE))
$menu['General']['Settings'] = url('manage-general-settings');
$userPerms = $authInfo->getPerms('user');
if($userPerms->check(Perm::U_USERS_MANAGE))
$menu['Users & Roles']['Users'] = url('manage-users');
if($userPerms->check(Perm::U_ROLES_MANAGE))
$menu['Users & Roles']['Roles'] = url('manage-roles');
if($userPerms->check(Perm::U_NOTES_MANAGE))
$menu['Users & Roles']['Notes'] = url('manage-users-notes');
if($userPerms->check(Perm::U_WARNINGS_MANAGE))
$menu['Users & Roles']['Warnings'] = url('manage-users-warnings');
if($userPerms->check(Perm::U_BANS_MANAGE))
$menu['Users & Roles']['Bans'] = url('manage-users-bans');
if($globalPerms->check(Perm::G_NEWS_POSTS_MANAGE))
$menu['News']['Posts'] = url('manage-news-posts');
if($globalPerms->check(Perm::G_NEWS_CATEGORIES_MANAGE))
$menu['News']['Categories'] = url('manage-news-categories');
if($globalPerms->check(Perm::G_FORUM_CATEGORIES_MANAGE))
$menu['Forum']['Permission Calculator'] = url('manage-forum-categories');
if($globalPerms->check(Perm::G_FORUM_TOPIC_REDIRS_MANAGE))
$menu['Forum']['Topic Redirects'] = url('manage-forum-topic-redirs');
if($globalPerms->check(Perm::G_CL_CHANGES_MANAGE))
$menu['Changelog']['Changes'] = url('manage-changelog-changes');
if($globalPerms->check(Perm::G_CL_TAGS_MANAGE))
$menu['Changelog']['Tags'] = url('manage-changelog-tags');
return $menu;
}
}

View File

@ -2,46 +2,30 @@
namespace Misuzu;
use InvalidArgumentException;
use Twig\Environment as TwigEnvironment;
use Twig\TwigFunction;
use Twig_Extensions_Extension_Date;
use Twig\Loader\FilesystemLoader as TwigLoaderFilesystem;
use Sasae\SasaeContext;
use Sasae\SasaeEnvironment;
use Misuzu\MisuzuContext;
final class Template {
private const FILE_EXT = '.twig';
private static $loader;
private static $env;
private static $vars = [];
private static SasaeEnvironment $env;
private static array $vars = [];
public static function init(MisuzuContext $ctx, ?string $cache = null, bool $debug = false): void {
self::$loader = new TwigLoaderFilesystem;
self::$env = new TwigEnvironment(self::$loader, [
'cache' => $cache ?? false,
'strict_variables' => true,
'auto_reload' => $debug,
'debug' => $debug,
]);
self::$env->addExtension(new TwigMisuzu($ctx));
}
public static function addPath(string $path): void {
self::$loader->addPath($path);
public static function init(SasaeEnvironment $env): void {
self::$env = $env;
}
public static function addFunction(string $name, callable $body): void {
self::$env->addFunction(new TwigFunction($name, $body));
self::$env->addFunction($name, $body);
}
public static function renderRaw(string $file, array $vars = []): string {
if(!defined('MSZ_TPL_RENDER')) {
if(!defined('MSZ_TPL_RENDER'))
define('MSZ_TPL_RENDER', microtime(true));
}
if(!str_ends_with($file, self::FILE_EXT)) {
if(!str_ends_with($file, self::FILE_EXT))
$file = str_replace('.', DIRECTORY_SEPARATOR, $file) . self::FILE_EXT;
}
return self::$env->render($file, array_merge(self::$vars, $vars));
}
@ -51,13 +35,12 @@ final class Template {
}
public static function set($arrayOrKey, $value = null): void {
if(is_string($arrayOrKey)) {
if(is_string($arrayOrKey))
self::$vars[$arrayOrKey] = $value;
} elseif(is_array($arrayOrKey)) {
elseif(is_array($arrayOrKey))
self::$vars = array_merge(self::$vars, $arrayOrKey);
} else {
else
throw new InvalidArgumentException('First parameter must be of type array or string.');
}
}
public static function displayInfo(?string $message, int $statusCode, ?string $template = null): never {

View File

@ -1,70 +0,0 @@
<?php
namespace Misuzu;
use Index\ByteFormat;
use Index\DateTime;
use Index\Environment;
use Misuzu\MisuzuContext;
use Misuzu\Tools;
use Misuzu\Parsers\Parser;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Twig\Environment as TwigEnvironment;
final class TwigMisuzu extends AbstractExtension {
private MisuzuContext $ctx;
public function __construct(MisuzuContext $ctx) {
$this->ctx = $ctx;
}
public function getFilters() {
return [
new TwigFilter('country_name', Tools::countryName(...)),
new TwigFilter('parse_text', fn(string $text, int $parser): string => Parser::instance($parser)->parseText($text)),
new TwigFilter('time_format', $this->timeFormat(...)),
];
}
public function getFunctions() {
return [
new TwigFunction('url_construct', 'url_construct'),
new TwigFunction('url', 'url'),
new TwigFunction('csrf_token', CSRF::token(...)),
new TwigFunction('git_commit_hash', GitInfo::hash(...)),
new TwigFunction('git_tag', GitInfo::tag(...)),
new TwigFunction('git_branch', GitInfo::branch(...)),
new TwigFunction('startup_time', fn(float $time = MSZ_STARTUP) => microtime(true) - $time),
new TwigFunction('sql_query_count', $this->ctx->getDbQueryCount(...)),
new TwigFunction('ndx_version', Environment::getIndexVersion(...)),
new TwigFunction('byte_symbol', ByteFormat::format(...)),
];
}
public function timeFormat(DateTime|string|int|null $dateTime): string {
if($dateTime === null)
return 'never';
if(is_string($dateTime))
$dateTime = new DateTime($dateTime);
elseif(is_int($dateTime))
$dateTime = DateTime::fromUnixTimeSeconds($dateTime);
$string = '';
$now = DateTime::now();
$isDiffYear = $now->getYear() !== $dateTime->getYear();
if($isDiffYear || $now->getMonth() !== $dateTime->getMonth() || $now->getDay() !== $dateTime->getDay()) {
$string .= $dateTime->format('M jS');
if($isDiffYear)
$string .= $dateTime->format(' Y');
$string .= ', ';
}
$string .= $dateTime->format('G:i ');
$string .= $dateTime->isUTC() ? 'UTC' : $dateTime->format('T');
return $string;
}
}

View File

@ -13,7 +13,7 @@
<a href="https://git.flash.moe/flashii/misuzu/src/tag/{{ git_tag }}" target="_blank" rel="noreferrer noopener" class="footer__link">{{ git_tag }}</a>
{% endif %}
# <a href="https://git.flash.moe/flashii/misuzu/commit/{{ git_commit_hash(true) }}" target="_blank" rel="noreferrer noopener" class="footer__link">{{ git_commit_hash() }}</a>
{% if display_timings_info %}
{% if globals.display_timings_info %}
/ Index {{ ndx_version() }}
/ {{ sql_query_count()|number_format }} queries
/ {{ (startup_time() - startup_time(constant('MSZ_TPL_RENDER')))|number_format(5) }} load

View File

@ -1,13 +1,14 @@
{% from 'macros.twig' import avatar %}
{% from '_layout/input.twig' import input_checkbox_raw %}
{% if current_user_real is defined %}
{% if globals.auth_info.isImpersonating %}
{% set real_user_info = globals.auth_info.realUserInfo %}
<div class="impersonate">
<div class="impersonate-content">
<div class="impersonate-user" style="--user-colour: {{ current_user_real_colour }}">
You are <a href="{{ url('user-profile', {'user': current_user_real.id}) }}" class="impersonate-user-link">
<div class="avatar impersonate-user-avatar">{{ avatar(current_user_real.id, 20, current_user_real.name) }}</div>
{{ current_user_real.name }}
<div class="impersonate-user">
You are <a href="{{ url('user-profile', {'user': real_user_info.id}) }}" class="impersonate-user-link">
<div class="avatar impersonate-user-avatar">{{ avatar(real_user_info.id, 20, real_user_info.name) }}</div>
{{ real_user_info.name }}
</a>
</div>
<div class="impersonate-options">
@ -17,6 +18,10 @@
</div>
{% endif %}
{% set is_in_manage = is_in_manage|default(false) %}
{% set header_menu = msz_header_menu() %}
{% set user_menu = msz_user_menu(is_in_manage, (is_in_manage ? site_link|default('') : manage_link|default(''))) %}
<nav class="header">
<div class="header__background"></div>
@ -51,9 +56,10 @@
</a>
{% endfor %}
{% if current_user is defined %}
<a href="{{ url('user-profile', {'user': current_user.id}) }}" class="avatar header__desktop__user__avatar" title="{{ current_user.name }}">
{{ avatar(current_user.id, 60, current_user.name) }}
{% if globals.auth_info.isLoggedIn %}
{% set user_info = globals.auth_info.userInfo %}
<a href="{{ url('user-profile', {'user': user_info.id}) }}" class="avatar header__desktop__user__avatar" title="{{ user_info.name }}">
{{ avatar(user_info.id, 60, user_info.name) }}
</a>
{% else %}
<a href="{{ url('auth-login') }}" class="avatar header__desktop__user__avatar">
@ -74,7 +80,12 @@
</a>
<label class="header__mobile__icon header__mobile__avatar" for="toggle-mobile-header">
{{ avatar(current_user.id|default(0), 40, current_user.name|default('Log in')) }}
{% if globals.auth_info.isLoggedIn %}
{% set user_info = globals.auth_info.userInfo %}
{{ avatar(user_info.id, 40, user_info.name) }}
{% else %}
{{ avatar(0, 40, 'Log in') }}
{% endif %}
</label>
</div>

View File

@ -176,9 +176,9 @@
</div>
</form>
{% if globals.eeprom.path is not empty %}
{% if globals.eeprom_path is not empty and globals.eeprom_app is not empty %}
<script type="text/javascript">
const peepPath = '{{ globals.eeprom.path }}', peepApp = '{{ globals.eeprom.app }}';
const peepPath = '{{ globals.eeprom_path }}', peepApp = '{{ globals.eeprom_app }}';
</script>
{% endif %}
{% endblock %}

View File

@ -36,6 +36,8 @@
},
] %}
{% set header_menu = msz_header_menu() %}
{% block main_header %}
<div class="landingv2-header">
<div class="landingv2-header-background"></div>
@ -87,7 +89,7 @@
{% endif %}
# <a href="https://github.com/flashwave/misuzu/commit/{{ git_commit_hash(true) }}" target="_blank" rel="noreferrer noopener">{{ git_commit_hash() }}</a>
</div>
{% if display_timings_info %}
{% if globals.display_timings_info %}
<div class="landingv2-footer-copyright-line">
{{ sql_query_count()|number_format }} queries / {{ (startup_time() - startup_time(constant('MSZ_TPL_RENDER')))|number_format(5) }} load / {{ startup_time(constant('MSZ_TPL_RENDER'))|number_format(5) }} template / {{ startup_time()|number_format(5) }} total
</div>

View File

@ -2,13 +2,14 @@
{% from 'macros.twig' import container_title %}
{% from 'manage/macros.twig' import manage_navigation %}
{% set is_in_manage = true %}
{% set title = title|default('Broom Closet') %}
{% set site_logo = '/images/logos/imouto-broom.png' %}
{% block content %}
<div class="manage">
<nav class="manage__sidebar">
{{ manage_navigation(manage_menu) }}
{{ manage_navigation(msz_manage_menu()) }}
</nav>
<div class="manage__content">

View File

@ -6,7 +6,7 @@
{% include '_layout/meta.twig' %}
<link href="/vendor/fontawesome/css/all.min.css" type="text/css" rel="stylesheet">
<link href="/vendor/highlightjs/styles/tomorrow-night.css" type="text/css" rel="stylesheet">
<link href="{{ assets.mszcss|default() }}" type="text/css" rel="stylesheet">
<link href="{{ globals.assets.mszcss|default() }}" type="text/css" rel="stylesheet">
{% if site_background is defined %}
<style>
:root {
@ -32,12 +32,12 @@
{% endblock %}
<div class="main__wrapper">
{% if current_user_ban_info is defined and current_user_ban_info is not null %}
{% if globals.active_ban_info is not null %}
<div class="warning warning--red">
<div class="warning__content">
<p>You have been banned {% if current_user_ban_info.isPermanent %}<strong>permanently</strong>{% else %}for <strong title="{{ current_user_ban_info.expiresTime|date('r') }}">{{ current_user_ban_info.remainingString }}</strong>{% endif %} since <strong><time datetime="{{ current_user_ban_info.createdTime|date('c') }}" title="{{ current_user_ban_info.createdTime|date('r') }}">{{ current_user_ban_info.createdTime|time_format }}</time></strong>.</p>
{% if current_user_ban_info.hasPublicReason %}
<p>Reason: {{ current_user_ban_info.publicReason }}</p>
<p>You have been banned {% if globals.active_ban_info.isPermanent %}<strong>permanently</strong>{% else %}for <strong title="{{ globals.active_ban_info.expiresTime|date('r') }}">{{ globals.active_ban_info.remainingString }}</strong>{% endif %} since <strong><time datetime="{{ globals.active_ban_info.createdTime|date('c') }}" title="{{ globals.active_ban_info.createdTime|date('r') }}">{{ globals.active_ban_info.createdTime|time_format }}</time></strong>.</p>
{% if globals.active_ban_info.hasPublicReason %}
<p>Reason: {{ globals.active_ban_info.publicReason }}</p>
{% endif %}
</div>
</div>
@ -58,6 +58,6 @@
window.addEventListener('DOMContentLoaded', function() { Misuzu(); });
</script>
<script src="/vendor/highlightjs/highlight.pack.js" type="text/javascript"></script>
<script src="{{ assets.mszjs|default() }}" type="text/javascript"></script>
<script src="{{ globals.assets.mszjs|default() }}" type="text/javascript"></script>
</body>
</html>

View File

@ -47,7 +47,7 @@
{% if perms.edit_avatar %}
<ul class="profile__guidelines__section">
<li class="profile__guidelines__line profile__guidelines__line--header">Avatar</li>
<li class="profile__guidelines__line">May not exceed the <span class="profile__guidelines__emphasis">{{ byte_symbol(profile_avatar_info.maxBytes) }}</span> file size limit.</li>
<li class="profile__guidelines__line">May not exceed the <span class="profile__guidelines__emphasis">{{ profile_avatar_info.maxBytes|format_filesize }}</span> file size limit.</li>
<li class="profile__guidelines__line">May not be larger than <span class="profile__guidelines__emphasis">{{ profile_avatar_info.maxWidth }}x{{ profile_avatar_info.maxHeight }}</span>.</li>
<li class="profile__guidelines__line">Will be centre cropped and scaled to at most <span class="profile__guidelines__emphasis">240x240</span>.</li>
<li class="profile__guidelines__line">Animated GIF images are allowed.</li>
@ -57,7 +57,7 @@
{% if perms.edit_background %}
<ul class="profile__guidelines__section">
<li class="profile__guidelines__line profile__guidelines__line--header">Background</li>
<li class="profile__guidelines__line">May not exceed the <span class="profile__guidelines__emphasis">{{ byte_symbol(profile_background_info.maxBytes) }}</span> file size limit.</li>
<li class="profile__guidelines__line">May not exceed the <span class="profile__guidelines__emphasis">{{ profile_background_info.maxBytes|format_filesize }}</span> file size limit.</li>
<li class="profile__guidelines__line">May not be larger than <span class="profile__guidelines__emphasis">{{ profile_background_info.maxWidth }}x{{ profile_background_info.maxHeight }}</span>.</li>
<li class="profile__guidelines__line">GIF images, in general, are only allowed when tiling.</li>
</ul>