Removed getter/setter methods in favour of property hooks and asymmetric visibility.

This commit is contained in:
flash 2024-11-30 04:09:29 +00:00
parent 2cb2918533
commit d103477fe1
149 changed files with 2169 additions and 3565 deletions
README.mdcomposer.jsoncomposer.lock
public-legacy
public
src

View file

@ -2,6 +2,6 @@
> Misuzu can and will steal your lunch money. > Misuzu can and will steal your lunch money.
## Requirements ## Requirements
- PHP 8.2 (64-bit) - PHP 8.4
- MariaDB 10.6 - MariaDB 10.6
- [Composer](https://getcomposer.org/) - [Composer](https://getcomposer.org/)

View file

@ -1,5 +1,6 @@
{ {
"require": { "require": {
"php": ">=8.4",
"flashwave/index": "^0.2410", "flashwave/index": "^0.2410",
"flashii/rpcii": "^2.0", "flashii/rpcii": "^2.0",
"erusev/parsedown": "~1.6", "erusev/parsedown": "~1.6",

208
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "33116c436dc775be3d0e0b1d33114224", "content-hash": "1bf2d030b7813e94e87ca04c39b83eff",
"packages": [ "packages": [
{ {
"name": "carbonphp/carbon-doctrine-types", "name": "carbonphp/carbon-doctrine-types",
@ -77,29 +77,29 @@
}, },
{ {
"name": "chillerlan/php-qrcode", "name": "chillerlan/php-qrcode",
"version": "4.4.1", "version": "4.4.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/chillerlan/php-qrcode.git", "url": "https://github.com/chillerlan/php-qrcode.git",
"reference": "f5e243f3b61a60934780579430a951460f40888d" "reference": "345ed8e4ffb56e6b3fcd9f42e3970b9026fa6ce4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/f5e243f3b61a60934780579430a951460f40888d", "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/345ed8e4ffb56e6b3fcd9f42e3970b9026fa6ce4",
"reference": "f5e243f3b61a60934780579430a951460f40888d", "reference": "345ed8e4ffb56e6b3fcd9f42e3970b9026fa6ce4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"chillerlan/php-settings-container": "^2.1.4 || ^3.1", "chillerlan/php-settings-container": "^2.1.6 || ^3.2.1",
"ext-mbstring": "*", "ext-mbstring": "*",
"php": "^7.4 || ^8.0" "php": "^7.4 || ^8.0"
}, },
"require-dev": { "require-dev": {
"phan/phan": "^5.4", "phan/phan": "^5.4.5",
"phpmd/phpmd": "^2.15", "phpmd/phpmd": "^2.15",
"phpunit/phpunit": "^9.6", "phpunit/phpunit": "^9.6",
"setasign/fpdf": "^1.8.2", "setasign/fpdf": "^1.8.2",
"squizlabs/php_codesniffer": "^3.8" "squizlabs/php_codesniffer": "^3.11"
}, },
"suggest": { "suggest": {
"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.", "chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
@ -142,19 +142,15 @@
], ],
"support": { "support": {
"issues": "https://github.com/chillerlan/php-qrcode/issues", "issues": "https://github.com/chillerlan/php-qrcode/issues",
"source": "https://github.com/chillerlan/php-qrcode/tree/4.4.1" "source": "https://github.com/chillerlan/php-qrcode/tree/4.4.2"
}, },
"funding": [ "funding": [
{
"url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4",
"type": "custom"
},
{ {
"url": "https://ko-fi.com/codemasher", "url": "https://ko-fi.com/codemasher",
"type": "ko_fi" "type": "ko_fi"
} }
], ],
"time": "2024-01-06T16:56:58+00:00" "time": "2024-11-15T15:36:24+00:00"
}, },
{ {
"name": "chillerlan/php-settings-container", "name": "chillerlan/php-settings-container",
@ -418,11 +414,11 @@
}, },
{ {
"name": "flashii/rpcii", "name": "flashii/rpcii",
"version": "v2.0.0", "version": "v2.0.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://patchii.net/flashii/rpcii-php.git", "url": "https://patchii.net/flashii/rpcii-php.git",
"reference": "93ec139171d023f210f0b7464266b6fc42b4d838" "reference": "1cbc1edb061612dc1d014a82e24b741d2a0bc11a"
}, },
"require": { "require": {
"ext-msgpack": ">=2.2", "ext-msgpack": ">=2.2",
@ -453,15 +449,15 @@
], ],
"description": "HTTP RPC client/server library.", "description": "HTTP RPC client/server library.",
"homepage": "https://railgun.sh/rpcii", "homepage": "https://railgun.sh/rpcii",
"time": "2024-11-13T23:17:29+00:00" "time": "2024-11-14T02:22:09+00:00"
}, },
{ {
"name": "flashwave/index", "name": "flashwave/index",
"version": "v0.2410.191603", "version": "v0.2410.211811",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://patchii.net/flash/index.git", "url": "https://patchii.net/flash/index.git",
"reference": "17cdb4d1c239241200d7e30968122a8cd8b26509" "reference": "40cbd35ba3855056987d2f7647f669e66f938979"
}, },
"require": { "require": {
"ext-mbstring": "*", "ext-mbstring": "*",
@ -508,7 +504,7 @@
], ],
"description": "Composer package for the common library for my projects.", "description": "Composer package for the common library for my projects.",
"homepage": "https://railgun.sh/index", "homepage": "https://railgun.sh/index",
"time": "2024-10-19T16:04:17+00:00" "time": "2024-10-21T18:15:09+00:00"
}, },
{ {
"name": "guzzlehttp/psr7", "name": "guzzlehttp/psr7",
@ -628,28 +624,28 @@
}, },
{ {
"name": "jean85/pretty-package-versions", "name": "jean85/pretty-package-versions",
"version": "2.0.6", "version": "2.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Jean85/pretty-package-versions.git", "url": "https://github.com/Jean85/pretty-package-versions.git",
"reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4" "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4", "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/3c4e5f62ba8d7de1734312e4fff32f67a8daaf10",
"reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4", "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"composer-runtime-api": "^2.0.0", "composer-runtime-api": "^2.1.0",
"php": "^7.1|^8.0" "php": "^7.4|^8.0"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "^3.2", "friendsofphp/php-cs-fixer": "^3.2",
"jean85/composer-provided-replaced-stub-package": "^1.0", "jean85/composer-provided-replaced-stub-package": "^1.0",
"phpstan/phpstan": "^1.4", "phpstan/phpstan": "^1.4",
"phpunit/phpunit": "^7.5|^8.5|^9.4", "phpunit/phpunit": "^7.5|^8.5|^9.6",
"vimeo/psalm": "^4.3" "vimeo/psalm": "^4.3 || ^5.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -681,9 +677,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/Jean85/pretty-package-versions/issues", "issues": "https://github.com/Jean85/pretty-package-versions/issues",
"source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6" "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.0"
}, },
"time": "2024-03-08T09:58:59+00:00" "time": "2024-11-18T16:19:46+00:00"
}, },
{ {
"name": "matomo/device-detector", "name": "matomo/device-detector",
@ -1413,16 +1409,16 @@
}, },
{ {
"name": "symfony/clock", "name": "symfony/clock",
"version": "v7.1.6", "version": "v7.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/clock.git", "url": "https://github.com/symfony/clock.git",
"reference": "97bebc53548684c17ed696bc8af016880f0f098d" "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/clock/zipball/97bebc53548684c17ed696bc8af016880f0f098d", "url": "https://api.github.com/repos/symfony/clock/zipball/b81435fbd6648ea425d1ee96a2d8e68f4ceacd24",
"reference": "97bebc53548684c17ed696bc8af016880f0f098d", "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1467,7 +1463,7 @@
"time" "time"
], ],
"support": { "support": {
"source": "https://github.com/symfony/clock/tree/v7.1.6" "source": "https://github.com/symfony/clock/tree/v7.2.0"
}, },
"funding": [ "funding": [
{ {
@ -1483,20 +1479,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-09-25T14:20:29+00:00" "time": "2024-09-25T14:21:43+00:00"
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
"version": "v3.5.0", "version": "v3.5.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git", "url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1534,7 +1530,7 @@
"description": "A generic function and convention to trigger deprecation notices", "description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1"
}, },
"funding": [ "funding": [
{ {
@ -1550,20 +1546,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-04-18T09:32:20+00:00" "time": "2024-09-25T14:20:29+00:00"
}, },
{ {
"name": "symfony/event-dispatcher", "name": "symfony/event-dispatcher",
"version": "v7.1.6", "version": "v7.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/event-dispatcher.git", "url": "https://github.com/symfony/event-dispatcher.git",
"reference": "87254c78dd50721cfd015b62277a8281c5589702" "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87254c78dd50721cfd015b62277a8281c5589702", "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1",
"reference": "87254c78dd50721cfd015b62277a8281c5589702", "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1614,7 +1610,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/event-dispatcher/tree/v7.1.6" "source": "https://github.com/symfony/event-dispatcher/tree/v7.2.0"
}, },
"funding": [ "funding": [
{ {
@ -1630,20 +1626,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-09-25T14:20:29+00:00" "time": "2024-09-25T14:21:43+00:00"
}, },
{ {
"name": "symfony/event-dispatcher-contracts", "name": "symfony/event-dispatcher-contracts",
"version": "v3.5.0", "version": "v3.5.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git", "url": "https://github.com/symfony/event-dispatcher-contracts.git",
"reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f",
"reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1690,7 +1686,7 @@
"standards" "standards"
], ],
"support": { "support": {
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1"
}, },
"funding": [ "funding": [
{ {
@ -1706,7 +1702,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-04-18T09:32:20+00:00" "time": "2024-09-25T14:20:29+00:00"
}, },
{ {
"name": "symfony/mailer", "name": "symfony/mailer",
@ -1790,16 +1786,16 @@
}, },
{ {
"name": "symfony/mime", "name": "symfony/mime",
"version": "v7.1.6", "version": "v7.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/mime.git", "url": "https://github.com/symfony/mime.git",
"reference": "caa1e521edb2650b8470918dfe51708c237f0598" "reference": "cc84a4b81f62158c3846ac7ff10f696aae2b524d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/mime/zipball/caa1e521edb2650b8470918dfe51708c237f0598", "url": "https://api.github.com/repos/symfony/mime/zipball/cc84a4b81f62158c3846ac7ff10f696aae2b524d",
"reference": "caa1e521edb2650b8470918dfe51708c237f0598", "reference": "cc84a4b81f62158c3846ac7ff10f696aae2b524d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1854,7 +1850,7 @@
"mime-type" "mime-type"
], ],
"support": { "support": {
"source": "https://github.com/symfony/mime/tree/v7.1.6" "source": "https://github.com/symfony/mime/tree/v7.2.0"
}, },
"funding": [ "funding": [
{ {
@ -1870,20 +1866,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-10-25T15:11:02+00:00" "time": "2024-11-23T09:19:39+00:00"
}, },
{ {
"name": "symfony/options-resolver", "name": "symfony/options-resolver",
"version": "v7.1.6", "version": "v7.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/options-resolver.git", "url": "https://github.com/symfony/options-resolver.git",
"reference": "85e95eeede2d41cd146146e98c9c81d9214cae85" "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/85e95eeede2d41cd146146e98c9c81d9214cae85", "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50",
"reference": "85e95eeede2d41cd146146e98c9c81d9214cae85", "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1921,7 +1917,7 @@
"options" "options"
], ],
"support": { "support": {
"source": "https://github.com/symfony/options-resolver/tree/v7.1.6" "source": "https://github.com/symfony/options-resolver/tree/v7.2.0"
}, },
"funding": [ "funding": [
{ {
@ -1937,7 +1933,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-09-25T14:20:29+00:00" "time": "2024-11-20T11:17:29+00:00"
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
@ -2416,16 +2412,16 @@
}, },
{ {
"name": "symfony/service-contracts", "name": "symfony/service-contracts",
"version": "v3.5.0", "version": "v3.5.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/service-contracts.git", "url": "https://github.com/symfony/service-contracts.git",
"reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
"reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2479,7 +2475,7 @@
"standards" "standards"
], ],
"support": { "support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.5.0" "source": "https://github.com/symfony/service-contracts/tree/v3.5.1"
}, },
"funding": [ "funding": [
{ {
@ -2495,24 +2491,25 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-04-18T09:32:20+00:00" "time": "2024-09-25T14:20:29+00:00"
}, },
{ {
"name": "symfony/translation", "name": "symfony/translation",
"version": "v7.1.6", "version": "v7.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/translation.git", "url": "https://github.com/symfony/translation.git",
"reference": "b9f72ab14efdb6b772f85041fa12f820dee8d55f" "reference": "dc89e16b44048ceecc879054e5b7f38326ab6cc5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/b9f72ab14efdb6b772f85041fa12f820dee8d55f", "url": "https://api.github.com/repos/symfony/translation/zipball/dc89e16b44048ceecc879054e5b7f38326ab6cc5",
"reference": "b9f72ab14efdb6b772f85041fa12f820dee8d55f", "reference": "dc89e16b44048ceecc879054e5b7f38326ab6cc5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=8.2", "php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-mbstring": "~1.0",
"symfony/translation-contracts": "^2.5|^3.0" "symfony/translation-contracts": "^2.5|^3.0"
}, },
@ -2573,7 +2570,7 @@
"description": "Provides tools to internationalize your application", "description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/translation/tree/v7.1.6" "source": "https://github.com/symfony/translation/tree/v7.2.0"
}, },
"funding": [ "funding": [
{ {
@ -2589,20 +2586,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-09-28T12:35:13+00:00" "time": "2024-11-12T20:47:56+00:00"
}, },
{ {
"name": "symfony/translation-contracts", "name": "symfony/translation-contracts",
"version": "v3.5.0", "version": "v3.5.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/translation-contracts.git", "url": "https://github.com/symfony/translation-contracts.git",
"reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" "reference": "4667ff3bd513750603a09c8dedbea942487fb07c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c",
"reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", "reference": "4667ff3bd513750603a09c8dedbea942487fb07c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2651,7 +2648,7 @@
"standards" "standards"
], ],
"support": { "support": {
"source": "https://github.com/symfony/translation-contracts/tree/v3.5.0" "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1"
}, },
"funding": [ "funding": [
{ {
@ -2667,20 +2664,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-04-18T09:32:20+00:00" "time": "2024-09-25T14:20:29+00:00"
}, },
{ {
"name": "twig/html-extra", "name": "twig/html-extra",
"version": "v3.13.0", "version": "v3.16.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/twigphp/html-extra.git", "url": "https://github.com/twigphp/html-extra.git",
"reference": "8229e750091171c1f11801a525927811c7ac5a7e" "reference": "2086023d3ffc4bae2b1115f715d17f97fd013665"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/twigphp/html-extra/zipball/8229e750091171c1f11801a525927811c7ac5a7e", "url": "https://api.github.com/repos/twigphp/html-extra/zipball/2086023d3ffc4bae2b1115f715d17f97fd013665",
"reference": "8229e750091171c1f11801a525927811c7ac5a7e", "reference": "2086023d3ffc4bae2b1115f715d17f97fd013665",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2723,7 +2720,7 @@
"twig" "twig"
], ],
"support": { "support": {
"source": "https://github.com/twigphp/html-extra/tree/v3.13.0" "source": "https://github.com/twigphp/html-extra/tree/v3.16.0"
}, },
"funding": [ "funding": [
{ {
@ -2735,20 +2732,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-09-03T13:08:40+00:00" "time": "2024-09-30T06:41:48+00:00"
}, },
{ {
"name": "twig/twig", "name": "twig/twig",
"version": "v3.14.2", "version": "v3.16.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/twigphp/Twig.git", "url": "https://github.com/twigphp/Twig.git",
"reference": "0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a" "reference": "475ad2dc97d65d8631393e721e7e44fb544f0561"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a", "url": "https://api.github.com/repos/twigphp/Twig/zipball/475ad2dc97d65d8631393e721e7e44fb544f0561",
"reference": "0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a", "reference": "475ad2dc97d65d8631393e721e7e44fb544f0561",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2759,6 +2756,7 @@
"symfony/polyfill-php81": "^1.29" "symfony/polyfill-php81": "^1.29"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "^2.0",
"psr/container": "^1.0|^2.0", "psr/container": "^1.0|^2.0",
"symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
}, },
@ -2802,7 +2800,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/twigphp/Twig/issues", "issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.14.2" "source": "https://github.com/twigphp/Twig/tree/v3.16.0"
}, },
"funding": [ "funding": [
{ {
@ -2814,22 +2812,22 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-11-07T12:36:22+00:00" "time": "2024-11-29T08:27:05+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [
{ {
"name": "phpstan/phpstan", "name": "phpstan/phpstan",
"version": "1.12.10", "version": "1.12.12",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "fc463b5d0fe906dcf19689be692c65c50406a071" "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/fc463b5d0fe906dcf19689be692c65c50406a071", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0",
"reference": "fc463b5d0fe906dcf19689be692c65c50406a071", "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2874,15 +2872,17 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-11-11T15:37:09+00:00" "time": "2024-11-28T22:13:23+00:00"
} }
], ],
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": [], "stability-flags": {},
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": [], "platform": {
"platform-dev": [], "php": ">=8.4"
},
"platform-dev": {},
"plugin-api-version": "2.6.0" "plugin-api-version": "2.6.0"
} }

View file

@ -101,8 +101,6 @@ if(!empty($repoInfo['master']))
if($data->ref !== $repoMaster) if($data->ref !== $repoMaster)
die('only the master branch is tracked'); die('only the master branch is tracked');
$changelog = $msz->getChangelog();
$tags = $repoInfo['tags'] ?? []; $tags = $repoInfo['tags'] ?? [];
$addresses = $config['addresses'] ?? []; $addresses = $config['addresses'] ?? [];
@ -118,7 +116,7 @@ foreach($data->commits as $commit) {
$line = $index === false ? $message : mb_substr($message, 0, $index); $line = $index === false ? $message : mb_substr($message, 0, $index);
$body = trim($index === false ? '' : mb_substr($message, $index + 1)); $body = trim($index === false ? '' : mb_substr($message, $index + 1));
$changeInfo = $changelog->createChange( $changeInfo = $msz->changelog->createChange(
ghcb_changelog_action($line), ghcb_changelog_action($line),
$line, $body, $line, $body,
$addresses[$commit->author->email] ?? null, $addresses[$commit->author->email] ?? null,
@ -127,5 +125,5 @@ foreach($data->commits as $commit) {
if(!empty($tags)) if(!empty($tags))
foreach($tags as $tag) foreach($tags as $tag)
$changelog->addTagToChange($changeInfo, $tag); $msz->changelog->addTagToChange($changeInfo, $tag);
} }

View file

@ -4,37 +4,30 @@ namespace Misuzu;
use Exception; use Exception;
use Misuzu\Auth\AuthTokenCookie; use Misuzu\Auth\AuthTokenCookie;
$urls = $msz->getUrls(); if($msz->authInfo->isLoggedIn) {
$authInfo = $msz->getAuthInfo(); Tools::redirect($msz->urls->format('index'));
if($authInfo->isLoggedIn()) {
Tools::redirect($urls->format('index'));
return; return;
} }
$authCtx = $msz->getAuthContext();
$users = $msz->getUsersContext()->getUsers();
$sessions = $authCtx->getSessions();
$loginAttempts = $authCtx->getLoginAttempts();
if(!empty($_GET['resolve'])) { if(!empty($_GET['resolve'])) {
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
try { try {
// Only works for usernames, this is by design // Only works for usernames, this is by design
$userInfo = $users->getUser((string)filter_input(INPUT_GET, 'name'), 'name'); $userInfo = $msz->usersCtx->users->getUser((string)filter_input(INPUT_GET, 'name'), 'name');
} catch(Exception $ex) { } catch(Exception $ex) {
echo json_encode([ echo json_encode([
'id' => 0, 'id' => 0,
'name' => '', 'name' => '',
'avatar' => $urls->format('user-avatar', ['res' => 200, 'user' => 0]), 'avatar' => $msz->urls->format('user-avatar', ['res' => 200, 'user' => 0]),
]); ]);
return; return;
} }
echo json_encode([ echo json_encode([
'id' => (int)$userInfo->getId(), 'id' => (int)$userInfo->getId(),
'name' => $userInfo->getName(), 'name' => $userInfo->name,
'avatar' => $urls->format('user-avatar', ['user' => $userInfo->getId(), 'res' => 200]), 'avatar' => $msz->urls->format('user-avatar', ['user' => $userInfo->getId(), 'res' => 200]),
]); ]);
return; return;
} }
@ -44,7 +37,7 @@ $ipAddress = $_SERVER['REMOTE_ADDR'];
$countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX'; $countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX';
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? ''; $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$remainingAttempts = $loginAttempts->countRemainingAttempts($ipAddress); $remainingAttempts = $msz->authCtx->loginAttempts->countRemainingAttempts($ipAddress);
$siteIsPrivate = $cfg->getBoolean('private.enable'); $siteIsPrivate = $cfg->getBoolean('private.enable');
if($siteIsPrivate) { if($siteIsPrivate) {
@ -95,49 +88,49 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) {
$loginFailedError = "Invalid username or password, {$attemptsRemainingError}."; $loginFailedError = "Invalid username or password, {$attemptsRemainingError}.";
try { try {
$userInfo = $users->getUser($_POST['login']['username'], 'login'); $userInfo = $msz->usersCtx->users->getUser($_POST['login']['username'], 'login');
} catch(Exception $ex) { } catch(Exception $ex) {
$loginAttempts->recordAttempt(false, $ipAddress, $countryCode, $userAgent, $clientInfo); $msz->authCtx->loginAttempts->recordAttempt(false, $ipAddress, $countryCode, $userAgent, $clientInfo);
$notices[] = $loginFailedError; $notices[] = $loginFailedError;
break; break;
} }
if(!$userInfo->hasPasswordHash()) { if(!$userInfo->hasPasswordHash) {
$notices[] = 'Your password has been invalidated, please reset it.'; $notices[] = 'Your password has been invalidated, please reset it.';
break; break;
} }
if($userInfo->isDeleted() || !$userInfo->verifyPassword($_POST['login']['password'])) { if($userInfo->deleted || !$userInfo->verifyPassword($_POST['login']['password'])) {
$loginAttempts->recordAttempt(false, $ipAddress, $countryCode, $userAgent, $clientInfo, $userInfo); $msz->authCtx->loginAttempts->recordAttempt(false, $ipAddress, $countryCode, $userAgent, $clientInfo, $userInfo);
$notices[] = $loginFailedError; $notices[] = $loginFailedError;
break; break;
} }
if($userInfo->passwordNeedsRehash()) if($userInfo->passwordNeedsRehash)
$users->updateUser($userInfo, password: $_POST['login']['password']); $msz->usersCtx->users->updateUser($userInfo, password: $_POST['login']['password']);
if(!empty($loginPermCat) && $loginPermVal > 0 && !$msz->getPerms()->checkPermissions($loginPermCat, $loginPermVal, $userInfo)) { if(!empty($loginPermCat) && $loginPermVal > 0 && !$msz->perms->checkPermissions($loginPermCat, $loginPermVal, $userInfo)) {
$notices[] = "Login succeeded, but you're not allowed to browse the site right now."; $notices[] = "Login succeeded, but you're not allowed to browse the site right now.";
$loginAttempts->recordAttempt(true, $ipAddress, $countryCode, $userAgent, $clientInfo, $userInfo); $msz->authCtx->loginAttempts->recordAttempt(true, $ipAddress, $countryCode, $userAgent, $clientInfo, $userInfo);
break; break;
} }
if($userInfo->hasTOTPKey()) { if($userInfo->hasTOTP) {
$tfaToken = $authCtx->getTwoFactorAuthSessions()->createToken($userInfo); $tfaToken = $msz->authCtx->tfaSessions->createToken($userInfo);
Tools::redirect($urls->format('auth-two-factor', ['token' => $tfaToken])); Tools::redirect($msz->urls->format('auth-two-factor', ['token' => $tfaToken]));
return; return;
} }
$loginAttempts->recordAttempt(true, $ipAddress, $countryCode, $userAgent, $clientInfo, $userInfo); $msz->authCtx->loginAttempts->recordAttempt(true, $ipAddress, $countryCode, $userAgent, $clientInfo, $userInfo);
try { try {
$sessionInfo = $sessions->createSession($userInfo, $ipAddress, $countryCode, $userAgent, $clientInfo); $sessionInfo = $msz->authCtx->sessions->createSession($userInfo, $ipAddress, $countryCode, $userAgent, $clientInfo);
} catch(Exception $ex) { } catch(Exception $ex) {
$notices[] = "Something broke while creating a session for you, please tell an administrator or developer about this!"; $notices[] = "Something broke while creating a session for you, please tell an administrator or developer about this!";
break; break;
} }
$tokenBuilder = $authInfo->getTokenInfo()->toBuilder(); $tokenBuilder = $msz->authInfo->tokenInfo->toBuilder();
$tokenBuilder->setUserId($userInfo); $tokenBuilder->setUserId($userInfo);
$tokenBuilder->setSessionToken($sessionInfo); $tokenBuilder->setSessionToken($sessionInfo);
$tokenBuilder->removeImpersonatedUserId(); $tokenBuilder->removeImpersonatedUserId();
@ -146,7 +139,7 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) {
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo)); AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
if(!Tools::isLocalURL($loginRedirect)) if(!Tools::isLocalURL($loginRedirect))
$loginRedirect = $urls->format('index'); $loginRedirect = $msz->urls->format('index');
Tools::redirect($loginRedirect); Tools::redirect($loginRedirect);
return; return;
@ -156,7 +149,7 @@ $welcomeMode = !empty($_GET['welcome']);
$loginUsername = !empty($_POST['login']['username']) && is_string($_POST['login']['username']) ? $_POST['login']['username'] : ( $loginUsername = !empty($_POST['login']['username']) && is_string($_POST['login']['username']) ? $_POST['login']['username'] : (
!empty($_GET['username']) && is_string($_GET['username']) ? $_GET['username'] : '' !empty($_GET['username']) && is_string($_GET['username']) ? $_GET['username'] : ''
); );
$loginRedirect = $welcomeMode ? $urls->format('index') : (!empty($_GET['redirect']) && is_string($_GET['redirect']) ? $_GET['redirect'] : null) ?? $_SERVER['HTTP_REFERER'] ?? $urls->format('index'); $loginRedirect = $welcomeMode ? $msz->urls->format('index') : (!empty($_GET['redirect']) && is_string($_GET['redirect']) ? $_GET['redirect'] : null) ?? $_SERVER['HTTP_REFERER'] ?? $msz->urls->format('index');
$canRegisterAccount = !$siteIsPrivate; $canRegisterAccount = !$siteIsPrivate;
Template::render('auth.login', [ Template::render('auth.login', [

View file

@ -3,25 +3,22 @@ namespace Misuzu;
use Misuzu\Auth\AuthTokenCookie; use Misuzu\Auth\AuthTokenCookie;
$authInfo = $msz->getAuthInfo(); if($msz->authInfo->isLoggedIn) {
if($authInfo->isLoggedIn()) {
if(!CSRF::validateRequest()) { if(!CSRF::validateRequest()) {
Template::render('auth.logout'); Template::render('auth.logout');
return; return;
} }
$tokenInfo = $authInfo->getTokenInfo(); $tokenInfo = $msz->authInfo->tokenInfo;
$msz->authCtx->sessions->deleteSessions(sessionTokens: $tokenInfo->sessionToken);
$authCtx = $msz->getAuthContext();
$authCtx->getSessions()->deleteSessions(sessionTokens: $tokenInfo->getSessionToken());
$tokenBuilder = $tokenInfo->toBuilder(); $tokenBuilder = $tokenInfo->toBuilder();
$tokenBuilder->removeUserId(); $tokenBuilder->removeUserId();
$tokenBuilder->removeSessionToken(); $tokenBuilder->removeSessionToken();
$tokenBuilder->removeImpersonatedUserId(); $tokenBuilder->removeImpersonatedUserId();
$tokenInfo = $tokenBuilder->toInfo();
$tokenInfo = $tokenBuilder->toInfo();
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo)); AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
} }
Tools::redirect($msz->getUrls()->format('index'));; Tools::redirect($msz->urls->format('index'));;

View file

@ -4,18 +4,11 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
use Misuzu\Users\User; use Misuzu\Users\User;
$urls = $msz->getUrls(); if($msz->authInfo->isLoggedIn) {
$authInfo = $msz->getAuthInfo(); Tools::redirect($msz->urls->format('settings-account'));
if($authInfo->isLoggedIn()) {
Tools::redirect($urls->format('settings-account'));
return; return;
} }
$authCtx = $msz->getAuthContext();
$users = $msz->getUsersContext()->getUsers();
$recoveryTokens = $authCtx->getRecoveryTokens();
$loginAttempts = $authCtx->getLoginAttempts();
$reset = !empty($_POST['reset']) && is_array($_POST['reset']) ? $_POST['reset'] : []; $reset = !empty($_POST['reset']) && is_array($_POST['reset']) ? $_POST['reset'] : [];
$forgot = !empty($_POST['forgot']) && is_array($_POST['forgot']) ? $_POST['forgot'] : []; $forgot = !empty($_POST['forgot']) && is_array($_POST['forgot']) ? $_POST['forgot'] : [];
$userId = !empty($reset['user']) ? (int)$reset['user'] : ( $userId = !empty($reset['user']) ? (int)$reset['user'] : (
@ -24,9 +17,9 @@ $userId = !empty($reset['user']) ? (int)$reset['user'] : (
if($userId > 0) if($userId > 0)
try { try {
$userInfo = $users->getUser((string)$userId, 'id'); $userInfo = $msz->usersCtx->users->getUser((string)$userId, 'id');
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Tools::redirect($urls->format('auth-forgot')); Tools::redirect($msz->urls->format('auth-forgot'));
return; return;
} }
@ -35,7 +28,7 @@ $ipAddress = $_SERVER['REMOTE_ADDR'];
$siteIsPrivate = $cfg->getBoolean('private.enable'); $siteIsPrivate = $cfg->getBoolean('private.enable');
$canResetPassword = $siteIsPrivate ? $cfg->getBoolean('private.allow_password_reset', true) : true; $canResetPassword = $siteIsPrivate ? $cfg->getBoolean('private.allow_password_reset', true) : true;
$remainingAttempts = $loginAttempts->countRemainingAttempts($ipAddress); $remainingAttempts = $msz->authCtx->loginAttempts->countRemainingAttempts($ipAddress);
while($canResetPassword) { while($canResetPassword) {
if(!empty($reset) && $userId > 0) { if(!empty($reset) && $userId > 0) {
@ -47,12 +40,12 @@ while($canResetPassword) {
$verifyCode = !empty($reset['verification']) && is_string($reset['verification']) ? $reset['verification'] : ''; $verifyCode = !empty($reset['verification']) && is_string($reset['verification']) ? $reset['verification'] : '';
try { try {
$tokenInfo = $recoveryTokens->getToken(verifyCode: $verifyCode); $tokenInfo = $msz->authCtx->recoveryTokens->getToken(verifyCode: $verifyCode);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
unset($tokenInfo); unset($tokenInfo);
} }
if(empty($tokenInfo) || !$tokenInfo->isValid() || $tokenInfo->getUserId() !== (string)$userInfo->getId()) { if(empty($tokenInfo) || !$tokenInfo->isValid || $tokenInfo->userId !== (string)$userInfo->getId()) {
$notices[] = 'Invalid verification code!'; $notices[] = 'Invalid verification code!';
break; break;
} }
@ -67,21 +60,21 @@ while($canResetPassword) {
break; break;
} }
$passwordValidation = $users->validatePassword($passwordNew); $passwordValidation = $msz->usersCtx->users->validatePassword($passwordNew);
if($passwordValidation !== '') { if($passwordValidation !== '') {
$notices[] = $users->validatePasswordText($passwordValidation); $notices[] = $msz->usersCtx->users->validatePasswordText($passwordValidation);
break; break;
} }
// also disables two factor auth to prevent getting locked out of account entirely // also disables two factor auth to prevent getting locked out of account entirely
// this behaviour should really be replaced with recovery keys... // this behaviour should really be replaced with recovery keys...
$users->updateUser($userInfo, password: $passwordNew, totpKey: ''); $msz->usersCtx->users->updateUser($userInfo, password: $passwordNew, totpKey: '');
$msz->createAuditLog('PASSWORD_RESET', [], $userInfo); $msz->createAuditLog('PASSWORD_RESET', [], $userInfo);
$recoveryTokens->invalidateToken($tokenInfo); $msz->authCtx->recoveryTokens->invalidateToken($tokenInfo);
Tools::redirect($urls->format('auth-login', ['redirect' => '/'])); Tools::redirect($msz->urls->format('auth-login', ['redirect' => '/']));
return; return;
} }
@ -102,39 +95,39 @@ while($canResetPassword) {
} }
try { try {
$forgotUser = $users->getUser($forgot['email'], 'email'); $forgotUser = $msz->usersCtx->users->getUser($forgot['email'], 'email');
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
unset($forgotUser); unset($forgotUser);
} }
if(empty($forgotUser) || $forgotUser->isDeleted()) { if(empty($forgotUser) || $forgotUser->deleted) {
$notices[] = "This e-mail address is not registered with us."; $notices[] = "This e-mail address is not registered with us.";
break; break;
} }
try { try {
$tokenInfo = $recoveryTokens->getToken(userInfo: $forgotUser, remoteAddr: $ipAddress); $tokenInfo = $msz->authCtx->recoveryTokens->getToken(userInfo: $forgotUser, remoteAddr: $ipAddress);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
$tokenInfo = $recoveryTokens->createToken($forgotUser, $ipAddress); $tokenInfo = $msz->authCtx->recoveryTokens->createToken($forgotUser, $ipAddress);
$recoveryMessage = Mailer::template('password-recovery', [ $recoveryMessage = Mailer::template('password-recovery', [
'username' => $forgotUser->getName(), 'username' => $forgotUser->name,
'token' => $tokenInfo->getCode(), 'token' => $tokenInfo->code,
]); ]);
$recoveryMail = Mailer::sendMessage( $recoveryMail = Mailer::sendMessage(
[$forgotUser->getEMailAddress() => $forgotUser->getName()], [$forgotUser->emailAddress => $forgotUser->name],
$recoveryMessage['subject'], $recoveryMessage['message'] $recoveryMessage['subject'], $recoveryMessage['message']
); );
if(!$recoveryMail) { if(!$recoveryMail) {
$notices[] = "Failed to send reset email, please contact the administrator."; $notices[] = "Failed to send reset email, please contact the administrator.";
$recoveryTokens->invalidateToken($tokenInfo); $msz->authCtx->recoveryTokens->invalidateToken($tokenInfo);
break; break;
} }
} }
Tools::redirect($urls->format('auth-reset', ['user' => $forgotUser->getId()])); Tools::redirect($msz->urls->format('auth-reset', ['user' => $forgotUser->getId()]));
return; return;
} }

View file

@ -4,19 +4,11 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
use Misuzu\Users\User; use Misuzu\Users\User;
$urls = $msz->getUrls(); if($msz->authInfo->isLoggedIn) {
$authInfo = $msz->getAuthInfo(); Tools::redirect($msz->urls->format('index'));
if($authInfo->isLoggedIn()) {
Tools::redirect($urls->format('index'));
return; return;
} }
$authCtx = $msz->getAuthContext();
$usersCtx = $msz->getUsersContext();
$users = $usersCtx->getUsers();
$roles = $usersCtx->getRoles();
$config = $msz->getConfig();
$register = !empty($_POST['register']) && is_array($_POST['register']) ? $_POST['register'] : []; $register = !empty($_POST['register']) && is_array($_POST['register']) ? $_POST['register'] : [];
$notices = []; $notices = [];
$ipAddress = $_SERVER['REMOTE_ADDR']; $ipAddress = $_SERVER['REMOTE_ADDR'];
@ -33,8 +25,7 @@ $countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX';
// for fast matching // for fast matching
$restricted = ''; $restricted = '';
$loginAttempts = $authCtx->getLoginAttempts(); $remainingAttempts = $msz->authCtx->loginAttempts->countRemainingAttempts($ipAddress);
$remainingAttempts = $loginAttempts->countRemainingAttempts($ipAddress);
while(!$restricted && !empty($register)) { while(!$restricted && !empty($register)) {
if(!CSRF::validateRequest()) { if(!CSRF::validateRequest()) {
@ -69,28 +60,28 @@ while(!$restricted && !empty($register)) {
break; break;
} }
$usernameValidation = $users->validateName($register['username']); $usernameValidation = $msz->usersCtx->users->validateName($register['username']);
if($usernameValidation !== '') if($usernameValidation !== '')
$notices[] = $users->validateNameText($usernameValidation); $notices[] = $msz->usersCtx->users->validateNameText($usernameValidation);
$emailValidation = $users->validateEMailAddress($register['email']); $emailValidation = $msz->usersCtx->users->validateEMailAddress($register['email']);
if($emailValidation !== '') if($emailValidation !== '')
$notices[] = $users->validateEMailAddressText($emailValidation); $notices[] = $msz->usersCtx->users->validateEMailAddressText($emailValidation);
if($register['password_confirm'] !== $register['password']) if($register['password_confirm'] !== $register['password'])
$notices[] = 'The given passwords don\'t match.'; $notices[] = 'The given passwords don\'t match.';
$passwordValidation = $users->validatePassword($register['password']); $passwordValidation = $msz->usersCtx->users->validatePassword($register['password']);
if($passwordValidation !== '') if($passwordValidation !== '')
$notices[] = $users->validatePasswordText($passwordValidation); $notices[] = $msz->usersCtx->users->validatePasswordText($passwordValidation);
if(!empty($notices)) if(!empty($notices))
break; break;
$defaultRoleInfo = $roles->getDefaultRole(); $defaultRoleInfo = $msz->usersCtx->roles->getDefaultRole();
try { try {
$userInfo = $users->createUser( $userInfo = $msz->usersCtx->users->createUser(
$register['username'], $register['username'],
$register['password'], $register['password'],
$register['email'], $register['email'],
@ -103,14 +94,14 @@ while(!$restricted && !empty($register)) {
break; break;
} }
$users->addRoles($userInfo, $defaultRoleInfo); $msz->usersCtx->users->addRoles($userInfo, $defaultRoleInfo);
$config->setString('users.newest', $userInfo->getId()); $msz->config->setString('users.newest', $userInfo->getId());
$msz->getPerms()->precalculatePermissions( $msz->perms->precalculatePermissions(
$msz->getForumContext()->getCategories(), $msz->forumCtx->categories,
[$userInfo->getId()] [$userInfo->getId()]
); );
Tools::redirect($urls->format('auth-login-welcome', ['username' => $userInfo->getName()])); Tools::redirect($msz->urls->format('auth-login-welcome', ['username' => $userInfo->name]));
return; return;
} }

View file

@ -3,21 +3,20 @@ namespace Misuzu;
use Misuzu\Auth\AuthTokenCookie; use Misuzu\Auth\AuthTokenCookie;
$urls = $msz->getUrls();
if(CSRF::validateRequest()) { if(CSRF::validateRequest()) {
$tokenInfo = $msz->getAuthInfo()->getTokenInfo(); $tokenInfo = $msz->authInfo->tokenInfo;
if($tokenInfo->hasImpersonatedUserId()) { if($tokenInfo->hasImpersonatedUserId) {
$impUserId = $tokenInfo->getImpersonatedUserId(); $impUserId = $tokenInfo->impersonatedUserId;
$tokenBuilder = $tokenInfo->toBuilder(); $tokenBuilder = $tokenInfo->toBuilder();
$tokenBuilder->removeImpersonatedUserId(); $tokenBuilder->removeImpersonatedUserId();
$tokenInfo = $tokenBuilder->toInfo();
$tokenInfo = $tokenBuilder->toInfo();
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo)); AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
Tools::redirect($urls->format('manage-user', ['user' => $impUserId])); Tools::redirect($msz->urls->format('manage-user', ['user' => $impUserId]));
return; return;
} }
} }
Tools::redirect($urls->format('index')); Tools::redirect($msz->urls->format('index'));

View file

@ -5,43 +5,35 @@ use RuntimeException;
use Misuzu\TOTPGenerator; use Misuzu\TOTPGenerator;
use Misuzu\Auth\AuthTokenCookie; use Misuzu\Auth\AuthTokenCookie;
$urls = $msz->getUrls(); if($msz->authInfo->isLoggedIn) {
$authInfo = $msz->getAuthInfo(); Tools::redirect($msz->urls->format('index'));
if($authInfo->isLoggedIn()) {
Tools::redirect($urls->format('index'));
return; return;
} }
$authCtx = $msz->getAuthContext();
$users = $msz->getUsersContext()->getUsers();
$sessions = $authCtx->getSessions();
$tfaSessions = $authCtx->getTwoFactorAuthSessions();
$loginAttempts = $authCtx->getLoginAttempts();
$ipAddress = $_SERVER['REMOTE_ADDR']; $ipAddress = $_SERVER['REMOTE_ADDR'];
$countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX'; $countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX';
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? ''; $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$twofactor = !empty($_POST['twofactor']) && is_array($_POST['twofactor']) ? $_POST['twofactor'] : []; $twofactor = !empty($_POST['twofactor']) && is_array($_POST['twofactor']) ? $_POST['twofactor'] : [];
$notices = []; $notices = [];
$remainingAttempts = $loginAttempts->countRemainingAttempts($ipAddress); $remainingAttempts = $msz->authCtx->loginAttempts->countRemainingAttempts($ipAddress);
$tokenString = !empty($_GET['token']) && is_string($_GET['token']) ? $_GET['token'] : ( $tokenString = !empty($_GET['token']) && is_string($_GET['token']) ? $_GET['token'] : (
!empty($twofactor['token']) && is_string($twofactor['token']) ? $twofactor['token'] : '' !empty($twofactor['token']) && is_string($twofactor['token']) ? $twofactor['token'] : ''
); );
$tokenUserId = $tfaSessions->getTokenUserId($tokenString); $tokenUserId = $msz->authCtx->tfaSessions->getTokenUserId($tokenString);
if(empty($tokenUserId)) { if(empty($tokenUserId)) {
Tools::redirect($urls->format('auth-login')); Tools::redirect($msz->urls->format('auth-login'));
return; return;
} }
$userInfo = $users->getUser($tokenUserId, 'id'); $userInfo = $msz->usersCtx->users->getUser($tokenUserId, 'id');
// checking user_totp_key specifically because there's a fringe chance that // checking user_totp_key specifically because there's a fringe chance that
// there's a token present, but totp is actually disabled // there's a token present, but totp is actually disabled
if(!$userInfo->hasTOTPKey()) { if(!$userInfo->hasTOTP) {
Tools::redirect($urls->format('auth-login')); Tools::redirect($msz->urls->format('auth-login'));
return; return;
} }
@ -65,7 +57,7 @@ while(!empty($twofactor)) {
} }
$clientInfo = ClientInfo::fromRequest(); $clientInfo = ClientInfo::fromRequest();
$totp = new TOTPGenerator($userInfo->getTOTPKey()); $totp = new TOTPGenerator($userInfo->totpKey);
if(!in_array($twofactor['code'], $totp->generateRange())) { if(!in_array($twofactor['code'], $totp->generateRange())) {
$notices[] = sprintf( $notices[] = sprintf(
@ -73,21 +65,21 @@ while(!empty($twofactor)) {
$remainingAttempts - 1, $remainingAttempts - 1,
$remainingAttempts === 2 ? '' : 's' $remainingAttempts === 2 ? '' : 's'
); );
$loginAttempts->recordAttempt(false, $ipAddress, $countryCode, $userAgent, $clientInfo, $userInfo); $msz->authCtx->loginAttempts->recordAttempt(false, $ipAddress, $countryCode, $userAgent, $clientInfo, $userInfo);
break; break;
} }
$loginAttempts->recordAttempt(true, $ipAddress, $countryCode, $userAgent, $clientInfo, $userInfo); $msz->authCtx->loginAttempts->recordAttempt(true, $ipAddress, $countryCode, $userAgent, $clientInfo, $userInfo);
$tfaSessions->deleteToken($tokenString); $msz->authCtx->tfaSessions->deleteToken($tokenString);
try { try {
$sessionInfo = $sessions->createSession($userInfo, $ipAddress, $countryCode, $userAgent, $clientInfo); $sessionInfo = $msz->authCtx->sessions->createSession($userInfo, $ipAddress, $countryCode, $userAgent, $clientInfo);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
$notices[] = "Something broke while creating a session for you, please tell an administrator or developer about this!"; $notices[] = "Something broke while creating a session for you, please tell an administrator or developer about this!";
break; break;
} }
$tokenBuilder = $authInfo->getTokenInfo()->toBuilder(); $tokenBuilder = $msz->authInfo->tokenInfo->toBuilder();
$tokenBuilder->setUserId($userInfo); $tokenBuilder->setUserId($userInfo);
$tokenBuilder->setSessionToken($sessionInfo); $tokenBuilder->setSessionToken($sessionInfo);
$tokenBuilder->removeImpersonatedUserId(); $tokenBuilder->removeImpersonatedUserId();
@ -96,7 +88,7 @@ while(!empty($twofactor)) {
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo)); AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
if(!Tools::isLocalURL($redirect)) if(!Tools::isLocalURL($redirect))
$redirect = $urls->format('index'); $redirect = $msz->urls->format('index');
Tools::redirect($redirect); Tools::redirect($redirect);
return; return;
@ -104,7 +96,7 @@ while(!empty($twofactor)) {
Template::render('auth.twofactor', [ Template::render('auth.twofactor', [
'twofactor_notices' => $notices, 'twofactor_notices' => $notices,
'twofactor_redirect' => !empty($_GET['redirect']) && is_string($_GET['redirect']) ? $_GET['redirect'] : $urls->format('index'), 'twofactor_redirect' => !empty($_GET['redirect']) && is_string($_GET['redirect']) ? $_GET['redirect'] : $msz->urls->format('index'),
'twofactor_attempts_remaining' => $remainingAttempts, 'twofactor_attempts_remaining' => $remainingAttempts,
'twofactor_token' => $tokenString, 'twofactor_token' => $tokenString,
]); ]);

View file

@ -3,8 +3,7 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
$usersCtx = $msz->getUsersContext(); $redirect = filter_input(INPUT_GET, 'return') ?? $_SERVER['HTTP_REFERER'] ?? $msz->urls->format('index');
$redirect = filter_input(INPUT_GET, 'return') ?? $_SERVER['HTTP_REFERER'] ?? $msz->getUrls()->format('index');
if(!Tools::isLocalURL($redirect)) if(!Tools::isLocalURL($redirect))
Template::displayInfo('Possible request forgery detected.', 403); Template::displayInfo('Possible request forgery detected.', 403);
@ -12,17 +11,13 @@ if(!Tools::isLocalURL($redirect))
if(!CSRF::validateRequest()) if(!CSRF::validateRequest())
Template::displayInfo("Couldn't verify this request, please refresh the page and try again.", 403); Template::displayInfo("Couldn't verify this request, please refresh the page and try again.", 403);
$authInfo = $msz->getAuthInfo(); if(!$msz->authInfo->isLoggedIn)
if(!$authInfo->isLoggedIn())
Template::displayInfo('You must be logged in to manage comments.', 403); Template::displayInfo('You must be logged in to manage comments.', 403);
$currentUserInfo = $authInfo->getUserInfo(); if($msz->usersCtx->hasActiveBan($msz->authInfo->userInfo))
if($usersCtx->hasActiveBan($currentUserInfo))
Template::displayInfo('You have been banned, check your profile for more information.', 403); Template::displayInfo('You have been banned, check your profile for more information.', 403);
$comments = $msz->getComments(); $perms = $msz->authInfo->getPerms('global');
$perms = $authInfo->getPerms('global');
$commentId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT); $commentId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
$commentMode = (string)filter_input(INPUT_GET, 'm'); $commentMode = (string)filter_input(INPUT_GET, 'm');
@ -30,12 +25,12 @@ $commentVote = (int)filter_input(INPUT_GET, 'v', FILTER_SANITIZE_NUMBER_INT);
if(!empty($commentId)) { if(!empty($commentId)) {
try { try {
$commentInfo = $comments->getPost($commentId); $commentInfo = $msz->comments->getPost($commentId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::displayInfo('Post not found.', 404); Template::displayInfo('Post not found.', 404);
} }
$categoryInfo = $comments->getCategory(postInfo: $commentInfo); $categoryInfo = $msz->comments->getCategory(postInfo: $commentInfo);
} }
if($commentMode !== 'create' && empty($commentInfo)) if($commentMode !== 'create' && empty($commentInfo))
@ -44,77 +39,77 @@ if($commentMode !== 'create' && empty($commentInfo))
switch($commentMode) { switch($commentMode) {
case 'pin': case 'pin':
case 'unpin': case 'unpin':
if(!$perms->check(Perm::G_COMMENTS_PIN) && !$categoryInfo->isOwner($currentUserInfo)) if(!$perms->check(Perm::G_COMMENTS_PIN) && !$categoryInfo->isOwner($msz->authInfo->userInfo))
Template::displayInfo("You're not allowed to pin comments.", 403); Template::displayInfo("You're not allowed to pin comments.", 403);
if($commentInfo->isDeleted()) if($commentInfo->deleted)
Template::displayInfo("This comment doesn't exist!", 400); Template::displayInfo("This comment doesn't exist!", 400);
if($commentInfo->isReply()) if($commentInfo->isReply)
Template::displayInfo("You can't pin replies!", 400); Template::displayInfo("You can't pin replies!", 400);
$isPinning = $commentMode === 'pin'; $isPinning = $commentMode === 'pin';
if($isPinning) { if($isPinning) {
if($commentInfo->isPinned()) if($commentInfo->pinned)
Template::displayInfo('This comment is already pinned.', 400); Template::displayInfo('This comment is already pinned.', 400);
$comments->pinPost($commentInfo); $msz->comments->pinPost($commentInfo);
} else { } else {
if(!$commentInfo->isPinned()) if(!$commentInfo->pinned)
Template::displayInfo("This comment isn't pinned yet.", 400); Template::displayInfo("This comment isn't pinned yet.", 400);
$comments->unpinPost($commentInfo); $msz->comments->unpinPost($commentInfo);
} }
Tools::redirect($redirect . '#comment-' . $commentInfo->getId()); Tools::redirect($redirect . '#comment-' . $commentInfo->id);
break; break;
case 'vote': case 'vote':
if(!$perms->check(Perm::G_COMMENTS_VOTE) && !$categoryInfo->isOwner($currentUserInfo)) if(!$perms->check(Perm::G_COMMENTS_VOTE) && !$categoryInfo->isOwner($msz->authInfo->userInfo))
Template::displayInfo("You're not allowed to vote on comments.", 403); Template::displayInfo("You're not allowed to vote on comments.", 403);
if($commentInfo->isDeleted()) if($commentInfo->deleted)
Template::displayInfo("This comment doesn't exist!", 400); Template::displayInfo("This comment doesn't exist!", 400);
if($commentVote > 0) if($commentVote > 0)
$comments->addPostPositiveVote($commentInfo, $currentUserInfo); $msz->comments->addPostPositiveVote($commentInfo, $msz->authInfo->userInfo);
elseif($commentVote < 0) elseif($commentVote < 0)
$comments->addPostNegativeVote($commentInfo, $currentUserInfo); $msz->comments->addPostNegativeVote($commentInfo, $msz->authInfo->userInfo);
else else
$comments->removePostVote($commentInfo, $currentUserInfo); $msz->comments->removePostVote($commentInfo, $msz->authInfo->userInfo);
Tools::redirect($redirect . '#comment-' . $commentInfo->getId()); Tools::redirect($redirect . '#comment-' . $commentInfo->id);
break; break;
case 'delete': case 'delete':
$canDelete = $perms->check(Perm::G_COMMENTS_DELETE_OWN | Perm::G_COMMENTS_DELETE_ANY); $canDelete = $perms->check(Perm::G_COMMENTS_DELETE_OWN | Perm::G_COMMENTS_DELETE_ANY);
if(!$canDelete && !$categoryInfo->isOwner($currentUserInfo)) if(!$canDelete && !$categoryInfo->isOwner($msz->authInfo->userInfo))
Template::displayInfo("You're not allowed to delete comments.", 403); Template::displayInfo("You're not allowed to delete comments.", 403);
$canDeleteAny = $perms->check(Perm::G_COMMENTS_DELETE_ANY); $canDeleteAny = $perms->check(Perm::G_COMMENTS_DELETE_ANY);
if($commentInfo->isDeleted()) if($commentInfo->deleted)
Template::displayInfo( Template::displayInfo(
$canDeleteAny ? 'This comment is already marked for deletion.' : "This comment doesn't exist.", $canDeleteAny ? 'This comment is already marked for deletion.' : "This comment doesn't exist.",
400 400
); );
$isOwnComment = $commentInfo->getUserId() === $currentUserInfo->getId(); $isOwnComment = $commentInfo->userId === $msz->authInfo->userInfo->getId();
$isModAction = $canDeleteAny && !$isOwnComment; $isModAction = $canDeleteAny && !$isOwnComment;
if(!$isModAction && !$isOwnComment) if(!$isModAction && !$isOwnComment)
Template::displayInfo("You're not allowed to delete comments made by others.", 403); Template::displayInfo("You're not allowed to delete comments made by others.", 403);
$comments->deletePost($commentInfo); $msz->comments->deletePost($commentInfo);
if($isModAction) { if($isModAction) {
$msz->createAuditLog('COMMENT_ENTRY_DELETE_MOD', [ $msz->createAuditLog('COMMENT_ENTRY_DELETE_MOD', [
$commentInfo->getId(), $commentInfo->id,
$commentUserId = $commentInfo->getUserId(), $commentUserId = $commentInfo->userId,
'<username>', '<username>',
]); ]);
} else { } else {
$msz->createAuditLog('COMMENT_ENTRY_DELETE', [$commentInfo->getId()]); $msz->createAuditLog('COMMENT_ENTRY_DELETE', [$commentInfo->id]);
} }
Tools::redirect($redirect); Tools::redirect($redirect);
@ -124,22 +119,22 @@ switch($commentMode) {
if(!$perms->check(Perm::G_COMMENTS_DELETE_ANY)) if(!$perms->check(Perm::G_COMMENTS_DELETE_ANY))
Template::displayInfo("You're not allowed to restore deleted comments.", 403); Template::displayInfo("You're not allowed to restore deleted comments.", 403);
if(!$commentInfo->isDeleted()) if(!$commentInfo->deleted)
Template::displayInfo("This comment isn't in a deleted state.", 400); Template::displayInfo("This comment isn't in a deleted state.", 400);
$comments->restorePost($commentInfo); $msz->comments->restorePost($commentInfo);
$msz->createAuditLog('COMMENT_ENTRY_RESTORE', [ $msz->createAuditLog('COMMENT_ENTRY_RESTORE', [
$commentInfo->getId(), $commentInfo->id,
$commentUserId = $commentInfo->getUserId(), $commentUserId = $commentInfo->userId,
'<username>', '<username>',
]); ]);
Tools::redirect($redirect . '#comment-' . $commentInfo->getId()); Tools::redirect($redirect . '#comment-' . $commentInfo->id);
break; break;
case 'create': case 'create':
if(!$perms->check(Perm::G_COMMENTS_CREATE) && !$categoryInfo->isOwner($currentUserInfo)) if(!$perms->check(Perm::G_COMMENTS_CREATE) && !$categoryInfo->isOwner($msz->authInfo->userInfo))
Template::displayInfo("You're not allowed to post comments.", 403); Template::displayInfo("You're not allowed to post comments.", 403);
if(empty($_POST['comment']) || !is_array($_POST['comment'])) if(empty($_POST['comment']) || !is_array($_POST['comment']))
@ -149,13 +144,13 @@ switch($commentMode) {
$categoryId = isset($_POST['comment']['category']) && is_string($_POST['comment']['category']) $categoryId = isset($_POST['comment']['category']) && is_string($_POST['comment']['category'])
? (int)$_POST['comment']['category'] ? (int)$_POST['comment']['category']
: 0; : 0;
$categoryInfo = $comments->getCategory(categoryId: $categoryId); $categoryInfo = $msz->comments->getCategory(categoryId: $categoryId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::displayInfo('This comment category doesn\'t exist.', 404); Template::displayInfo('This comment category doesn\'t exist.', 404);
} }
$canLock = $perms->check(Perm::G_COMMENTS_LOCK); $canLock = $perms->check(Perm::G_COMMENTS_LOCK);
if($categoryInfo->isLocked() && !$canLock) if($categoryInfo->locked && !$canLock)
Template::displayInfo('This comment category has been locked.', 403); Template::displayInfo('This comment category has been locked.', 403);
$commentText = !empty($_POST['comment']['text']) && is_string($_POST['comment']['text']) ? $_POST['comment']['text'] : ''; $commentText = !empty($_POST['comment']['text']) && is_string($_POST['comment']['text']) ? $_POST['comment']['text'] : '';
@ -164,10 +159,10 @@ switch($commentMode) {
$commentPin = !empty($_POST['comment']['pin']) && $perms->check(Perm::G_COMMENTS_PIN); $commentPin = !empty($_POST['comment']['pin']) && $perms->check(Perm::G_COMMENTS_PIN);
if($commentLock) { if($commentLock) {
if($categoryInfo->isLocked()) if($categoryInfo->locked)
$comments->unlockCategory($categoryInfo); $msz->comments->unlockCategory($categoryInfo);
else else
$comments->lockCategory($categoryInfo); $msz->comments->lockCategory($categoryInfo);
} }
if(strlen($commentText) > 0) { if(strlen($commentText) > 0) {
@ -186,22 +181,22 @@ switch($commentMode) {
if($commentReply > 0) { if($commentReply > 0) {
try { try {
$parentInfo = $comments->getPost($commentReply); $parentInfo = $msz->comments->getPost($commentReply);
} catch(RuntimeException $ex) {} } catch(RuntimeException $ex) {}
if(!isset($parentInfo) || $parentInfo->isDeleted()) if(!isset($parentInfo) || $parentInfo->deleted)
Template::displayInfo('The comment you tried to reply to does not exist.', 404); Template::displayInfo('The comment you tried to reply to does not exist.', 404);
} }
$commentInfo = $comments->createPost( $commentInfo = $msz->comments->createPost(
$categoryInfo, $categoryInfo,
$parentInfo ?? null, $parentInfo ?? null,
$currentUserInfo, $msz->authInfo->userInfo,
$commentText, $commentText,
$commentPin $commentPin
); );
Tools::redirect($redirect . '#comment-' . $commentInfo->getId()); Tools::redirect($redirect . '#comment-' . $commentInfo->id);
break; break;
default: default:

View file

@ -4,43 +4,32 @@ namespace Misuzu;
use stdClass; use stdClass;
use RuntimeException; use RuntimeException;
$forumCtx = $msz->getForumContext();
$forumCategories = $forumCtx->getCategories();
$forumTopics = $forumCtx->getTopics();
$forumPosts = $forumCtx->getPosts();
$usersCtx = $msz->getUsersContext();
$categoryId = (int)filter_input(INPUT_GET, 'f', FILTER_SANITIZE_NUMBER_INT); $categoryId = (int)filter_input(INPUT_GET, 'f', FILTER_SANITIZE_NUMBER_INT);
try { try {
$categoryInfo = $forumCategories->getCategory(categoryId: $categoryId); $categoryInfo = $msz->forumCtx->categories->getCategory(categoryId: $categoryId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
$authInfo = $msz->getAuthInfo(); $perms = $msz->authInfo->getPerms('forum', $categoryInfo);
$perms = $authInfo->getPerms('forum', $categoryInfo);
$currentUser = $authInfo->getUserInfo(); $currentUser = $msz->authInfo->userInfo;
$currentUserId = $currentUser === null ? '0' : $currentUser->getId(); $currentUserId = $currentUser === null ? '0' : $currentUser->getId();
if(!$perms->check(Perm::F_CATEGORY_VIEW)) if(!$perms->check(Perm::F_CATEGORY_VIEW))
Template::throwError(403); Template::throwError(403);
if($usersCtx->hasActiveBan($currentUser)) if($msz->usersCtx->hasActiveBan($currentUser))
$perms = $perms->apply(fn($calc) => $calc & (Perm::F_CATEGORY_LIST | Perm::F_CATEGORY_VIEW)); $perms = $perms->apply(fn($calc) => $calc & (Perm::F_CATEGORY_LIST | Perm::F_CATEGORY_VIEW));
if($categoryInfo->isLink()) { if($categoryInfo->isLink) {
if($categoryInfo->hasLinkTarget()) { $msz->forumCtx->categories->incrementCategoryClicks($categoryInfo);
$forumCategories->incrementCategoryClicks($categoryInfo); Tools::redirect($categoryInfo->linkTarget ?? '/');
Tools::redirect($categoryInfo->getLinkTarget()); return;
return;
}
Template::throwError(404);
} }
$forumPagination = new Pagination($forumTopics->countTopics( $forumPagination = new Pagination($msz->forumCtx->topics->countTopics(
categoryInfo: $categoryInfo, categoryInfo: $categoryInfo,
global: true, global: true,
deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false
@ -52,11 +41,11 @@ if(!$forumPagination->hasValidOffset())
$children = []; $children = [];
$topics = []; $topics = [];
if($categoryInfo->mayHaveChildren()) { if($categoryInfo->mayHaveChildren) {
$children = $forumCategories->getCategoryChildren($categoryInfo, hidden: false, asTree: true); $children = $msz->forumCtx->categories->getCategoryChildren($categoryInfo, hidden: false, asTree: true);
foreach($children as $childId => $child) { foreach($children as $childId => $child) {
$childPerms = $authInfo->getPerms('forum', $child->info); $childPerms = $msz->authInfo->getPerms('forum', $child->info);
if(!$childPerms->check(Perm::F_CATEGORY_LIST)) { if(!$childPerms->check(Perm::F_CATEGORY_LIST)) {
unset($category->children[$childId]); unset($category->children[$childId]);
continue; continue;
@ -64,9 +53,9 @@ if($categoryInfo->mayHaveChildren()) {
$childUnread = false; $childUnread = false;
if($child->info->mayHaveChildren()) { if($child->info->mayHaveChildren) {
foreach($child->children as $grandChildId => $grandChild) { foreach($child->children as $grandChildId => $grandChild) {
$grandChildPerms = $authInfo->getPerms('forum', $grandChild->info); $grandChildPerms = $msz->authInfo->getPerms('forum', $grandChild->info);
if(!$grandChildPerms->check(Perm::F_CATEGORY_LIST)) { if(!$grandChildPerms->check(Perm::F_CATEGORY_LIST)) {
unset($child->children[$grandChildId]); unset($child->children[$grandChildId]);
continue; continue;
@ -74,15 +63,15 @@ if($categoryInfo->mayHaveChildren()) {
$grandChildUnread = false; $grandChildUnread = false;
if($grandChild->info->mayHaveTopics()) { if($grandChild->info->mayHaveTopics) {
$catIds = [$grandChild->info->getId()]; $catIds = [$grandChild->info->id];
foreach($grandChild->childIds as $greatGrandChildId) { foreach($grandChild->childIds as $greatGrandChildId) {
$greatGrandChildPerms = $authInfo->getPerms('forum', $greatGrandChildId); $greatGrandChildPerms = $msz->authInfo->getPerms('forum', $greatGrandChildId);
if(!$greatGrandChildPerms->check(Perm::F_CATEGORY_LIST)) if(!$greatGrandChildPerms->check(Perm::F_CATEGORY_LIST))
$catIds[] = $greatGrandChildId; $catIds[] = $greatGrandChildId;
} }
$grandChildUnread = $forumCategories->checkCategoryUnread($catIds, $currentUser); $grandChildUnread = $msz->forumCtx->categories->checkCategoryUnread($catIds, $currentUser);
if($grandChildUnread) if($grandChildUnread)
$childUnread = true; $childUnread = true;
} }
@ -92,16 +81,16 @@ if($categoryInfo->mayHaveChildren()) {
} }
} }
if($child->info->mayHaveChildren() || $child->info->mayHaveTopics()) { if($child->info->mayHaveChildren || $child->info->mayHaveTopics) {
$catIds = [$child->info->getId()]; $catIds = [$child->info->id];
foreach($child->childIds as $grandChildId) { foreach($child->childIds as $grandChildId) {
$grandChildPerms = $authInfo->getPerms('forum', $grandChildId); $grandChildPerms = $msz->authInfo->getPerms('forum', $grandChildId);
if($grandChildPerms->check(Perm::F_CATEGORY_LIST)) if($grandChildPerms->check(Perm::F_CATEGORY_LIST))
$catIds[] = $grandChildId; $catIds[] = $grandChildId;
} }
try { try {
$lastPostInfo = $forumPosts->getPost(categoryInfos: $catIds, getLast: true, deleted: false); $lastPostInfo = $msz->forumCtx->posts->getPost(categoryInfos: $catIds, getLast: true, deleted: false);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
$lastPostInfo = null; $lastPostInfo = null;
} }
@ -109,25 +98,25 @@ if($categoryInfo->mayHaveChildren()) {
if($lastPostInfo !== null) { if($lastPostInfo !== null) {
$child->lastPost = new stdClass; $child->lastPost = new stdClass;
$child->lastPost->info = $lastPostInfo; $child->lastPost->info = $lastPostInfo;
$child->lastPost->topicInfo = $forumTopics->getTopic(postInfo: $lastPostInfo); $child->lastPost->topicInfo = $msz->forumCtx->topics->getTopic(postInfo: $lastPostInfo);
if($lastPostInfo->hasUserId()) { if($lastPostInfo->userId !== null) {
$child->lastPost->user = $usersCtx->getUserInfo($lastPostInfo->getUserId()); $child->lastPost->user = $msz->usersCtx->getUserInfo($lastPostInfo->userId);
$child->lastPost->colour = $usersCtx->getUserColour($child->lastPost->user); $child->lastPost->colour = $msz->usersCtx->getUserColour($child->lastPost->user);
} }
} }
} }
if($child->info->mayHaveTopics() && !$childUnread) if($child->info->mayHaveTopics && !$childUnread)
$childUnread = $forumCategories->checkCategoryUnread($child->info, $currentUser); $childUnread = $msz->forumCtx->categories->checkCategoryUnread($child->info, $currentUser);
$child->perms = $childPerms; $child->perms = $childPerms;
$child->unread = $childUnread; $child->unread = $childUnread;
} }
} }
if($categoryInfo->mayHaveTopics()) { if($categoryInfo->mayHaveTopics) {
$topicInfos = $forumTopics->getTopics( $topicInfos = $msz->forumCtx->topics->getTopics(
categoryInfo: $categoryInfo, categoryInfo: $categoryInfo,
global: true, global: true,
deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false, deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false,
@ -137,25 +126,25 @@ if($categoryInfo->mayHaveTopics()) {
foreach($topicInfos as $topicInfo) { foreach($topicInfos as $topicInfo) {
$topics[] = $topic = new stdClass; $topics[] = $topic = new stdClass;
$topic->info = $topicInfo; $topic->info = $topicInfo;
$topic->unread = $forumTopics->checkTopicUnread($topicInfo, $currentUser); $topic->unread = $msz->forumCtx->topics->checkTopicUnread($topicInfo, $currentUser);
$topic->participated = $forumTopics->checkTopicParticipated($topicInfo, $currentUser); $topic->participated = $msz->forumCtx->topics->checkTopicParticipated($topicInfo, $currentUser);
if($topicInfo->hasUserId()) { if($topicInfo->userId !== null) {
$topic->user = $usersCtx->getUserInfo($topicInfo->getUserId()); $topic->user = $msz->usersCtx->getUserInfo($topicInfo->userId);
$topic->colour = $usersCtx->getUserColour($topic->user); $topic->colour = $msz->usersCtx->getUserColour($topic->user);
} }
try { try {
$topic->lastPost = new stdClass; $topic->lastPost = new stdClass;
$topic->lastPost->info = $lastPostInfo = $forumPosts->getPost( $topic->lastPost->info = $lastPostInfo = $msz->forumCtx->posts->getPost(
topicInfo: $topicInfo, topicInfo: $topicInfo,
getLast: true, getLast: true,
deleted: $topicInfo->isDeleted() ? null : false, deleted: $topicInfo->deleted ? null : false,
); );
if($lastPostInfo->hasUserId()) { if($lastPostInfo->userId !== null) {
$topic->lastPost->user = $usersCtx->getUserInfo($lastPostInfo->getUserId()); $topic->lastPost->user = $msz->usersCtx->getUserInfo($lastPostInfo->userId);
$topic->lastPost->colour = $usersCtx->getUserColour($topic->lastPost->user); $topic->lastPost->colour = $msz->usersCtx->getUserColour($topic->lastPost->user);
} }
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
$topic->lastPost = null; $topic->lastPost = null;
@ -168,8 +157,8 @@ $perms = $perms->checkMany([
]); ]);
Template::render('forum.forum', [ Template::render('forum.forum', [
'forum_breadcrumbs' => iterator_to_array($forumCategories->getCategoryAncestry($categoryInfo)), 'forum_breadcrumbs' => iterator_to_array($msz->forumCtx->categories->getCategoryAncestry($categoryInfo)),
'global_accent_colour' => $forumCategories->getCategoryColour($categoryInfo), 'global_accent_colour' => $msz->forumCtx->categories->getCategoryColour($categoryInfo),
'forum_info' => $categoryInfo, 'forum_info' => $categoryInfo,
'forum_children' => $children, 'forum_children' => $children,
'forum_topics' => $topics, 'forum_topics' => $topics,

View file

@ -4,42 +4,36 @@ namespace Misuzu;
use stdClass; use stdClass;
use RuntimeException; use RuntimeException;
$forumCtx = $msz->getForumContext();
$forumCategories = $forumCtx->getCategories();
$forumTopics = $forumCtx->getTopics();
$forumPosts = $forumCtx->getPosts();
$usersCtx = $msz->getUsersContext();
$mode = (string)filter_input(INPUT_GET, 'm'); $mode = (string)filter_input(INPUT_GET, 'm');
$authInfo = $msz->getAuthInfo(); $currentUser = $msz->authInfo->userInfo;
$currentUser = $authInfo->getUserInfo();
$currentUserId = $currentUser === null ? '0' : $currentUser->getId(); $currentUserId = $currentUser === null ? '0' : $currentUser->getId();
if($mode === 'mark') { if($mode === 'mark') {
if(!$authInfo->isLoggedIn()) if(!$msz->authInfo->isLoggedIn)
Template::throwError(403); Template::throwError(403);
$categoryId = filter_input(INPUT_GET, 'f', FILTER_SANITIZE_NUMBER_INT); $categoryId = filter_input(INPUT_GET, 'f', FILTER_SANITIZE_NUMBER_INT);
if($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) { if($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$categoryInfos = $categoryId === null $categoryInfos = $categoryId === null
? $forumCategories->getCategories() ? $msz->forumCtx->categories->getCategories()
: $forumCategories->getCategoryChildren(parentInfo: $categoryId, includeSelf: true); : $msz->forumCtx->categories->getCategoryChildren(parentInfo: $categoryId, includeSelf: true);
foreach($categoryInfos as $categoryInfo) { foreach($categoryInfos as $categoryInfo) {
$perms = $authInfo->getPerms('forum', $categoryInfo); $perms = $msz->authInfo->getPerms('forum', $categoryInfo);
if($perms->check(Perm::F_CATEGORY_LIST)) if($perms->check(Perm::F_CATEGORY_LIST))
$forumCategories->updateUserReadCategory($userInfo, $categoryInfo); $msz->forumCtx->categories->updateUserReadCategory($userInfo, $categoryInfo);
} }
Tools::redirect($msz->getUrls()->format($categoryId ? 'forum-category' : 'forum-index', ['forum' => $categoryId])); Tools::redirect($msz->urls->format($categoryId ? 'forum-category' : 'forum-index', ['forum' => $categoryId]));
return; return;
} }
Template::render('confirm', [ Template::render('confirm', [
'title' => 'Mark forum as read', 'title' => 'Mark forum as read',
'message' => 'Are you sure you want to mark ' . ($categoryId < 1 ? 'the entire' : 'this') . ' forum as read?', 'message' => 'Are you sure you want to mark ' . ($categoryId < 1 ? 'the entire' : 'this') . ' forum as read?',
'return' => $msz->getUrls()->format($categoryId ? 'forum-category' : 'forum-index', ['forum' => $categoryId]), 'return' => $msz->urls->format($categoryId ? 'forum-category' : 'forum-index', ['forum' => $categoryId]),
'params' => [ 'params' => [
'forum' => $categoryId, 'forum' => $categoryId,
] ]
@ -50,10 +44,10 @@ if($mode === 'mark') {
if($mode !== '') if($mode !== '')
Template::throwError(404); Template::throwError(404);
$categories = $forumCategories->getCategories(hidden: false, asTree: true); $categories = $msz->forumCtx->categories->getCategories(hidden: false, asTree: true);
foreach($categories as $categoryId => $category) { foreach($categories as $categoryId => $category) {
$perms = $authInfo->getPerms('forum', $category->info); $perms = $msz->authInfo->getPerms('forum', $category->info);
if(!$perms->check(Perm::F_CATEGORY_LIST)) { if(!$perms->check(Perm::F_CATEGORY_LIST)) {
unset($categories[$categoryId]); unset($categories[$categoryId]);
continue; continue;
@ -61,9 +55,9 @@ foreach($categories as $categoryId => $category) {
$unread = false; $unread = false;
if($category->info->mayHaveChildren()) if($category->info->mayHaveChildren)
foreach($category->children as $childId => $child) { foreach($category->children as $childId => $child) {
$childPerms = $authInfo->getPerms('forum', $child->info); $childPerms = $msz->authInfo->getPerms('forum', $child->info);
if(!$childPerms->check(Perm::F_CATEGORY_LIST)) { if(!$childPerms->check(Perm::F_CATEGORY_LIST)) {
unset($category->children[$childId]); unset($category->children[$childId]);
continue; continue;
@ -71,10 +65,10 @@ foreach($categories as $categoryId => $category) {
$childUnread = false; $childUnread = false;
if($category->info->isListing()) { if($category->info->isListing) {
if($child->info->mayHaveChildren()) { if($child->info->mayHaveChildren) {
foreach($child->children as $grandChildId => $grandChild) { foreach($child->children as $grandChildId => $grandChild) {
$grandChildPerms = $authInfo->getPerms('forum', $grandChild->info); $grandChildPerms = $msz->authInfo->getPerms('forum', $grandChild->info);
if(!$grandChildPerms->check(Perm::F_CATEGORY_LIST)) { if(!$grandChildPerms->check(Perm::F_CATEGORY_LIST)) {
unset($child->children[$grandChildId]); unset($child->children[$grandChildId]);
continue; continue;
@ -82,15 +76,15 @@ foreach($categories as $categoryId => $category) {
$grandChildUnread = false; $grandChildUnread = false;
if($grandChild->info->mayHaveTopics()) { if($grandChild->info->mayHaveTopics) {
$catIds = [$grandChild->info->getId()]; $catIds = [$grandChild->info->id];
foreach($grandChild->childIds as $greatGrandChildId) { foreach($grandChild->childIds as $greatGrandChildId) {
$greatGrandChildPerms = $authInfo->getPerms('forum', $greatGrandChildId); $greatGrandChildPerms = $msz->authInfo->getPerms('forum', $greatGrandChildId);
if($greatGrandChildPerms->check(Perm::F_CATEGORY_LIST)) if($greatGrandChildPerms->check(Perm::F_CATEGORY_LIST))
$catIds[] = $greatGrandChildId; $catIds[] = $greatGrandChildId;
} }
$grandChildUnread = $forumCategories->checkCategoryUnread($catIds, $currentUser); $grandChildUnread = $msz->forumCtx->categories->checkCategoryUnread($catIds, $currentUser);
if($grandChildUnread) if($grandChildUnread)
$childUnread = true; $childUnread = true;
} }
@ -100,16 +94,16 @@ foreach($categories as $categoryId => $category) {
} }
} }
if($child->info->mayHaveChildren() || $child->info->mayHaveTopics()) { if($child->info->mayHaveChildren || $child->info->mayHaveTopics) {
$catIds = [$child->info->getId()]; $catIds = [$child->info->id];
foreach($child->childIds as $grandChildId) { foreach($child->childIds as $grandChildId) {
$grandChildPerms = $authInfo->getPerms('forum', $grandChildId); $grandChildPerms = $msz->authInfo->getPerms('forum', $grandChildId);
if($grandChildPerms->check(Perm::F_CATEGORY_LIST)) if($grandChildPerms->check(Perm::F_CATEGORY_LIST))
$catIds[] = $grandChildId; $catIds[] = $grandChildId;
} }
try { try {
$lastPostInfo = $forumPosts->getPost(categoryInfos: $catIds, getLast: true, deleted: false); $lastPostInfo = $msz->forumCtx->posts->getPost(categoryInfos: $catIds, getLast: true, deleted: false);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
$lastPostInfo = null; $lastPostInfo = null;
} }
@ -117,18 +111,18 @@ foreach($categories as $categoryId => $category) {
if($lastPostInfo !== null) { if($lastPostInfo !== null) {
$child->lastPost = new stdClass; $child->lastPost = new stdClass;
$child->lastPost->info = $lastPostInfo; $child->lastPost->info = $lastPostInfo;
$child->lastPost->topicInfo = $forumTopics->getTopic(postInfo: $lastPostInfo); $child->lastPost->topicInfo = $msz->forumCtx->topics->getTopic(postInfo: $lastPostInfo);
if($lastPostInfo->hasUserId()) { if($lastPostInfo->userId !== null) {
$child->lastPost->user = $usersCtx->getUserInfo($lastPostInfo->getUserId()); $child->lastPost->user = $msz->usersCtx->getUserInfo($lastPostInfo->userId);
$child->lastPost->colour = $usersCtx->getUserColour($child->lastPost->user); $child->lastPost->colour = $msz->usersCtx->getUserColour($child->lastPost->user);
} }
} }
} }
} }
if($child->info->mayHaveTopics() && !$childUnread) { if($child->info->mayHaveTopics && !$childUnread) {
$childUnread = $forumCategories->checkCategoryUnread($child->info, $currentUser); $childUnread = $msz->forumCtx->categories->checkCategoryUnread($child->info, $currentUser);
if($childUnread) if($childUnread)
$unread = true; $unread = true;
} }
@ -137,10 +131,10 @@ foreach($categories as $categoryId => $category) {
$child->unread = $childUnread; $child->unread = $childUnread;
} }
if($category->info->mayHaveTopics() && !$unread) if($category->info->mayHaveTopics && !$unread)
$unread = $forumCategories->checkCategoryUnread($category->info, $currentUser); $unread = $msz->forumCtx->categories->checkCategoryUnread($category->info, $currentUser);
if(!$category->info->isListing()) { if(!$category->info->isListing) {
if(!array_key_exists('0', $categories)) { if(!array_key_exists('0', $categories)) {
$categories['0'] = $root = new stdClass; $categories['0'] = $root = new stdClass;
$root->info = null; $root->info = null;
@ -153,16 +147,16 @@ foreach($categories as $categoryId => $category) {
$categories['0']->children[$categoryId] = $category; $categories['0']->children[$categoryId] = $category;
unset($categories[$categoryId]); unset($categories[$categoryId]);
if($category->info->mayHaveChildren() || $category->info->mayHaveTopics()) { if($category->info->mayHaveChildren || $category->info->mayHaveTopics) {
$catIds = [$category->info->getId()]; $catIds = [$category->info->id];
foreach($category->childIds as $childId) { foreach($category->childIds as $childId) {
$childPerms = $authInfo->getPerms('forum', $childId); $childPerms = $msz->authInfo->getPerms('forum', $childId);
if($childPerms->check(Perm::F_CATEGORY_LIST)) if($childPerms->check(Perm::F_CATEGORY_LIST))
$catIds[] = $childId; $catIds[] = $childId;
} }
try { try {
$lastPostInfo = $forumPosts->getPost(categoryInfos: $catIds, getLast: true, deleted: false); $lastPostInfo = $msz->forumCtx->posts->getPost(categoryInfos: $catIds, getLast: true, deleted: false);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
$lastPostInfo = null; $lastPostInfo = null;
} }
@ -170,11 +164,11 @@ foreach($categories as $categoryId => $category) {
if($lastPostInfo !== null) { if($lastPostInfo !== null) {
$category->lastPost = new stdClass; $category->lastPost = new stdClass;
$category->lastPost->info = $lastPostInfo; $category->lastPost->info = $lastPostInfo;
$category->lastPost->topicInfo = $forumTopics->getTopic(postInfo: $lastPostInfo); $category->lastPost->topicInfo = $msz->forumCtx->topics->getTopic(postInfo: $lastPostInfo);
if($lastPostInfo->hasUserId()) { if($lastPostInfo->userId !== null) {
$category->lastPost->user = $usersCtx->getUserInfo($lastPostInfo->getUserId()); $category->lastPost->user = $msz->usersCtx->getUserInfo($lastPostInfo->userId);
$category->lastPost->colour = $usersCtx->getUserColour($category->lastPost->user); $category->lastPost->colour = $msz->usersCtx->getUserColour($category->lastPost->user);
} }
} }
} }

View file

@ -3,11 +3,9 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_FORUM_LEADERBOARD_VIEW)) if(!$msz->authInfo->getPerms('global')->check(Perm::G_FORUM_LEADERBOARD_VIEW))
Template::throwError(403); Template::throwError(403);
$forumCtx = $msz->getForumContext();
$usersCtx = $msz->getUsersContext();
$config = $cfg->getValues([ $config = $cfg->getValues([
['forum_leader.first_year:i', 2018], ['forum_leader.first_year:i', 2018],
['forum_leader.first_month:i', 12], ['forum_leader.first_month:i', 12],
@ -61,12 +59,12 @@ for($i = $currentYear, $j = $currentMonth;;) {
break; break;
} }
$rankings = $forumCtx->getPosts()->generatePostRankings($year, $month, $unrankedForums, $unrankedTopics); $rankings = $msz->forumCtx->posts->generatePostRankings($year, $month, $unrankedForums, $unrankedTopics);
foreach($rankings as $ranking) { foreach($rankings as $ranking) {
$ranking->user = $ranking->colour = null; $ranking->user = $ranking->colour = null;
if($ranking->userId !== '') if($ranking->userId !== '')
$ranking->user = $usersCtx->getUserInfo($ranking->userId); $ranking->user = $msz->usersCtx->getUserInfo($ranking->userId);
} }
$name = 'All Time'; $name = 'All Time';
@ -92,9 +90,9 @@ MD;
foreach($rankings as $ranking) { foreach($rankings as $ranking) {
$totalPostsCount += $ranking->postsCount; $totalPostsCount += $ranking->postsCount;
$markdown .= sprintf("| %s | [%s](%s%s) | %s |\r\n", $ranking->position, $markdown .= sprintf("| %s | [%s](%s%s) | %s |\r\n", $ranking->position,
$ranking->user?->getName() ?? 'Deleted User', $ranking->user?->name ?? 'Deleted User',
$msz->getSiteInfo()->getURL(), $msz->siteInfo->url,
$msz->getUrls()->format('user-profile', ['user' => $ranking->userId]), $msz->urls->format('user-profile', ['user' => $ranking->userId]),
number_format($ranking->postsCount)); number_format($ranking->postsCount));
} }

View file

@ -3,34 +3,28 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
$urls = $msz->getUrls();
$forumCtx = $msz->getForumContext();
$forumPosts = $forumCtx->getPosts();
$usersCtx = $msz->getUsersContext();
$postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0; $postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0;
$postMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : ''; $postMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
$submissionConfirmed = !empty($_GET['confirm']) && is_string($_GET['confirm']) && $_GET['confirm'] === '1'; $submissionConfirmed = !empty($_GET['confirm']) && is_string($_GET['confirm']) && $_GET['confirm'] === '1';
$postRequestVerified = CSRF::validateRequest(); $postRequestVerified = CSRF::validateRequest();
$authInfo = $msz->getAuthInfo(); if(!empty($postMode) && !$msz->authInfo->isLoggedIn)
if(!empty($postMode) && !$authInfo->isLoggedIn())
Template::displayInfo('You must be logged in to manage posts.', 401); Template::displayInfo('You must be logged in to manage posts.', 401);
$currentUser = $authInfo->getUserInfo(); $currentUser = $msz->authInfo->userInfo;
$currentUserId = $currentUser === null ? '0' : $currentUser->getId(); $currentUserId = $currentUser === null ? '0' : $currentUser->getId();
if($postMode !== '' && $usersCtx->hasActiveBan($currentUser)) if($postMode !== '' && $msz->usersCtx->hasActiveBan($currentUser))
Template::displayInfo('You have been banned, check your profile for more information.', 403); Template::displayInfo('You have been banned, check your profile for more information.', 403);
try { try {
$postInfo = $forumPosts->getPost(postId: $postId); $postInfo = $msz->forumCtx->posts->getPost(postId: $postId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
$perms = $authInfo->getPerms('forum', $postInfo->getCategoryId()); $perms = $msz->authInfo->getPerms('forum', $postInfo->categoryId);
if(!$perms->check(Perm::F_CATEGORY_VIEW)) if(!$perms->check(Perm::F_CATEGORY_VIEW))
Template::throwError(403); Template::throwError(403);
@ -40,48 +34,48 @@ $canDeleteAny = $perms->check(Perm::F_POST_DELETE_ANY);
switch($postMode) { switch($postMode) {
case 'delete': case 'delete':
if($canDeleteAny) { if($canDeleteAny) {
if($postInfo->isDeleted()) if($postInfo->deleted)
Template::displayInfo('This post has already been marked as deleted.', 404); Template::displayInfo('This post has already been marked as deleted.', 404);
} else { } else {
if($postInfo->isDeleted()) if($postInfo->deleted)
Template::throwError(404); Template::throwError(404);
if(!$perms->check(Perm::F_POST_DELETE_OWN)) if(!$perms->check(Perm::F_POST_DELETE_OWN))
Template::displayInfo('You are not allowed to delete posts.', 403); Template::displayInfo('You are not allowed to delete posts.', 403);
if($postInfo->getUserId() !== $currentUser->getId()) if($postInfo->userId !== $currentUser->getId())
Template::displayInfo('You can only delete your own posts.', 403); Template::displayInfo('You can only delete your own posts.', 403);
// posts may only be deleted within a week of creation, this should be a config value // posts may only be deleted within a week of creation, this should be a config value
$deleteTimeFrame = 60 * 60 * 24 * 7; $deleteTimeFrame = 60 * 60 * 24 * 7;
if($postInfo->getCreatedTime() < time() - $deleteTimeFrame) if($postInfo->createdTime < time() - $deleteTimeFrame)
Template::displayInfo('This post has existed for too long. Ask a moderator to remove if it absolutely necessary.', 403); Template::displayInfo('This post has existed for too long. Ask a moderator to remove if it absolutely necessary.', 403);
} }
$originalPostInfo = $forumPosts->getPost(topicInfo: $postInfo->getTopicId()); $originalPostInfo = $msz->forumCtx->posts->getPost(topicInfo: $postInfo->topicId);
if($originalPostInfo->getId() === $postInfo->getId()) if($originalPostInfo->id === $postInfo->id)
Template::displayInfo('This is the opening post of the topic it belongs to, it may not be deleted without deleting the entire topic as well.', 403); Template::displayInfo('This is the opening post of the topic it belongs to, it may not be deleted without deleting the entire topic as well.', 403);
if($postRequestVerified && !$submissionConfirmed) { if($postRequestVerified && !$submissionConfirmed) {
Tools::redirect($urls->format('forum-post', ['post' => $postInfo->getId()])); Tools::redirect($msz->urls->format('forum-post', ['post' => $postInfo->id]));
break; break;
} elseif(!$postRequestVerified) { } elseif(!$postRequestVerified) {
Template::render('forum.confirm', [ Template::render('forum.confirm', [
'title' => 'Confirm post deletion', 'title' => 'Confirm post deletion',
'class' => 'far fa-trash-alt', 'class' => 'far fa-trash-alt',
'message' => sprintf('You are about to delete post #%d. Are you sure about that?', $postInfo->getId()), 'message' => sprintf('You are about to delete post #%d. Are you sure about that?', $postInfo->id),
'params' => [ 'params' => [
'p' => $postInfo->getId(), 'p' => $postInfo->id,
'm' => 'delete', 'm' => 'delete',
], ],
]); ]);
break; break;
} }
$forumPosts->deletePost($postInfo); $msz->forumCtx->posts->deletePost($postInfo);
$msz->createAuditLog('FORUM_POST_DELETE', [$postInfo->getId()]); $msz->createAuditLog('FORUM_POST_DELETE', [$postInfo->id]);
Tools::redirect($urls->format('forum-topic', ['topic' => $postInfo->getTopicId()])); Tools::redirect($msz->urls->format('forum-topic', ['topic' => $postInfo->topicId]));
break; break;
case 'nuke': case 'nuke':
@ -89,25 +83,25 @@ switch($postMode) {
Template::throwError(403); Template::throwError(403);
if($postRequestVerified && !$submissionConfirmed) { if($postRequestVerified && !$submissionConfirmed) {
Tools::redirect($urls->format('forum-post', ['post' => $postInfo->getId()])); Tools::redirect($msz->urls->format('forum-post', ['post' => $postInfo->id]));
break; break;
} elseif(!$postRequestVerified) { } elseif(!$postRequestVerified) {
Template::render('forum.confirm', [ Template::render('forum.confirm', [
'title' => 'Confirm post nuke', 'title' => 'Confirm post nuke',
'class' => 'fas fa-radiation', 'class' => 'fas fa-radiation',
'message' => sprintf('You are about to PERMANENTLY DELETE post #%d. Are you sure about that?', $postInfo->getId()), 'message' => sprintf('You are about to PERMANENTLY DELETE post #%d. Are you sure about that?', $postInfo->id),
'params' => [ 'params' => [
'p' => $postInfo->getId(), 'p' => $postInfo->id,
'm' => 'nuke', 'm' => 'nuke',
], ],
]); ]);
break; break;
} }
$forumPosts->nukePost($postInfo->getId()); $msz->forumCtx->posts->nukePost($postInfo->id);
$msz->createAuditLog('FORUM_POST_NUKE', [$postInfo->getId()]); $msz->createAuditLog('FORUM_POST_NUKE', [$postInfo->id]);
Tools::redirect($urls->format('forum-topic', ['topic' => $postInfo->getTopicId()])); Tools::redirect($msz->urls->format('forum-topic', ['topic' => $postInfo->topicId]));
break; break;
case 'restore': case 'restore':
@ -115,28 +109,28 @@ switch($postMode) {
Template::throwError(403); Template::throwError(403);
if($postRequestVerified && !$submissionConfirmed) { if($postRequestVerified && !$submissionConfirmed) {
Tools::redirect($urls->format('forum-post', ['post' => $postInfo->getId()])); Tools::redirect($msz->urls->format('forum-post', ['post' => $postInfo->id]));
break; break;
} elseif(!$postRequestVerified) { } elseif(!$postRequestVerified) {
Template::render('forum.confirm', [ Template::render('forum.confirm', [
'title' => 'Confirm post restore', 'title' => 'Confirm post restore',
'class' => 'fas fa-magic', 'class' => 'fas fa-magic',
'message' => sprintf('You are about to restore post #%d. Are you sure about that?', $postInfo->getId()), 'message' => sprintf('You are about to restore post #%d. Are you sure about that?', $postInfo->id),
'params' => [ 'params' => [
'p' => $postInfo->getId(), 'p' => $postInfo->id,
'm' => 'restore', 'm' => 'restore',
], ],
]); ]);
break; break;
} }
$forumPosts->restorePost($postInfo->getId()); $msz->forumCtx->posts->restorePost($postInfo->id);
$msz->createAuditLog('FORUM_POST_RESTORE', [$postInfo->getId()]); $msz->createAuditLog('FORUM_POST_RESTORE', [$postInfo->id]);
Tools::redirect($urls->format('forum-topic', ['topic' => $postInfo->getTopicId()])); Tools::redirect($msz->urls->format('forum-topic', ['topic' => $postInfo->topicId]));
break; break;
default: // function as an alt for topic.php?p= by default default: // function as an alt for topic.php?p= by default
Tools::redirect($urls->format('forum-post', ['post' => $postInfo->getId()])); Tools::redirect($msz->urls->format('forum-post', ['post' => $postInfo->id]));
break; break;
} }

View file

@ -8,19 +8,12 @@ use Misuzu\Parsers\Parser;
use Index\XDateTime; use Index\XDateTime;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
$authInfo = $msz->getAuthInfo(); if(!$msz->authInfo->isLoggedIn)
if(!$authInfo->isLoggedIn())
Template::throwError(401); Template::throwError(401);
$forumCtx = $msz->getForumContext(); $currentUser = $msz->authInfo->userInfo;
$forumCategories = $forumCtx->getCategories();
$forumTopics = $forumCtx->getTopics();
$forumPosts = $forumCtx->getPosts();
$usersCtx = $msz->getUsersContext();
$currentUser = $authInfo->getUserInfo();
$currentUserId = $currentUser->getId(); $currentUserId = $currentUser->getId();
if($usersCtx->hasActiveBan($currentUser)) if($msz->usersCtx->hasActiveBan($currentUser))
Template::throwError(403); Template::throwError(403);
$forumPostingModes = [ $forumPostingModes = [
@ -65,16 +58,16 @@ if(empty($postId)) {
$hasPostInfo = false; $hasPostInfo = false;
} else { } else {
try { try {
$postInfo = $forumPosts->getPost(postId: $postId); $postInfo = $msz->forumCtx->posts->getPost(postId: $postId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
if($postInfo->isDeleted()) if($postInfo->deleted)
Template::throwError(404); Template::throwError(404);
// should automatic cross-quoting be a thing? if so, check if $topicId is < 1 first <-- what did i mean by this? // should automatic cross-quoting be a thing? if so, check if $topicId is < 1 first <-- what did i mean by this?
$topicId = $postInfo->getTopicId(); $topicId = $postInfo->topicId;
$hasPostInfo = true; $hasPostInfo = true;
} }
@ -82,16 +75,16 @@ if(empty($topicId)) {
$hasTopicInfo = false; $hasTopicInfo = false;
} else { } else {
try { try {
$topicInfo = $forumTopics->getTopic(topicId: $topicId); $topicInfo = $msz->forumCtx->topics->getTopic(topicId: $topicId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
if($topicInfo->isDeleted()) if($topicInfo->deleted)
Template::throwError(404); Template::throwError(404);
$forumId = $topicInfo->getCategoryId(); $forumId = $topicInfo->categoryId;
$originalPostInfo = $forumPosts->getPost(topicInfo: $topicInfo); $originalPostInfo = $msz->forumCtx->posts->getPost(topicInfo: $topicInfo);
$hasTopicInfo = true; $hasTopicInfo = true;
} }
@ -99,7 +92,7 @@ if(empty($forumId)) {
$hasCategoryInfo = false; $hasCategoryInfo = false;
} else { } else {
try { try {
$categoryInfo = $forumCategories->getCategory(categoryId: $forumId); $categoryInfo = $msz->forumCtx->categories->getCategory(categoryId: $forumId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
@ -107,16 +100,16 @@ if(empty($forumId)) {
$hasCategoryInfo = true; $hasCategoryInfo = true;
} }
$perms = $authInfo->getPerms('forum', $categoryInfo); $perms = $msz->authInfo->getPerms('forum', $categoryInfo);
if($categoryInfo->isArchived() if($categoryInfo->archived
|| (isset($topicInfo) && $topicInfo->isLocked() && !$perms->check(Perm::F_TOPIC_LOCK)) || (isset($topicInfo) && $topicInfo->locked && !$perms->check(Perm::F_TOPIC_LOCK))
|| !$perms->check(Perm::F_CATEGORY_VIEW) || !$perms->check(Perm::F_CATEGORY_VIEW)
|| !$perms->check(Perm::F_POST_CREATE) || !$perms->check(Perm::F_POST_CREATE)
|| (!isset($topicInfo) && !$perms->check(Perm::F_TOPIC_CREATE))) || (!isset($topicInfo) && !$perms->check(Perm::F_TOPIC_CREATE)))
Template::throwError(403); Template::throwError(403);
if(!$categoryInfo->mayHaveTopics()) if(!$categoryInfo->mayHaveTopics)
Template::throwError(400); Template::throwError(400);
$topicTypes = []; $topicTypes = [];
@ -133,7 +126,7 @@ if($mode === 'create' || $mode === 'edit') {
} }
// edit mode stuff // edit mode stuff
if($mode === 'edit' && !$perms->check($postInfo->getUserId() === $currentUserId ? Perm::F_POST_EDIT_OWN : Perm::F_POST_EDIT_ANY)) if($mode === 'edit' && !$perms->check($postInfo->userId === $currentUserId ? Perm::F_POST_EDIT_OWN : Perm::F_POST_EDIT_ANY))
Template::throwError(403); Template::throwError(403);
$notices = []; $notices = [];
@ -148,13 +141,13 @@ if(!empty($_POST)) {
if(!CSRF::validateRequest()) { if(!CSRF::validateRequest()) {
$notices[] = 'Could not verify request.'; $notices[] = 'Could not verify request.';
} else { } else {
$isEditingTopic = empty($topicInfo) || ($mode === 'edit' && $originalPostInfo->getId() == $postInfo->getId()); $isEditingTopic = empty($topicInfo) || ($mode === 'edit' && $originalPostInfo->id == $postInfo->id);
if($mode === 'create') { if($mode === 'create') {
$postTimeout = $cfg->getInteger('forum.posting.timeout', 5); $postTimeout = $cfg->getInteger('forum.posting.timeout', 5);
if($postTimeout > 0) { if($postTimeout > 0) {
$postTimeoutThreshold = new CarbonImmutable(sprintf('-%d seconds', $postTimeout)); $postTimeoutThreshold = new CarbonImmutable(sprintf('-%d seconds', $postTimeout));
$lastPostCreatedAt = $forumPosts->getUserLastPostCreatedAt($currentUser); $lastPostCreatedAt = $msz->forumCtx->posts->getUserLastPostCreatedAt($currentUser);
if(XDateTime::compare($lastPostCreatedAt, $postTimeoutThreshold) > 0) { if(XDateTime::compare($lastPostCreatedAt, $postTimeoutThreshold) > 0) {
$waitSeconds = $postTimeout + ((int)$lastPostCreatedAt->format('U') - time()); $waitSeconds = $postTimeout + ((int)$lastPostCreatedAt->format('U') - time());
@ -166,9 +159,9 @@ if(!empty($_POST)) {
} }
if($isEditingTopic) { if($isEditingTopic) {
$originalTopicTitle = $topicInfo?->getTitle() ?? null; $originalTopicTitle = $topicInfo?->title ?? null;
$topicTitleChanged = $topicTitle !== $originalTopicTitle; $topicTitleChanged = $topicTitle !== $originalTopicTitle;
$originalTopicType = $topicInfo?->getTypeString() ?? 'discussion'; $originalTopicType = $topicInfo?->typeString ?? 'discussion';
$topicTypeChanged = $topicType !== null && $topicType !== $originalTopicType; $topicTypeChanged = $topicType !== null && $topicType !== $originalTopicType;
$topicTitleLengths = $cfg->getValues([ $topicTitleLengths = $cfg->getValues([
@ -207,19 +200,19 @@ if(!empty($_POST)) {
switch($mode) { switch($mode) {
case 'create': case 'create':
if(empty($topicInfo)) { if(empty($topicInfo)) {
$topicInfo = $forumTopics->createTopic( $topicInfo = $msz->forumCtx->topics->createTopic(
$categoryInfo, $categoryInfo,
$currentUser, $currentUser,
$topicTitle, $topicTitle,
$topicType $topicType
); );
$topicId = $topicInfo->getId(); $topicId = $topicInfo->id;
$forumCategories->incrementCategoryTopics($categoryInfo); $msz->forumCtx->categories->incrementCategoryTopics($categoryInfo);
} else } else
$forumTopics->bumpTopic($topicInfo); $msz->forumCtx->topics->bumpTopic($topicInfo);
$postInfo = $forumPosts->createPost( $postInfo = $msz->forumCtx->posts->createPost(
$topicId, $topicId,
$currentUser, $currentUser,
$_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_ADDR'],
@ -229,16 +222,16 @@ if(!empty($_POST)) {
$categoryInfo $categoryInfo
); );
$postId = $postInfo->getId(); $postId = $postInfo->id;
$forumCategories->incrementCategoryPosts($categoryInfo); $msz->forumCtx->categories->incrementCategoryPosts($categoryInfo);
break; break;
case 'edit': case 'edit':
$markUpdated = $postInfo->getUserId() === $currentUserId $markUpdated = $postInfo->userId === $currentUserId
&& $postInfo->shouldMarkAsEdited() && $postInfo->shouldMarkAsEdited
&& $postText !== $postInfo->getBody(); && $postText !== $postInfo->body;
$forumPosts->updatePost( $msz->forumCtx->posts->updatePost(
$postId, $postId,
remoteAddr: $_SERVER['REMOTE_ADDR'], remoteAddr: $_SERVER['REMOTE_ADDR'],
body: $postText, body: $postText,
@ -248,7 +241,7 @@ if(!empty($_POST)) {
); );
if($isEditingTopic && ($topicTitleChanged || $topicTypeChanged)) if($isEditingTopic && ($topicTitleChanged || $topicTypeChanged))
$forumTopics->updateTopic( $msz->forumCtx->topics->updateTopic(
$topicId, $topicId,
title: $topicTitle, title: $topicTitle,
type: $topicType type: $topicType
@ -258,7 +251,7 @@ if(!empty($_POST)) {
if(empty($notices)) { if(empty($notices)) {
// does this ternary ever return forum-topic? // does this ternary ever return forum-topic?
$redirect = $msz->getUrls()->format(empty($topicInfo) ? 'forum-topic' : 'forum-post', [ $redirect = $msz->urls->format(empty($topicInfo) ? 'forum-topic' : 'forum-post', [
'topic' => $topicId ?? 0, 'topic' => $topicId ?? 0,
'post' => $postId ?? 0, 'post' => $postId ?? 0,
]); ]);
@ -276,32 +269,32 @@ if($mode === 'edit') { // $post is pretty much sure to be populated at this poin
$post = new stdClass; $post = new stdClass;
$post->info = $postInfo; $post->info = $postInfo;
if($postInfo->hasUserId()) { if($postInfo->userId !== null) {
$post->user = $usersCtx->getUserInfo($postInfo->getUserId()); $post->user = $msz->usersCtx->getUserInfo($postInfo->userId);
$post->colour = $usersCtx->getUserColour($post->user); $post->colour = $msz->usersCtx->getUserColour($post->user);
$post->postsCount = $forumCtx->countTotalUserPosts($post->user); $post->postsCount = $msz->forumCtx->countTotalUserPosts($post->user);
} }
$post->isOriginalPost = $originalPostInfo->getId() == $postInfo->getId(); $post->isOriginalPost = $originalPostInfo->id == $postInfo->id;
$post->isOriginalPoster = $originalPostInfo->hasUserId() && $postInfo->hasUserId() $post->isOriginalPoster = $originalPostInfo->userId !== null && $postInfo->userId !== nul
&& $originalPostInfo->getUserId() === $postInfo->getUserId(); && $originalPostInfo->userId === $postInfo->userId;
Template::set('posting_post', $post); Template::set('posting_post', $post);
} }
try { try {
$lastPostInfo = $forumPosts->getPost(userInfo: $currentUser, getLast: true, deleted: false); $lastPostInfo = $msz->forumCtx->posts->getPost(userInfo: $currentUser, getLast: true, deleted: false);
$selectedParser = $lastPostInfo->getParser(); $selectedParser = $lastPostInfo->parser;
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
$selectedParser = Parser::BBCODE; $selectedParser = Parser::BBCODE;
} }
Template::render('forum.posting', [ Template::render('forum.posting', [
'posting_breadcrumbs' => iterator_to_array($forumCategories->getCategoryAncestry($categoryInfo)), 'posting_breadcrumbs' => iterator_to_array($msz->forumCtx->categories->getCategoryAncestry($categoryInfo)),
'global_accent_colour' => $forumCategories->getCategoryColour($categoryInfo), 'global_accent_colour' => $msz->forumCtx->categories->getCategoryColour($categoryInfo),
'posting_user' => $currentUser, 'posting_user' => $currentUser,
'posting_user_colour' => $usersCtx->getUserColour($currentUser), 'posting_user_colour' => $msz->usersCtx->getUserColour($currentUser),
'posting_user_posts_count' => $forumCtx->countTotalUserPosts($currentUser), 'posting_user_posts_count' => $msz->forumCtx->countTotalUserPosts($currentUser),
'posting_user_preferred_parser' => $selectedParser, 'posting_user_preferred_parser' => $selectedParser,
'posting_forum' => $categoryInfo, 'posting_forum' => $categoryInfo,
'posting_notices' => $notices, 'posting_notices' => $notices,

View file

@ -4,40 +4,31 @@ namespace Misuzu;
use stdClass; use stdClass;
use RuntimeException; use RuntimeException;
$urls = $msz->getUrls();
$forumCtx = $msz->getForumContext();
$forumCategories = $forumCtx->getCategories();
$forumTopics = $forumCtx->getTopics();
$forumTopicRedirects = $forumCtx->getTopicRedirects();
$forumPosts = $forumCtx->getPosts();
$usersCtx = $msz->getUsersContext();
$postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0; $postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0;
$topicId = !empty($_GET['t']) && is_string($_GET['t']) ? (int)$_GET['t'] : 0; $topicId = !empty($_GET['t']) && is_string($_GET['t']) ? (int)$_GET['t'] : 0;
$categoryId = null; $categoryId = null;
$moderationMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : ''; $moderationMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
$submissionConfirmed = !empty($_GET['confirm']) && is_string($_GET['confirm']) && $_GET['confirm'] === '1'; $submissionConfirmed = !empty($_GET['confirm']) && is_string($_GET['confirm']) && $_GET['confirm'] === '1';
$authInfo = $msz->getAuthInfo(); $currentUser = $msz->authInfo->userInfo;
$currentUser = $authInfo->getUserInfo();
$currentUserId = $currentUser === null ? '0' : $currentUser->getId(); $currentUserId = $currentUser === null ? '0' : $currentUser->getId();
if($topicId < 1 && $postId > 0) { if($topicId < 1 && $postId > 0) {
try { try {
$postInfo = $forumPosts->getPost(postId: $postId); $postInfo = $msz->forumCtx->posts->getPost(postId: $postId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
$categoryId = $postInfo->getCategoryId(); $categoryId = (int)$postInfo->categoryId;
$perms = $authInfo->getPerms('forum', $postInfo->getCategoryId()); $perms = $msz->authInfo->getPerms('forum', $postInfo->categoryId);
$canDeleteAny = $perms->check(Perm::F_POST_DELETE_ANY); $canDeleteAny = $perms->check(Perm::F_POST_DELETE_ANY);
if($postInfo->isDeleted() && !$canDeleteAny) if($postInfo->deleted && !$canDeleteAny)
Template::throwError(404); Template::throwError(404);
$topicId = $postInfo->getTopicId(); $topicId = $postInfo->topicId;
$preceedingPostCount = $forumPosts->countPosts( $preceedingPostCount = $msz->forumCtx->posts->countPosts(
topicInfo: $topicId, topicInfo: $topicId,
upToPostInfo: $postInfo, upToPostInfo: $postInfo,
deleted: $canDeleteAny ? null : false deleted: $canDeleteAny ? null : false
@ -46,32 +37,32 @@ if($topicId < 1 && $postId > 0) {
try { try {
$topicIsNuked = $topicIsDeleted = $canDeleteAny = false; $topicIsNuked = $topicIsDeleted = $canDeleteAny = false;
$topicInfo = $forumTopics->getTopic(topicId: $topicId); $topicInfo = $msz->forumCtx->topics->getTopic(topicId: $topicId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
$topicIsNuked = true; $topicIsNuked = true;
} }
if(!$topicIsNuked) { if(!$topicIsNuked) {
$topicIsDeleted = $topicInfo->isDeleted(); $topicIsDeleted = $topicInfo->deleted;
if($categoryId !== (int)$topicInfo->getCategoryId()) { if($categoryId !== (int)$topicInfo->categoryId) {
$categoryId = (int)$topicInfo->getCategoryId(); $categoryId = (int)$topicInfo->categoryId;
$perms = $authInfo->getPerms('forum', $topicInfo->getCategoryId()); $perms = $msz->authInfo->getPerms('forum', $topicInfo->categoryId);
} }
if($usersCtx->hasActiveBan($currentUser)) if($msz->usersCtx->hasActiveBan($currentUser))
$perms = $perms->apply(fn($calc) => $calc & (Perm::F_CATEGORY_LIST | Perm::F_CATEGORY_VIEW)); $perms = $perms->apply(fn($calc) => $calc & (Perm::F_CATEGORY_LIST | Perm::F_CATEGORY_VIEW));
$canDeleteAny = $perms->check(Perm::F_POST_DELETE_ANY); $canDeleteAny = $perms->check(Perm::F_POST_DELETE_ANY);
} }
if($topicIsNuked || $topicIsDeleted) { if($topicIsNuked || $topicIsDeleted) {
if($forumTopicRedirects->hasTopicRedirect($topicId)) { if($msz->forumCtx->topicRedirects->hasTopicRedirect($topicId)) {
$topicRedirectInfo = $forumTopicRedirects->getTopicRedirect($topicId); $topicRedirectInfo = $msz->forumCtx->topicRedirects->getTopicRedirect($topicId);
Template::set('topic_redir_info', $topicRedirectInfo); Template::set('topic_redir_info', $topicRedirectInfo);
if($topicIsNuked || !$canDeleteAny) { if($topicIsNuked || !$canDeleteAny) {
header('Location: ' . $topicRedirectInfo->getLinkTarget()); header('Location: ' . $topicRedirectInfo->linkTarget);
return; return;
} }
} }
@ -87,10 +78,10 @@ if(!$perms->check(Perm::F_CATEGORY_VIEW))
// this should be in the config // this should be in the config
$deletePostThreshold = 1; $deletePostThreshold = 1;
$categoryInfo = $forumCategories->getCategory(topicInfo: $topicInfo); $categoryInfo = $msz->forumCtx->categories->getCategory(topicInfo: $topicInfo);
$topicIsLocked = $topicInfo->isLocked(); $topicIsLocked = $topicInfo->locked;
$topicIsArchived = $categoryInfo->isArchived(); $topicIsArchived = $categoryInfo->archived;
$topicPostsTotal = $topicInfo->getTotalPostsCount(); $topicPostsTotal = $topicInfo->totalPostsCount;
$topicIsFrozen = $topicIsArchived || $topicIsDeleted; $topicIsFrozen = $topicIsArchived || $topicIsDeleted;
$canDeleteOwn = !$topicIsFrozen && !$topicIsLocked && $perms->check(Perm::F_POST_DELETE_OWN); $canDeleteOwn = !$topicIsFrozen && !$topicIsLocked && $perms->check(Perm::F_POST_DELETE_OWN);
$canBumpTopic = !$topicIsFrozen && $perms->check(Perm::F_TOPIC_BUMP); $canBumpTopic = !$topicIsFrozen && $perms->check(Perm::F_TOPIC_BUMP);
@ -101,7 +92,7 @@ $canDelete = !$topicIsDeleted && (
$topicPostsTotal > 0 $topicPostsTotal > 0
&& $topicPostsTotal <= $deletePostThreshold && $topicPostsTotal <= $deletePostThreshold
&& $canDeleteOwn && $canDeleteOwn
&& $topicInfo->getUserId() === (string)$currentUserId && $topicInfo->userId === (string)$currentUserId
) )
); );
@ -114,35 +105,34 @@ if(in_array($moderationMode, $validModerationModes, true)) {
if(!CSRF::validateRequest()) if(!CSRF::validateRequest())
Template::displayInfo("Couldn't verify this request, please refresh the page and try again.", 403); Template::displayInfo("Couldn't verify this request, please refresh the page and try again.", 403);
$authInfo = $authInfo; if(!$msz->authInfo->isLoggedIn)
if(!$authInfo->isLoggedIn())
Template::displayInfo('You must be logged in to manage posts.', 401); Template::displayInfo('You must be logged in to manage posts.', 401);
if($usersCtx->hasActiveBan($currentUser)) if($msz->usersCtx->hasActiveBan($currentUser))
Template::displayInfo('You have been banned, check your profile for more information.', 403); Template::displayInfo('You have been banned, check your profile for more information.', 403);
switch($moderationMode) { switch($moderationMode) {
case 'delete': case 'delete':
if($canDeleteAny) { if($canDeleteAny) {
if($topicInfo->isDeleted()) if($topicInfo->deleted)
Template::displayInfo('This topic has already been marked as deleted.', 404); Template::displayInfo('This topic has already been marked as deleted.', 404);
} else { } else {
if($topicInfo->isDeleted()) if($topicInfo->deleted)
Template::throwError(404); Template::throwError(404);
if(!$canDeleteOwn) if(!$canDeleteOwn)
Template::displayInfo("You aren't allowed to delete topics.", 403); Template::displayInfo("You aren't allowed to delete topics.", 403);
if($topicInfo->getUserId() !== $currentUser->getId()) if($topicInfo->userId !== $currentUser->getId())
Template::displayInfo('You can only delete your own topics.', 403); Template::displayInfo('You can only delete your own topics.', 403);
// topics may only be deleted within a day of creation, this should be a config value // topics may only be deleted within a day of creation, this should be a config value
$deleteTimeFrame = 60 * 60 * 24; $deleteTimeFrame = 60 * 60 * 24;
if($topicInfo->getCreatedTime() < time() - $deleteTimeFrame) if($topicInfo->createdTime < time() - $deleteTimeFrame)
Template::displayInfo('This topic has existed for too long. Ask a moderator to remove if it absolutely necessary.', 403); Template::displayInfo('This topic has existed for too long. Ask a moderator to remove if it absolutely necessary.', 403);
// deleted posts are intentionally included // deleted posts are intentionally included
$topicPostCount = $forumPosts->countPosts(topicInfo: $topicInfo); $topicPostCount = $msz->forumCtx->posts->countPosts(topicInfo: $topicInfo);
if($topicPostCount > $deletePostThreshold) if($topicPostCount > $deletePostThreshold)
Template::displayInfo('This topic already has replies, you may no longer delete it. Ask a moderator to remove if it absolutely necessary.', 403); Template::displayInfo('This topic already has replies, you may no longer delete it. Ask a moderator to remove if it absolutely necessary.', 403);
} }
@ -151,26 +141,26 @@ if(in_array($moderationMode, $validModerationModes, true)) {
Template::render('forum.confirm', [ Template::render('forum.confirm', [
'title' => 'Confirm topic deletion', 'title' => 'Confirm topic deletion',
'class' => 'far fa-trash-alt', 'class' => 'far fa-trash-alt',
'message' => sprintf('You are about to delete topic #%d. Are you sure about that?', $topicInfo->getId()), 'message' => sprintf('You are about to delete topic #%d. Are you sure about that?', $topicInfo->id),
'params' => [ 'params' => [
't' => $topicInfo->getId(), 't' => $topicInfo->id,
'm' => 'delete', 'm' => 'delete',
], ],
]); ]);
break; break;
} elseif(!$submissionConfirmed) { } elseif(!$submissionConfirmed) {
Tools::redirect($urls->format( Tools::redirect($msz->urls->format(
'forum-topic', 'forum-topic',
['topic' => $topicInfo->getId()] ['topic' => $topicInfo->id]
)); ));
break; break;
} }
$forumTopics->deleteTopic($topicInfo->getId()); $msz->forumCtx->topics->deleteTopic($topicInfo->id);
$msz->createAuditLog('FORUM_TOPIC_DELETE', [$topicInfo->getId()]); $msz->createAuditLog('FORUM_TOPIC_DELETE', [$topicInfo->id]);
Tools::redirect($urls->format('forum-category', [ Tools::redirect($msz->urls->format('forum-category', [
'forum' => $categoryInfo->getId(), 'forum' => $categoryInfo->id,
])); ]));
break; break;
@ -182,25 +172,25 @@ if(in_array($moderationMode, $validModerationModes, true)) {
Template::render('forum.confirm', [ Template::render('forum.confirm', [
'title' => 'Confirm topic restore', 'title' => 'Confirm topic restore',
'class' => 'fas fa-magic', 'class' => 'fas fa-magic',
'message' => sprintf('You are about to restore topic #%d. Are you sure about that?', $topicInfo->getId()), 'message' => sprintf('You are about to restore topic #%d. Are you sure about that?', $topicInfo->id),
'params' => [ 'params' => [
't' => $topicInfo->getId(), 't' => $topicInfo->id,
'm' => 'restore', 'm' => 'restore',
], ],
]); ]);
break; break;
} elseif(!$submissionConfirmed) { } elseif(!$submissionConfirmed) {
Tools::redirect($urls->format('forum-topic', [ Tools::redirect($msz->urls->format('forum-topic', [
'topic' => $topicInfo->getId(), 'topic' => $topicInfo->id,
])); ]));
break; break;
} }
$forumTopics->restoreTopic($topicInfo->getId()); $msz->forumCtx->topics->restoreTopic($topicInfo->id);
$msz->createAuditLog('FORUM_TOPIC_RESTORE', [$topicInfo->getId()]); $msz->createAuditLog('FORUM_TOPIC_RESTORE', [$topicInfo->id]);
Tools::redirect($urls->format('forum-category', [ Tools::redirect($msz->urls->format('forum-category', [
'forum' => $categoryInfo->getId(), 'forum' => $categoryInfo->id,
])); ]));
break; break;
@ -212,67 +202,67 @@ if(in_array($moderationMode, $validModerationModes, true)) {
Template::render('forum.confirm', [ Template::render('forum.confirm', [
'title' => 'Confirm topic nuke', 'title' => 'Confirm topic nuke',
'class' => 'fas fa-radiation', 'class' => 'fas fa-radiation',
'message' => sprintf('You are about to PERMANENTLY DELETE topic #%d. Are you sure about that?', $topicInfo->getId()), 'message' => sprintf('You are about to PERMANENTLY DELETE topic #%d. Are you sure about that?', $topicInfo->id),
'params' => [ 'params' => [
't' => $topicInfo->getId(), 't' => $topicInfo->id,
'm' => 'nuke', 'm' => 'nuke',
], ],
]); ]);
break; break;
} elseif(!$submissionConfirmed) { } elseif(!$submissionConfirmed) {
Tools::redirect($urls->format('forum-topic', [ Tools::redirect($msz->urls->format('forum-topic', [
'topic' => $topicInfo->getId(), 'topic' => $topicInfo->id,
])); ]));
break; break;
} }
$forumTopics->nukeTopic($topicInfo->getId()); $msz->forumCtx->topics->nukeTopic($topicInfo->id);
$msz->createAuditLog('FORUM_TOPIC_NUKE', [$topicInfo->getId()]); $msz->createAuditLog('FORUM_TOPIC_NUKE', [$topicInfo->id]);
Tools::redirect($urls->format('forum-category', [ Tools::redirect($msz->urls->format('forum-category', [
'forum' => $categoryInfo->getId(), 'forum' => $categoryInfo->id,
])); ]));
break; break;
case 'bump': case 'bump':
if($canBumpTopic) { if($canBumpTopic) {
$forumTopics->bumpTopic($topicInfo->getId()); $msz->forumCtx->topics->bumpTopic($topicInfo->id);
$msz->createAuditLog('FORUM_TOPIC_BUMP', [$topicInfo->getId()]); $msz->createAuditLog('FORUM_TOPIC_BUMP', [$topicInfo->id]);
} }
Tools::redirect($urls->format('forum-topic', [ Tools::redirect($msz->urls->format('forum-topic', [
'topic' => $topicInfo->getId(), 'topic' => $topicInfo->id,
])); ]));
break; break;
case 'lock': case 'lock':
if($canLockTopic && !$topicIsLocked) { if($canLockTopic && !$topicIsLocked) {
$forumTopics->lockTopic($topicInfo->getId()); $msz->forumCtx->topics->lockTopic($topicInfo->id);
$msz->createAuditLog('FORUM_TOPIC_LOCK', [$topicInfo->getId()]); $msz->createAuditLog('FORUM_TOPIC_LOCK', [$topicInfo->id]);
} }
Tools::redirect($urls->format('forum-topic', [ Tools::redirect($msz->urls->format('forum-topic', [
'topic' => $topicInfo->getId(), 'topic' => $topicInfo->id,
])); ]));
break; break;
case 'unlock': case 'unlock':
if($canLockTopic && $topicIsLocked) { if($canLockTopic && $topicIsLocked) {
$forumTopics->unlockTopic($topicInfo->getId()); $msz->forumCtx->topics->unlockTopic($topicInfo->id);
$msz->createAuditLog('FORUM_TOPIC_UNLOCK', [$topicInfo->getId()]); $msz->createAuditLog('FORUM_TOPIC_UNLOCK', [$topicInfo->id]);
} }
Tools::redirect($urls->format('forum-topic', [ Tools::redirect($msz->urls->format('forum-topic', [
'topic' => $topicInfo->getId(), 'topic' => $topicInfo->id,
])); ]));
break; break;
} }
return; return;
} }
$topicPosts = $topicInfo->getPostsCount(); $topicPosts = $topicInfo->postsCount;
if($canDeleteAny) if($canDeleteAny)
$topicPosts += $topicInfo->getDeletedPostsCount(); $topicPosts += $topicInfo->deletedPostsCount;
$topicPagination = new Pagination($topicPosts, 10, 'page'); $topicPagination = new Pagination($topicPosts, 10, 'page');
@ -282,7 +272,7 @@ if(isset($preceedingPostCount))
if(!$topicPagination->hasValidOffset()) if(!$topicPagination->hasValidOffset())
Template::throwError(404); Template::throwError(404);
$postInfos = $forumPosts->getPosts( $postInfos = $msz->forumCtx->posts->getPosts(
topicInfo: $topicInfo, topicInfo: $topicInfo,
deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false, deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false,
pagination: $topicPagination, pagination: $topicPagination,
@ -292,7 +282,7 @@ if(empty($postInfos))
Template::throwError(404); Template::throwError(404);
try { try {
$originalPostInfo = $forumPosts->getPost(topicInfo: $topicInfo); $originalPostInfo = $msz->forumCtx->posts->getPost(topicInfo: $topicInfo);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
@ -303,23 +293,23 @@ foreach($postInfos as $postInfo) {
$posts[] = $post = new stdClass; $posts[] = $post = new stdClass;
$post->info = $postInfo; $post->info = $postInfo;
if($postInfo->hasUserId()) { if($postInfo->userId !== null) {
$post->user = $usersCtx->getUserInfo($postInfo->getUserId()); $post->user = $msz->usersCtx->getUserInfo($postInfo->userId);
$post->colour = $usersCtx->getUserColour($post->user); $post->colour = $msz->usersCtx->getUserColour($post->user);
$post->postsCount = $forumCtx->countTotalUserPosts($post->user); $post->postsCount = $msz->forumCtx->countTotalUserPosts($post->user);
} }
$post->isOriginalPost = $originalPostInfo->getId() == $postInfo->getId(); $post->isOriginalPost = $originalPostInfo->id == $postInfo->id;
$post->isOriginalPoster = $originalPostInfo->hasUserId() && $postInfo->hasUserId() $post->isOriginalPoster = $originalPostInfo->userId !== null && $postInfo->userId !== null
&& $originalPostInfo->getUserId() === $postInfo->getUserId(); && $originalPostInfo->userId === $postInfo->userId;
} }
$canReply = !$topicIsArchived && !$topicIsLocked && !$topicIsDeleted && $perms->check(Perm::F_POST_CREATE); $canReply = !$topicIsArchived && !$topicIsLocked && !$topicIsDeleted && $perms->check(Perm::F_POST_CREATE);
if(!$forumTopics->checkUserHasReadTopic($currentUser, $topicInfo)) if(!$msz->forumCtx->topics->checkUserHasReadTopic($currentUser, $topicInfo))
$forumTopics->incrementTopicViews($topicInfo); $msz->forumCtx->topics->incrementTopicViews($topicInfo);
$forumTopics->updateUserReadTopic($currentUser, $topicInfo); $msz->forumCtx->topics->updateUserReadTopic($currentUser, $topicInfo);
$perms = $perms->checkMany([ $perms = $perms->checkMany([
'can_create_post' => Perm::F_POST_CREATE, 'can_create_post' => Perm::F_POST_CREATE,
@ -330,8 +320,8 @@ $perms = $perms->checkMany([
]); ]);
Template::render('forum.topic', [ Template::render('forum.topic', [
'topic_breadcrumbs' => iterator_to_array($forumCategories->getCategoryAncestry($topicInfo)), 'topic_breadcrumbs' => iterator_to_array($msz->forumCtx->categories->getCategoryAncestry($topicInfo)),
'global_accent_colour' => $forumCategories->getCategoryColour($topicInfo), 'global_accent_colour' => $msz->forumCtx->categories->getCategoryColour($topicInfo),
'topic_info' => $topicInfo, 'topic_info' => $topicInfo,
'category_info' => $categoryInfo, 'category_info' => $categoryInfo,
'topic_posts' => $posts, 'topic_posts' => $posts,

View file

@ -7,28 +7,25 @@ use Misuzu\Changelog\Changelog;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use Index\{XArray,XDateTime}; use Index\{XArray,XDateTime};
$authInfo = $msz->getAuthInfo(); if(!$msz->authInfo->getPerms('global')->check(Perm::G_CL_CHANGES_MANAGE))
if(!$authInfo->getPerms('global')->check(Perm::G_CL_CHANGES_MANAGE))
Template::throwError(403); Template::throwError(403);
$changeActions = []; $changeActions = [];
foreach(Changelog::ACTIONS as $action) foreach(Changelog::ACTIONS as $action)
$changeActions[$action] = Changelog::actionText($action); $changeActions[$action] = Changelog::actionText($action);
$urls = $msz->getUrls();
$changelog = $msz->getChangelog();
$changeId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT); $changeId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
$changeInfo = null; $changeInfo = null;
$changeTagIds = []; $changeTagIds = [];
$tagInfos = $changelog->getTags(); $tagInfos = $msz->changelog->getTags();
if(empty($changeId)) if(empty($changeId))
$isNew = true; $isNew = true;
else else
try { try {
$isNew = false; $isNew = false;
$changeInfo = $changelog->getChange($changeId); $changeInfo = $msz->changelog->getChange($changeId);
$changeTagIds = XArray::select($changelog->getTags(changeInfo: $changeInfo), fn($tagInfo) => $tagInfo->getId()); $changeTagIds = XArray::select($msz->changelog->getTags(changeInfo: $changeInfo), fn($tagInfo) => $tagInfo->id);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
@ -37,9 +34,9 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
if(!CSRF::validateRequest()) if(!CSRF::validateRequest())
Template::throwError(403); Template::throwError(403);
$changelog->deleteChange($changeInfo); $msz->changelog->deleteChange($changeInfo);
$msz->createAuditLog('CHANGELOG_ENTRY_DELETE', [$changeInfo->getId()]); $msz->createAuditLog('CHANGELOG_ENTRY_DELETE', [$changeInfo->id]);
Tools::redirect($urls->format('manage-changelog-changes')); Tools::redirect($msz->urls->format('manage-changelog-changes'));
return; return;
} }
@ -64,20 +61,20 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
} }
if($isNew) { if($isNew) {
$changeInfo = $changelog->createChange($action, $summary, $body, $userId, $createdAt); $changeInfo = $msz->changelog->createChange($action, $summary, $body, $userId, $createdAt);
} else { } else {
if($action === $changeInfo->getAction()) if($action === $changeInfo->action)
$action = null; $action = null;
if($summary === $changeInfo->getSummary()) if($summary === $changeInfo->summary)
$summary = null; $summary = null;
if($body === $changeInfo->getBody()) if($body === $changeInfo->body)
$body = null; $body = null;
if($createdAt !== null && XDateTime::compare($createdAt, $changeInfo->getCreatedAt()) === 0) if($createdAt !== null && XDateTime::compare($createdAt, $changeInfo->createdAt) === 0)
$createdAt = null; $createdAt = null;
$updateUserInfo = $userId !== $changeInfo->getUserId(); $updateUserInfo = $userId !== $changeInfo->userId;
if($action !== null || $summary !== null || $body !== null || $createdAt !== null || $updateUserInfo) if($action !== null || $summary !== null || $body !== null || $createdAt !== null || $updateUserInfo)
$changelog->updateChange($changeInfo, $action, $summary, $body, $updateUserInfo, $userId, $createdAt); $msz->changelog->updateChange($changeInfo, $action, $summary, $body, $updateUserInfo, $userId, $createdAt);
} }
if(!empty($tags)) { if(!empty($tags)) {
@ -88,24 +85,24 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
foreach($tCurrent as $tag) foreach($tCurrent as $tag)
if(!in_array($tag, $tApply)) { if(!in_array($tag, $tApply)) {
$tRemove[] = $tag; $tRemove[] = $tag;
$changelog->removeTagFromChange($changeInfo, $tag); $msz->changelog->removeTagFromChange($changeInfo, $tag);
} }
$tCurrent = array_diff($tCurrent, $tRemove); $tCurrent = array_diff($tCurrent, $tRemove);
foreach($tApply as $tag) foreach($tApply as $tag)
if(!in_array($tag, $tCurrent)) { if(!in_array($tag, $tCurrent)) {
$changelog->addTagToChange($changeInfo, $tag); $msz->changelog->addTagToChange($changeInfo, $tag);
$tCurrent[] = $tag; $tCurrent[] = $tag;
} }
} }
$msz->createAuditLog( $msz->createAuditLog(
$isNew ? 'CHANGELOG_ENTRY_CREATE' : 'CHANGELOG_ENTRY_EDIT', $isNew ? 'CHANGELOG_ENTRY_CREATE' : 'CHANGELOG_ENTRY_EDIT',
[$changeInfo->getId()] [$changeInfo->id]
); );
Tools::redirect($urls->format('manage-changelog-change', ['change' => $changeInfo->getId()])); Tools::redirect($msz->urls->format('manage-changelog-change', ['change' => $changeInfo->id]));
return; return;
} }
@ -115,5 +112,5 @@ Template::render('manage.changelog.change', [
'change_info_tags' => $changeTagIds, 'change_info_tags' => $changeTagIds,
'change_tags' => $tagInfos, 'change_tags' => $tagInfos,
'change_actions' => $changeActions, 'change_actions' => $changeActions,
'change_author_id' => $authInfo->getUserInfo()->getId(), 'change_author_id' => $msz->authInfo->userId,
]); ]);

View file

@ -3,32 +3,28 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CL_CHANGES_MANAGE)) if(!$msz->authInfo->getPerms('global')->check(Perm::G_CL_CHANGES_MANAGE))
Template::throwError(403); Template::throwError(403);
$changelog = $msz->getChangelog(); $pagination = new Pagination($msz->changelog->countChanges(), 30);
$usersCtx = $msz->getUsersContext(); if(!$pagination->hasValidOffset())
$changelogPagination = new Pagination($changelog->countChanges(), 30);
if(!$changelogPagination->hasValidOffset())
Template::throwError(404); Template::throwError(404);
$changeInfos = $changelog->getChanges(pagination: $changelogPagination); $changeInfos = $msz->changelog->getChanges(pagination: $pagination);
$changes = []; $changes = [];
foreach($changeInfos as $changeInfo) { foreach($changeInfos as $changeInfo) {
$userInfo = $changeInfo->hasUserId() ? $usersCtx->getUserInfo($changeInfo->getUserId()) : null; $userInfo = $changeInfo->userId !== null ? $msz->usersCtx->getUserInfo($changeInfo->userId) : null;
$changes[] = [ $changes[] = [
'change' => $changeInfo, 'change' => $changeInfo,
'tags' => $changelog->getTags(changeInfo: $changeInfo), 'tags' => $msz->changelog->getTags(changeInfo: $changeInfo),
'user' => $userInfo, 'user' => $userInfo,
'user_colour' => $usersCtx->getUserColour($userInfo), 'user_colour' => $msz->usersCtx->getUserColour($userInfo),
]; ];
} }
Template::render('manage.changelog.changes', [ Template::render('manage.changelog.changes', [
'changelog_changes' => $changes, 'changelog_changes' => $changes,
'changelog_pagination' => $changelogPagination, 'changelog_pagination' => $pagination,
]); ]);

View file

@ -3,13 +3,11 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CL_TAGS_MANAGE)) if(!$msz->authInfo->getPerms('global')->check(Perm::G_CL_TAGS_MANAGE))
Template::throwError(403); Template::throwError(403);
$urls = $msz->getUrls();
$changelog = $msz->getChangelog();
$tagId = (string)filter_input(INPUT_GET, 't', FILTER_SANITIZE_NUMBER_INT); $tagId = (string)filter_input(INPUT_GET, 't', FILTER_SANITIZE_NUMBER_INT);
$loadTagInfo = fn() => $changelog->getTag($tagId); $loadTagInfo = fn() => $msz->changelog->getTag($tagId);
if(empty($tagId)) if(empty($tagId))
$isNew = true; $isNew = true;
@ -25,9 +23,9 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
if(!CSRF::validateRequest()) if(!CSRF::validateRequest())
Template::throwError(403); Template::throwError(403);
$changelog->deleteTag($tagInfo); $msz->changelog->deleteTag($tagInfo);
$msz->createAuditLog('CHANGELOG_TAG_DELETE', [$tagInfo->getId()]); $msz->createAuditLog('CHANGELOG_TAG_DELETE', [$tagInfo->id]);
Tools::redirect($urls->format('manage-changelog-tags')); Tools::redirect($msz->urls->format('manage-changelog-tags'));
return; return;
} }
@ -37,26 +35,26 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$archive = !empty($_POST['ct_archive']); $archive = !empty($_POST['ct_archive']);
if($isNew) { if($isNew) {
$tagInfo = $changelog->createTag($name, $description, $archive); $tagInfo = $msz->changelog->createTag($name, $description, $archive);
} else { } else {
if($name === $tagInfo->getName()) if($name === $tagInfo->name)
$name = null; $name = null;
if($description === $tagInfo->getDescription()) if($description === $tagInfo->description)
$description = null; $description = null;
if($archive === $tagInfo->isArchived()) if($archive === $tagInfo->archived)
$archive = null; $archive = null;
if($name !== null || $description !== null || $archive !== null) if($name !== null || $description !== null || $archive !== null)
$changelog->updateTag($tagInfo, $name, $description, $archive); $msz->changelog->updateTag($tagInfo, $name, $description, $archive);
} }
$msz->createAuditLog( $msz->createAuditLog(
$isNew ? 'CHANGELOG_TAG_CREATE' : 'CHANGELOG_TAG_EDIT', $isNew ? 'CHANGELOG_TAG_CREATE' : 'CHANGELOG_TAG_EDIT',
[$tagInfo->getId()] [$tagInfo->id]
); );
if($isNew) { if($isNew) {
Tools::redirect($urls->format('manage-changelog-tag', ['tag' => $tagInfo->getId()])); Tools::redirect($msz->urls->format('manage-changelog-tag', ['tag' => $tagInfo->id]));
return; return;
} else $tagInfo = $loadTagInfo(); } else $tagInfo = $loadTagInfo();
break; break;

View file

@ -1,9 +1,9 @@
<?php <?php
namespace Misuzu; namespace Misuzu;
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CL_TAGS_MANAGE)) if(!$msz->authInfo->getPerms('global')->check(Perm::G_CL_TAGS_MANAGE))
Template::throwError(403); Template::throwError(403);
Template::render('manage.changelog.tags', [ Template::render('manage.changelog.tags', [
'changelog_tags' => $msz->getChangelog()->getTags(), 'changelog_tags' => $msz->changelog->getTags(),
]); ]);

View file

@ -3,11 +3,10 @@ namespace Misuzu;
use Misuzu\Perm; use Misuzu\Perm;
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_FORUM_CATEGORIES_MANAGE)) if(!$msz->authInfo->getPerms('global')->check(Perm::G_FORUM_CATEGORIES_MANAGE))
Template::throwError(403); Template::throwError(403);
$perms = $msz->getPerms(); $permsInfos = $msz->perms->getPermissionInfo(categoryNames: Perm::INFO_FOR_FORUM_CATEGORY);
$permsInfos = $perms->getPermissionInfo(categoryNames: Perm::INFO_FOR_FORUM_CATEGORY);
$permsLists = Perm::createList(Perm::LISTS_FOR_FORUM_CATEGORY); $permsLists = Perm::createList(Perm::LISTS_FOR_FORUM_CATEGORY);
if(filter_has_var(INPUT_POST, 'perms')) if(filter_has_var(INPUT_POST, 'perms'))

View file

@ -1,14 +1,9 @@
<?php <?php
namespace Misuzu; namespace Misuzu;
$authInfo = $msz->getAuthInfo(); if(!$msz->authInfo->getPerms('global')->check(Perm::G_FORUM_TOPIC_REDIRS_MANAGE))
if(!$authInfo->getPerms('global')->check(Perm::G_FORUM_TOPIC_REDIRS_MANAGE))
Template::throwError(403); Template::throwError(403);
$urls = $msz->getUrls();
$forumCtx = $msz->getForumContext();
$forumTopicRedirects = $forumCtx->getTopicRedirects();
if($_SERVER['REQUEST_METHOD'] === 'POST') { if($_SERVER['REQUEST_METHOD'] === 'POST') {
if(!CSRF::validateRequest()) if(!CSRF::validateRequest())
throw new \Exception("Request verification failed."); throw new \Exception("Request verification failed.");
@ -17,8 +12,8 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
$rTopicURL = trim((string)filter_input(INPUT_POST, 'topic_redir_url')); $rTopicURL = trim((string)filter_input(INPUT_POST, 'topic_redir_url'));
$msz->createAuditLog('FORUM_TOPIC_REDIR_CREATE', [$rTopicId]); $msz->createAuditLog('FORUM_TOPIC_REDIR_CREATE', [$rTopicId]);
$forumTopicRedirects->createTopicRedirect($rTopicId, $authInfo->getUserInfo(), $rTopicURL); $msz->forumCtx->topicRedirects->createTopicRedirect($rTopicId, $msz->authInfo->userInfo, $rTopicURL);
Tools::redirect($urls->format('manage-forum-topic-redirs')); Tools::redirect($msz->urls->format('manage-forum-topic-redirs'));
return; return;
} }
@ -28,16 +23,16 @@ if(filter_input(INPUT_GET, 'm') === 'explode') {
$rTopicId = (string)filter_input(INPUT_GET, 't'); $rTopicId = (string)filter_input(INPUT_GET, 't');
$msz->createAuditLog('FORUM_TOPIC_REDIR_REMOVE', [$rTopicId]); $msz->createAuditLog('FORUM_TOPIC_REDIR_REMOVE', [$rTopicId]);
$forumTopicRedirects->deleteTopicRedirect($rTopicId); $msz->forumCtx->topicRedirects->deleteTopicRedirect($rTopicId);
Tools::redirect($urls->format('manage-forum-topic-redirs')); Tools::redirect($msz->urls->format('manage-forum-topic-redirs'));
return; return;
} }
$pagination = new Pagination($forumTopicRedirects->countTopicRedirects(), 20); $pagination = new Pagination($msz->forumCtx->topicRedirects->countTopicRedirects(), 20);
if(!$pagination->hasValidOffset()) if(!$pagination->hasValidOffset())
Template::throwError(404); Template::throwError(404);
$redirs = $forumTopicRedirects->getTopicRedirects(pagination: $pagination); $redirs = $msz->forumCtx->topicRedirects->getTopicRedirects(pagination: $pagination);
Template::render('manage.forum.redirs', [ Template::render('manage.forum.redirs', [
'manage_redirs' => $redirs, 'manage_redirs' => $redirs,

View file

@ -4,10 +4,9 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
use Index\XArray; use Index\XArray;
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_EMOTES_MANAGE)) if(!$msz->authInfo->getPerms('global')->check(Perm::G_EMOTES_MANAGE))
Template::throwError(403); Template::throwError(403);
$emotes = $msz->getEmotes();
$emoteId = (string)filter_input(INPUT_GET, 'e', FILTER_SANITIZE_NUMBER_INT); $emoteId = (string)filter_input(INPUT_GET, 'e', FILTER_SANITIZE_NUMBER_INT);
$emoteInfo = []; $emoteInfo = [];
$emoteStrings = []; $emoteStrings = [];
@ -17,8 +16,8 @@ if(empty($emoteId))
else else
try { try {
$isNew = false; $isNew = false;
$emoteInfo = $emotes->getEmote($emoteId); $emoteInfo = $msz->emotes->getEmote($emoteId);
$emoteStrings = iterator_to_array($emotes->getEmoteStrings($emoteInfo)); $emoteStrings = iterator_to_array($msz->emotes->getEmoteStrings($emoteInfo));
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
@ -30,8 +29,8 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$url = trim((string)filter_input(INPUT_POST, 'em_url')); $url = trim((string)filter_input(INPUT_POST, 'em_url'));
$strings = explode(' ', trim((string)filter_input(INPUT_POST, 'em_strings'))); $strings = explode(' ', trim((string)filter_input(INPUT_POST, 'em_strings')));
if($isNew || $url !== $emoteInfo->getUrl()) { if($isNew || $url !== $emoteInfo->url) {
$checkUrl = $emotes->checkEmoteUrl($url); $checkUrl = $msz->emotes->checkEmoteUrl($url);
if($checkUrl !== '') { if($checkUrl !== '') {
echo match($checkUrl) { echo match($checkUrl) {
'empty' => 'URL may not be empty.', 'empty' => 'URL may not be empty.',
@ -47,36 +46,36 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$order = null; $order = null;
if($isNew) { if($isNew) {
$emoteInfo = $emotes->createEmote($url, $minRank, $order); $emoteInfo = $msz->emotes->createEmote($url, $minRank, $order);
} else { } else {
if($order === $emoteInfo->getOrder()) if($order === $emoteInfo->order)
$order = null; $order = null;
if($minRank === $emoteInfo->getMinRank()) if($minRank === $emoteInfo->minRank)
$minRank = null; $minRank = null;
if($url === $emoteInfo->getUrl()) if($url === $emoteInfo->url)
$url = null; $url = null;
if($order !== null || $minRank !== null || $url !== null) if($order !== null || $minRank !== null || $url !== null)
$emotes->updateEmote($emoteInfo, $order, $minRank, $url); $msz->emotes->updateEmote($emoteInfo, $order, $minRank, $url);
} }
$sCurrent = XArray::select($emoteStrings, fn($stringInfo) => $stringInfo->getString()); $sCurrent = XArray::select($emoteStrings, fn($stringInfo) => $stringInfo->string);
$sApply = $strings; $sApply = $strings;
$sRemove = []; $sRemove = [];
foreach($sCurrent as $string) foreach($sCurrent as $string)
if(!in_array($string, $sApply)) { if(!in_array($string, $sApply)) {
$sRemove[] = $string; $sRemove[] = $string;
$emotes->removeEmoteString($string); $msz->emotes->removeEmoteString($string);
} }
$sCurrent = array_diff($sCurrent, $sRemove); $sCurrent = array_diff($sCurrent, $sRemove);
foreach($sApply as $string) foreach($sApply as $string)
if(!in_array($string, $sCurrent)) { if(!in_array($string, $sCurrent)) {
$checkString = $emotes->checkEmoteString($string); $checkString = $msz->emotes->checkEmoteString($string);
if($checkString === '') { if($checkString === '') {
$emotes->addEmoteString($emoteInfo, $string); $msz->emotes->addEmoteString($emoteInfo, $string);
} else { } else {
echo match($checkString) { echo match($checkString) {
'empty' => 'String may not be empty.', 'empty' => 'String may not be empty.',
@ -94,10 +93,10 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$msz->createAuditLog( $msz->createAuditLog(
$isNew ? 'EMOTICON_CREATE' : 'EMOTICON_EDIT', $isNew ? 'EMOTICON_CREATE' : 'EMOTICON_EDIT',
[$emoteInfo->getId()] [$emoteInfo->id]
); );
Tools::redirect($msz->getUrls()->format('manage-general-emoticon', ['emote' => $emoteInfo->getId()])); Tools::redirect($msz->urls->format('manage-general-emoticon', ['emote' => $emoteInfo->id]));
return; return;
} }

View file

@ -3,44 +3,42 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_EMOTES_MANAGE)) if(!$msz->authInfo->getPerms('global')->check(Perm::G_EMOTES_MANAGE))
Template::throwError(403); Template::throwError(403);
$emotes = $msz->getEmotes();
if(CSRF::validateRequest() && !empty($_GET['emote'])) { if(CSRF::validateRequest() && !empty($_GET['emote'])) {
$emoteId = (string)filter_input(INPUT_GET, 'emote', FILTER_SANITIZE_NUMBER_INT); $emoteId = (string)filter_input(INPUT_GET, 'emote', FILTER_SANITIZE_NUMBER_INT);
try { try {
$emoteInfo = $emotes->getEmote($emoteId); $emoteInfo = $msz->emotes->getEmote($emoteId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
if(!empty($_GET['delete'])) { if(!empty($_GET['delete'])) {
$emotes->deleteEmote($emoteInfo); $msz->emotes->deleteEmote($emoteInfo);
$msz->createAuditLog('EMOTICON_DELETE', [$emoteInfo->getId()]); $msz->createAuditLog('EMOTICON_DELETE', [$emoteInfo->id]);
} else { } else {
if(isset($_GET['order'])) { if(isset($_GET['order'])) {
$order = filter_input(INPUT_GET, 'order'); $order = filter_input(INPUT_GET, 'order');
$offset = $order === 'i' ? 10 : ($order === 'd' ? -10 : 0); $offset = $order === 'i' ? 10 : ($order === 'd' ? -10 : 0);
$emotes->updateEmoteOrderOffset($emoteInfo, $offset); $msz->emotes->updateEmoteOrderOffset($emoteInfo, $offset);
$msz->createAuditLog('EMOTICON_ORDER', [$emoteInfo->getId()]); $msz->createAuditLog('EMOTICON_ORDER', [$emoteInfo->id]);
} }
if(isset($_GET['alias'])) { if(isset($_GET['alias'])) {
$alias = (string)filter_input(INPUT_GET, 'alias'); $alias = (string)filter_input(INPUT_GET, 'alias');
if($emotes->checkEmoteString($alias) === '') { if($msz->emotes->checkEmoteString($alias) === '') {
$emotes->addEmoteString($emoteInfo, $alias); $msz->emotes->addEmoteString($emoteInfo, $alias);
$msz->createAuditLog('EMOTICON_ALIAS', [$emoteInfo->getId(), $alias]); $msz->createAuditLog('EMOTICON_ALIAS', [$emoteInfo->id, $alias]);
} }
} }
} }
Tools::redirect($msz->getUrls()->format('manage-general-emoticons')); Tools::redirect($msz->urls->format('manage-general-emoticons'));
return; return;
} }
Template::render('manage.general.emoticons', [ Template::render('manage.general.emoticons', [
'emotes' => $emotes->getEmotes(), 'emotes' => $msz->emotes->getEmotes(),
]); ]);

View file

@ -1,8 +1,8 @@
<?php <?php
namespace Misuzu; namespace Misuzu;
$counterInfos = $msz->getCounters()->getCounters(orderBy: 'name'); $counterInfos = $msz->counters->getCounters(orderBy: 'name');
$counterNamesRaw = $msz->getConfig()->getArray('counters.names'); $counterNamesRaw = $msz->config->getArray('counters.names');
$counterNamesCount = count($counterNamesRaw); $counterNamesCount = count($counterNamesRaw);
$counterNames = []; $counterNames = [];

View file

@ -3,26 +3,24 @@ namespace Misuzu;
use Misuzu\Pagination; use Misuzu\Pagination;
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_LOGS_VIEW)) if(!$msz->authInfo->getPerms('global')->check(Perm::G_LOGS_VIEW))
Template::throwError(403); Template::throwError(403);
$usersCtx = $msz->getUsersContext(); $pagination = new Pagination($msz->auditLog->countLogs(), 50);
$auditLog = $msz->getAuditLog();
$pagination = new Pagination($auditLog->countLogs(), 50);
if(!$pagination->hasValidOffset()) if(!$pagination->hasValidOffset())
Template::throwError(404); Template::throwError(404);
$logs = iterator_to_array($auditLog->getLogs(pagination: $pagination)); $logs = iterator_to_array($msz->auditLog->getLogs(pagination: $pagination));
$userInfos = []; $userInfos = [];
$userColours = []; $userColours = [];
foreach($logs as $log) foreach($logs as $log)
if($log->hasUserId()) { if($log->userId !== null) {
$userId = $log->getUserId(); $userId = $log->userId;
if(!array_key_exists($userId, $userInfos)) { if(!array_key_exists($userId, $userInfos)) {
$userInfos[$userId] = $usersCtx->getUserInfo($userId); $userInfos[$userId] = $msz->usersCtx->getUserInfo($userId);
$userColours[$userId] = $usersCtx->getUserColour($userId); $userColours[$userId] = $msz->usersCtx->getUserColour($userId);
} }
} }

View file

@ -1,7 +1,7 @@
<?php <?php
namespace Misuzu; namespace Misuzu;
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CONFIG_MANAGE)) if(!$msz->authInfo->getPerms('global')->check(Perm::G_CONFIG_MANAGE))
Template::throwError(403); Template::throwError(403);
$valueName = (string)filter_input(INPUT_GET, 'name'); $valueName = (string)filter_input(INPUT_GET, 'name');
@ -13,7 +13,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$valueName = $valueInfo->getName(); $valueName = $valueInfo->getName();
$msz->createAuditLog('CONFIG_DELETE', [$valueName]); $msz->createAuditLog('CONFIG_DELETE', [$valueName]);
$cfg->removeValues($valueName); $cfg->removeValues($valueName);
Tools::redirect($msz->getUrls()->format('manage-general-settings')); Tools::redirect($msz->urls->format('manage-general-settings'));
return; return;
} }

View file

@ -3,7 +3,7 @@ namespace Misuzu;
use Index\Config\Db\DbConfig; use Index\Config\Db\DbConfig;
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CONFIG_MANAGE)) if(!$msz->authInfo->getPerms('global')->check(Perm::G_CONFIG_MANAGE))
Template::throwError(403); Template::throwError(403);
$isNew = true; $isNew = true;
@ -73,7 +73,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$msz->createAuditLog($isNew ? 'CONFIG_CREATE' : 'CONFIG_UPDATE', [$sName]); $msz->createAuditLog($isNew ? 'CONFIG_CREATE' : 'CONFIG_UPDATE', [$sName]);
$applyFunc($sName, $sValue); $applyFunc($sName, $sValue);
Tools::redirect($msz->getUrls()->format('manage-general-settings')); Tools::redirect($msz->urls->format('manage-general-settings'));
return; return;
} }

View file

@ -1,7 +1,7 @@
<?php <?php
namespace Misuzu; namespace Misuzu;
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CONFIG_MANAGE)) if(!$msz->authInfo->getPerms('global')->check(Perm::G_CONFIG_MANAGE))
Template::throwError(403); Template::throwError(403);
$hidden = $cfg->getArray('settings.hidden'); $hidden = $cfg->getArray('settings.hidden');

View file

@ -1,16 +1,15 @@
<?php <?php
namespace Misuzu; namespace Misuzu;
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_NEWS_CATEGORIES_MANAGE)) if(!$msz->authInfo->getPerms('global')->check(Perm::G_NEWS_CATEGORIES_MANAGE))
Template::throwError(403); Template::throwError(403);
$news = $msz->getNews(); $pagination = new Pagination($msz->news->countCategories(), 15);
$pagination = new Pagination($news->countCategories(), 15);
if(!$pagination->hasValidOffset()) if(!$pagination->hasValidOffset())
Template::throwError(404); Template::throwError(404);
$categories = $news->getCategories(pagination: $pagination); $categories = $msz->news->getCategories(pagination: $pagination);
Template::render('manage.news.categories', [ Template::render('manage.news.categories', [
'news_categories' => $categories, 'news_categories' => $categories,

View file

@ -3,13 +3,11 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_NEWS_CATEGORIES_MANAGE)) if(!$msz->authInfo->getPerms('global')->check(Perm::G_NEWS_CATEGORIES_MANAGE))
Template::throwError(403); Template::throwError(403);
$urls = $msz->getUrls();
$news = $msz->getNews();
$categoryId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT); $categoryId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
$loadCategoryInfo = fn() => $news->getCategory(categoryId: $categoryId); $loadCategoryInfo = fn() => $msz->news->getCategory(categoryId: $categoryId);
if(empty($categoryId)) if(empty($categoryId))
$isNew = true; $isNew = true;
@ -25,9 +23,9 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
if(!CSRF::validateRequest()) if(!CSRF::validateRequest())
Template::throwError(403); Template::throwError(403);
$news->deleteCategory($categoryInfo); $msz->news->deleteCategory($categoryInfo);
$msz->createAuditLog('NEWS_CATEGORY_DELETE', [$categoryInfo->getId()]); $msz->createAuditLog('NEWS_CATEGORY_DELETE', [$categoryInfo->id]);
Tools::redirect($urls->format('manage-news-categories')); Tools::redirect($msz->urls->format('manage-news-categories'));
return; return;
} }
@ -37,26 +35,26 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$hidden = !empty($_POST['nc_hidden']); $hidden = !empty($_POST['nc_hidden']);
if($isNew) { if($isNew) {
$categoryInfo = $news->createCategory($name, $description, $hidden); $categoryInfo = $msz->news->createCategory($name, $description, $hidden);
} else { } else {
if($name === $categoryInfo->getName()) if($name === $categoryInfo->name)
$name = null; $name = null;
if($description === $categoryInfo->getDescription()) if($description === $categoryInfo->description)
$description = null; $description = null;
if($hidden === $categoryInfo->isHidden()) if($hidden === $categoryInfo->hidden)
$hidden = null; $hidden = null;
if($name !== null || $description !== null || $hidden !== null) if($name !== null || $description !== null || $hidden !== null)
$news->updateCategory($categoryInfo, $name, $description, $hidden); $msz->news->updateCategory($categoryInfo, $name, $description, $hidden);
} }
$msz->createAuditLog( $msz->createAuditLog(
$isNew ? 'NEWS_CATEGORY_CREATE' : 'NEWS_CATEGORY_EDIT', $isNew ? 'NEWS_CATEGORY_CREATE' : 'NEWS_CATEGORY_EDIT',
[$categoryInfo->getId()] [$categoryInfo->id]
); );
if($isNew) { if($isNew) {
Tools::redirect($urls->format('manage-news-category', ['category' => $categoryInfo->getId()])); Tools::redirect($msz->urls->format('manage-news-category', ['category' => $categoryInfo->id]));
return; return;
} else $categoryInfo = $loadCategoryInfo(); } else $categoryInfo = $loadCategoryInfo();
break; break;

View file

@ -3,14 +3,11 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
$authInfo = $msz->getAuthInfo(); if(!$msz->authInfo->getPerms('global')->check(Perm::G_NEWS_POSTS_MANAGE))
if(!$authInfo->getPerms('global')->check(Perm::G_NEWS_POSTS_MANAGE))
Template::throwError(403); Template::throwError(403);
$urls = $msz->getUrls();
$news = $msz->getNews();
$postId = (string)filter_input(INPUT_GET, 'p', FILTER_SANITIZE_NUMBER_INT); $postId = (string)filter_input(INPUT_GET, 'p', FILTER_SANITIZE_NUMBER_INT);
$loadPostInfo = fn() => $news->getPost($postId); $loadPostInfo = fn() => $msz->news->getPost($postId);
if(empty($postId)) if(empty($postId))
$isNew = true; $isNew = true;
@ -26,9 +23,9 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
if(!CSRF::validateRequest()) if(!CSRF::validateRequest())
Template::throwError(403); Template::throwError(403);
$news->deletePost($postInfo); $msz->news->deletePost($postInfo);
$msz->createAuditLog('NEWS_POST_DELETE', [$postInfo->getId()]); $msz->createAuditLog('NEWS_POST_DELETE', [$postInfo->id]);
Tools::redirect($urls->format('manage-news-posts')); Tools::redirect($msz->urls->format('manage-news-posts'));
return; return;
} }
@ -39,40 +36,40 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$body = trim((string)filter_input(INPUT_POST, 'np_body')); $body = trim((string)filter_input(INPUT_POST, 'np_body'));
if($isNew) { if($isNew) {
$postInfo = $news->createPost($category, $title, $body, $featured, $authInfo->getUserInfo()); $postInfo = $msz->news->createPost($category, $title, $body, $featured, $msz->authInfo->userInfo);
} else { } else {
if($category === $postInfo->getCategoryId()) if($category === $postInfo->categoryId)
$category = null; $category = null;
if($title === $postInfo->getTitle()) if($title === $postInfo->title)
$title = null; $title = null;
if($body === $postInfo->getBody()) if($body === $postInfo->body)
$body = null; $body = null;
if($featured === $postInfo->isFeatured()) if($featured === $postInfo->featured)
$featured = null; $featured = null;
if($category !== null || $title !== null || $body !== null || $featured !== null) if($category !== null || $title !== null || $body !== null || $featured !== null)
$news->updatePost($postInfo, $category, $title, $body, $featured); $msz->news->updatePost($postInfo, $category, $title, $body, $featured);
} }
$msz->createAuditLog( $msz->createAuditLog(
$isNew ? 'NEWS_POST_CREATE' : 'NEWS_POST_EDIT', $isNew ? 'NEWS_POST_CREATE' : 'NEWS_POST_EDIT',
[$postInfo->getId()] [$postInfo->id]
); );
if($isNew) { if($isNew) {
if($postInfo->isFeatured()) { if($postInfo->featured) {
// Twitter integration used to be here, replace with Railgun Pulse integration // Twitter integration used to be here, replace with Railgun Pulse integration
} }
Tools::redirect($urls->format('manage-news-post', ['post' => $postInfo->getId()])); Tools::redirect($msz->urls->format('manage-news-post', ['post' => $postInfo->id]));
return; return;
} else $postInfo = $loadPostInfo(); } else $postInfo = $loadPostInfo();
break; break;
} }
$categories = []; $categories = [];
foreach($news->getCategories() as $categoryInfo) foreach($msz->news->getCategories() as $categoryInfo)
$categories[$categoryInfo->getId()] = $categoryInfo->getName(); $categories[$categoryInfo->id] = $categoryInfo->name;
Template::render('manage.news.post', [ Template::render('manage.news.post', [
'categories' => $categories, 'categories' => $categories,

View file

@ -1,11 +1,10 @@
<?php <?php
namespace Misuzu; namespace Misuzu;
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_NEWS_POSTS_MANAGE)) if(!$msz->authInfo->getPerms('global')->check(Perm::G_NEWS_POSTS_MANAGE))
Template::throwError(403); Template::throwError(403);
$news = $msz->getNews(); $pagination = new Pagination($msz->news->countPosts(
$pagination = new Pagination($news->countPosts(
includeScheduled: true, includeScheduled: true,
includeDeleted: true includeDeleted: true
), 15); ), 15);
@ -13,7 +12,7 @@ $pagination = new Pagination($news->countPosts(
if(!$pagination->hasValidOffset()) if(!$pagination->hasValidOffset())
Template::throwError(404); Template::throwError(404);
$posts = $news->getPosts( $posts = $msz->news->getPosts(
includeScheduled: true, includeScheduled: true,
includeDeleted: true, includeDeleted: true,
pagination: $pagination pagination: $pagination

View file

@ -5,37 +5,32 @@ use DateTimeInterface;
use RuntimeException; use RuntimeException;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
$authInfo = $msz->getAuthInfo(); if(!$msz->authInfo->getPerms('user')->check(Perm::U_BANS_MANAGE))
if(!$authInfo->getPerms('user')->check(Perm::U_BANS_MANAGE))
Template::throwError(403); Template::throwError(403);
$urls = $msz->getUrls();
$usersCtx = $msz->getUsersContext();
$bans = $usersCtx->getBans();
if($_SERVER['REQUEST_METHOD'] === 'GET' && filter_has_var(INPUT_GET, 'delete')) { if($_SERVER['REQUEST_METHOD'] === 'GET' && filter_has_var(INPUT_GET, 'delete')) {
if(!CSRF::validateRequest()) if(!CSRF::validateRequest())
Template::throwError(403); Template::throwError(403);
try { try {
$banInfo = $bans->getBan((string)filter_input(INPUT_GET, 'b')); $banInfo = $msz->usersCtx->bans->getBan((string)filter_input(INPUT_GET, 'b'));
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
$bans->deleteBans($banInfo); $msz->usersCtx->bans->deleteBans($banInfo);
$msz->createAuditLog('BAN_DELETE', [$banInfo->getId(), $banInfo->getUserId()]); $msz->createAuditLog('BAN_DELETE', [$banInfo->id, $banInfo->userId]);
Tools::redirect($urls->format('manage-users-bans', ['user' => $banInfo->getUserId()])); Tools::redirect($msz->urls->format('manage-users-bans', ['user' => $banInfo->userId]));
return; return;
} }
try { try {
$userInfo = $usersCtx->getUserInfo(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT), 'id'); $userInfo = $msz->usersCtx->getUserInfo(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT), 'id');
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
$modInfo = $authInfo->getUserInfo(); $modInfo = $msz->authInfo->userInfo;
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) { while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$expires = (int)filter_input(INPUT_POST, 'ub_expires', FILTER_SANITIZE_NUMBER_INT); $expires = (int)filter_input(INPUT_POST, 'ub_expires', FILTER_SANITIZE_NUMBER_INT);
@ -64,13 +59,13 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
} else } else
$expires = time() + $expires; $expires = time() + $expires;
$banInfo = $bans->createBan( $banInfo = $msz->usersCtx->bans->createBan(
$userInfo, $expires, $publicReason, $privateReason, $userInfo, $expires, $publicReason, $privateReason,
severity: $severity, modInfo: $modInfo severity: $severity, modInfo: $modInfo
); );
$msz->createAuditLog('BAN_CREATE', [$banInfo->getId(), $userInfo->getId()]); $msz->createAuditLog('BAN_CREATE', [$banInfo->id, $userInfo->getId()]);
Tools::redirect($urls->format('manage-users-bans', ['user' => $userInfo->getId()])); Tools::redirect($msz->urls->format('manage-users-bans', ['user' => $userInfo->getId()]));
return; return;
} }

View file

@ -3,40 +3,37 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_BANS_MANAGE)) if(!$msz->authInfo->getPerms('user')->check(Perm::U_BANS_MANAGE))
Template::throwError(403); Template::throwError(403);
$usersCtx = $msz->getUsersContext();
$bans = $usersCtx->getBans();
$filterUser = null; $filterUser = null;
if(filter_has_var(INPUT_GET, 'u')) { if(filter_has_var(INPUT_GET, 'u')) {
$filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT); $filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT);
try { try {
$filterUser = $usersCtx->getUserInfo($filterUserId); $filterUser = $msz->usersCtx->getUserInfo($filterUserId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
} }
$pagination = new Pagination($bans->countBans(userInfo: $filterUser), 10); $pagination = new Pagination($msz->usersCtx->bans->countBans(userInfo: $filterUser), 10);
if(!$pagination->hasValidOffset()) if(!$pagination->hasValidOffset())
Template::throwError(404); Template::throwError(404);
$banList = []; $banList = [];
$banInfos = $bans->getBans(userInfo: $filterUser, activeFirst: true, pagination: $pagination); $banInfos = $msz->usersCtx->bans->getBans(userInfo: $filterUser, activeFirst: true, pagination: $pagination);
foreach($banInfos as $banInfo) { foreach($banInfos as $banInfo) {
$userInfo = $usersCtx->getUserInfo($banInfo->getUserId()); $userInfo = $msz->usersCtx->getUserInfo($banInfo->userId);
$userColour = $usersCtx->getUserColour($userInfo); $userColour = $msz->usersCtx->getUserColour($userInfo);
if(!$banInfo->hasModId()) { if($banInfo->modId === null) {
$modInfo = null; $modInfo = null;
$modColour = null; $modColour = null;
} else { } else {
$modInfo = $usersCtx->getUserInfo($banInfo->getModId()); $modInfo = $msz->usersCtx->getUserInfo($banInfo->modId);
$modColour = $usersCtx->getUserColour($modInfo); $modColour = $msz->usersCtx->getUserColour($modInfo);
} }
$banList[] = [ $banList[] = [

View file

@ -1,29 +1,28 @@
<?php <?php
namespace Misuzu; namespace Misuzu;
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_USERS_MANAGE)) use Misuzu\Users\Roles;
if(!$msz->authInfo->getPerms('user')->check(Perm::U_USERS_MANAGE))
Template::throwError(403); Template::throwError(403);
$usersCtx = $msz->getUsersContext(); $pagination = new Pagination($msz->usersCtx->users->countUsers(), 30);
$users = $usersCtx->getUsers();
$roles = $usersCtx->getRoles();
$pagination = new Pagination($users->countUsers(), 30);
if(!$pagination->hasValidOffset()) if(!$pagination->hasValidOffset())
Template::throwError(404); Template::throwError(404);
$userList = []; $userList = [];
$userInfos = $users->getUsers(pagination: $pagination, orderBy: 'id'); $userInfos = $msz->usersCtx->users->getUsers(pagination: $pagination, orderBy: 'id');
$roleInfos = []; $roleInfos = [];
foreach($userInfos as $userInfo) { foreach($userInfos as $userInfo) {
$displayRoleId = $userInfo->getDisplayRoleId() ?? '1'; $displayRoleId = $userInfo->displayRoleId ?? Roles::DEFAULT_ROLE;
if(array_key_exists($displayRoleId, $roleInfos)) if(array_key_exists($displayRoleId, $roleInfos))
$roleInfo = $roleInfos[$displayRoleId]; $roleInfo = $roleInfos[$displayRoleId];
else else
$roleInfos[$displayRoleId] = $roleInfo = $roles->getRole($displayRoleId); $roleInfos[$displayRoleId] = $roleInfo = $msz->usersCtx->roles->getRole($displayRoleId);
$colour = $userInfo->hasColour() ? $userInfo->getColour() : $roleInfo->getColour(); $colour = $userInfo->hasColour ? $userInfo->colour : $roleInfo->colour;
$userList[] = [ $userList[] = [
'info' => $userInfo, 'info' => $userInfo,

View file

@ -3,8 +3,7 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
$authInfo = $msz->getAuthInfo(); if(!$msz->authInfo->getPerms('user')->check(Perm::U_NOTES_MANAGE))
if(!$authInfo->getPerms('user')->check(Perm::U_NOTES_MANAGE))
Template::throwError(403); Template::throwError(403);
$hasNoteId = filter_has_var(INPUT_GET, 'n'); $hasNoteId = filter_has_var(INPUT_GET, 'n');
@ -13,25 +12,21 @@ $hasUserId = filter_has_var(INPUT_GET, 'u');
if((!$hasNoteId && !$hasUserId) || ($hasNoteId && $hasUserId)) if((!$hasNoteId && !$hasUserId) || ($hasNoteId && $hasUserId))
Template::throwError(400); Template::throwError(400);
$urls = $msz->getUrls();
$usersCtx = $msz->getUsersContext();
$modNotes = $usersCtx->getModNotes();
if($hasUserId) { if($hasUserId) {
$isNew = true; $isNew = true;
try { try {
$userInfo = $usersCtx->getUserInfo(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT)); $userInfo = $msz->usersCtx->getUserInfo(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT));
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
$authorInfo = $authInfo->getUserInfo(); $authorInfo = $msz->authInfo->userInfo;
} elseif($hasNoteId) { } elseif($hasNoteId) {
$isNew = false; $isNew = false;
try { try {
$noteInfo = $modNotes->getNote((string)filter_input(INPUT_GET, 'n', FILTER_SANITIZE_NUMBER_INT)); $noteInfo = $msz->usersCtx->modNotes->getNote((string)filter_input(INPUT_GET, 'n', FILTER_SANITIZE_NUMBER_INT));
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
@ -40,14 +35,14 @@ if($hasUserId) {
if(!CSRF::validateRequest()) if(!CSRF::validateRequest())
Template::throwError(403); Template::throwError(403);
$modNotes->deleteNotes($noteInfo); $msz->usersCtx->modNotes->deleteNotes($noteInfo);
$msz->createAuditLog('MOD_NOTE_DELETE', [$noteInfo->getId(), $noteInfo->getUserId()]); $msz->createAuditLog('MOD_NOTE_DELETE', [$noteInfo->id, $noteInfo->userId]);
Tools::redirect($urls->format('manage-users-notes', ['user' => $noteInfo->getUserId()])); Tools::redirect($msz->urls->format('manage-users-notes', ['user' => $noteInfo->userId]));
return; return;
} }
$userInfo = $usersCtx->getUserInfo($noteInfo->getUserId()); $userInfo = $msz->usersCtx->getUserInfo($noteInfo->userId);
$authorInfo = $noteInfo->hasAuthorId() ? $usersCtx->getUserInfo($noteInfo->getAuthorId()) : null; $authorInfo = $noteInfo->authorId !== null ? $msz->usersCtx->getUserInfo($noteInfo->authorId) : null;
} }
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) { while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
@ -55,24 +50,24 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$body = trim((string)filter_input(INPUT_POST, 'mn_body')); $body = trim((string)filter_input(INPUT_POST, 'mn_body'));
if($isNew) { if($isNew) {
$noteInfo = $modNotes->createNote($userInfo, $title, $body, $authorInfo); $noteInfo = $msz->usersCtx->modNotes->createNote($userInfo, $title, $body, $authorInfo);
} else { } else {
if($title === $noteInfo->getTitle()) if($title === $noteInfo->title)
$title = null; $title = null;
if($body === $noteInfo->getBody()) if($body === $noteInfo->body)
$body = null; $body = null;
if($title !== null || $body !== null) if($title !== null || $body !== null)
$modNotes->updateNote($noteInfo, $title, $body); $msz->usersCtx->modNotes->updateNote($noteInfo, $title, $body);
} }
$msz->createAuditLog( $msz->createAuditLog(
$isNew ? 'MOD_NOTE_CREATE' : 'MOD_NOTE_UPDATE', $isNew ? 'MOD_NOTE_CREATE' : 'MOD_NOTE_UPDATE',
[$noteInfo->getId(), $userInfo->getId()] [$noteInfo->id, $userInfo->getId()]
); );
// this is easier // this is easier
Tools::redirect($urls->format('manage-users-note', ['note' => $noteInfo->getId()])); Tools::redirect($msz->urls->format('manage-users-note', ['note' => $noteInfo->id]));
return; return;
} }
@ -80,7 +75,7 @@ Template::render('manage.users.note', [
'note_new' => $isNew, 'note_new' => $isNew,
'note_info' => $noteInfo ?? null, 'note_info' => $noteInfo ?? null,
'note_user' => $userInfo, 'note_user' => $userInfo,
'note_user_colour' => $usersCtx->getUserColour($userInfo), 'note_user_colour' => $msz->usersCtx->getUserColour($userInfo),
'note_author' => $authorInfo, 'note_author' => $authorInfo,
'note_author_colour' => $usersCtx->getUserColour($authorInfo), 'note_author_colour' => $msz->usersCtx->getUserColour($authorInfo),
]); ]);

View file

@ -3,40 +3,37 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_NOTES_MANAGE)) if(!$msz->authInfo->getPerms('user')->check(Perm::U_NOTES_MANAGE))
Template::throwError(403); Template::throwError(403);
$usersCtx = $msz->getUsersContext();
$modNotes = $usersCtx->getModNotes();
$filterUser = null; $filterUser = null;
if(filter_has_var(INPUT_GET, 'u')) { if(filter_has_var(INPUT_GET, 'u')) {
$filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT); $filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT);
try { try {
$filterUser = $usersCtx->getUserInfo($filterUserId); $filterUser = $msz->usersCtx->getUserInfo($filterUserId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
} }
$pagination = new Pagination($modNotes->countNotes(userInfo: $filterUser), 10); $pagination = new Pagination($msz->usersCtx->modNotes->countNotes(userInfo: $filterUser), 10);
if(!$pagination->hasValidOffset()) if(!$pagination->hasValidOffset())
Template::throwError(404); Template::throwError(404);
$notes = []; $notes = [];
$noteInfos = $modNotes->getNotes(userInfo: $filterUser, pagination: $pagination); $noteInfos = $msz->usersCtx->modNotes->getNotes(userInfo: $filterUser, pagination: $pagination);
foreach($noteInfos as $noteInfo) { foreach($noteInfos as $noteInfo) {
$userInfo = $usersCtx->getUserInfo($noteInfo->getUserId()); $userInfo = $msz->usersCtx->getUserInfo($noteInfo->userId);
$userColour = $usersCtx->getUserColour($userInfo); $userColour = $msz->usersCtx->getUserColour($userInfo);
if(!$noteInfo->hasAuthorId()) { if($noteInfo->authorId === null) {
$authorInfo = null; $authorInfo = null;
$authorColour = null; $authorColour = null;
} else { } else {
$authorInfo = $usersCtx->getUserInfo($noteInfo->getAuthorId()); $authorInfo = $msz->usersCtx->getUserInfo($noteInfo->authorId);
$authorColour = $usersCtx->getUserColour($authorInfo); $authorColour = $msz->usersCtx->getUserColour($authorInfo);
} }
$notes[] = [ $notes[] = [

View file

@ -6,38 +6,33 @@ use Index\Colour\Colour;
use Index\Colour\ColourRgb; use Index\Colour\ColourRgb;
use Misuzu\Perm; use Misuzu\Perm;
$authInfo = $msz->getAuthInfo(); $viewerPerms = $msz->authInfo->getPerms('user');
$viewerPerms = $authInfo->getPerms('user');
if(!$viewerPerms->check(Perm::U_ROLES_MANAGE)) if(!$viewerPerms->check(Perm::U_ROLES_MANAGE))
Template::throwError(403); Template::throwError(403);
$roleInfo = null; $roleInfo = null;
$usersCtx = $msz->getUsersContext();
$users = $usersCtx->getUsers();
$roles = $usersCtx->getRoles();
$perms = $msz->getPerms();
if(filter_has_var(INPUT_GET, 'r')) { if(filter_has_var(INPUT_GET, 'r')) {
$roleId = (string)filter_input(INPUT_GET, 'r', FILTER_SANITIZE_NUMBER_INT); $roleId = (string)filter_input(INPUT_GET, 'r', FILTER_SANITIZE_NUMBER_INT);
try { try {
$isNew = false; $isNew = false;
$roleInfo = $roles->getRole($roleId); $roleInfo = $msz->usersCtx->roles->getRole($roleId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
} else $isNew = true; } else $isNew = true;
$currentUser = $authInfo->getUserInfo(); $currentUser = $msz->authInfo->userInfo;
$canEditPerms = $viewerPerms->check(Perm::U_PERMS_MANAGE); $canEditPerms = $viewerPerms->check(Perm::U_PERMS_MANAGE);
$permsInfos = $roleInfo === null ? null : $perms->getPermissionInfo(roleInfo: $roleInfo, categoryNames: Perm::INFO_FOR_ROLE); $permsInfos = $roleInfo === null ? null : $msz->perms->getPermissionInfo(roleInfo: $roleInfo, categoryNames: Perm::INFO_FOR_ROLE);
$permsLists = Perm::createList(Perm::LISTS_FOR_ROLE); $permsLists = Perm::createList(Perm::LISTS_FOR_ROLE);
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) { while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$userRank = $users->getUserRank($currentUser); $userRank = $msz->usersCtx->users->getUserRank($currentUser);
if(!$isNew && !$currentUser->isSuperUser() && $roleInfo->getRank() >= $userRank) { if(!$isNew && !$currentUser->super && $roleInfo->rank >= $userRank) {
echo 'You aren\'t allowed to edit this role.'; echo 'You aren\'t allowed to edit this role.';
break; break;
} }
@ -68,7 +63,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
'role_ur_col_blue' => $colourBlue, 'role_ur_col_blue' => $colourBlue,
]); ]);
if(!$currentUser->isSuperUser() && $roleRank >= $userRank) { if(!$currentUser->super && $roleRank >= $userRank) {
echo 'You aren\'t allowed to make a role with equal rank to your own.'; echo 'You aren\'t allowed to make a role with equal rank to your own.';
break; break;
} }
@ -108,7 +103,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
} }
if($isNew) { if($isNew) {
$roleInfo = $roles->createRole( $roleInfo = $msz->usersCtx->roles->createRole(
$roleName, $roleName,
$roleRank, $roleRank,
$roleColour, $roleColour,
@ -119,25 +114,25 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
leavable: $roleLeavable leavable: $roleLeavable
); );
} else { } else {
if($roleName === $roleInfo->getName()) if($roleName === $roleInfo->name)
$roleName = null; $roleName = null;
if($roleString === $roleInfo->getString()) if($roleString === $roleInfo->string)
$roleString = null; $roleString = null;
if($roleHide === $roleInfo->isHidden()) if($roleHide === $roleInfo->hidden)
$roleHide = null; $roleHide = null;
if($roleLeavable === $roleInfo->isLeavable()) if($roleLeavable === $roleInfo->leavable)
$roleLeavable = null; $roleLeavable = null;
if($roleRank === $roleInfo->getRank()) if($roleRank === $roleInfo->rank)
$roleRank = null; $roleRank = null;
if($roleTitle === $roleInfo->getTitle()) if($roleTitle === $roleInfo->title)
$roleTitle = null; $roleTitle = null;
if($roleDesc === $roleInfo->getDescription()) if($roleDesc === $roleInfo->description)
$roleDesc = null; $roleDesc = null;
// local genius did not implement colour comparison // local genius did not implement colour comparison
if((string)$roleColour === (string)$roleInfo->getColour()) if((string)$roleColour === (string)$roleInfo->colour)
$roleColour = null; $roleColour = null;
$roles->updateRole( $msz->usersCtx->roles->updateRole(
$roleInfo, $roleInfo,
string: $roleString, string: $roleString,
name: $roleName, name: $roleName,
@ -152,7 +147,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$msz->createAuditLog( $msz->createAuditLog(
$isNew ? 'ROLE_CREATE' : 'ROLE_UPDATE', $isNew ? 'ROLE_CREATE' : 'ROLE_UPDATE',
[$roleInfo->getId()] [$roleInfo->id]
); );
if($canEditPerms && filter_has_var(INPUT_POST, 'perms')) { if($canEditPerms && filter_has_var(INPUT_POST, 'perms')) {
@ -162,13 +157,13 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
); );
foreach($permsApply as $categoryName => $values) foreach($permsApply as $categoryName => $values)
$perms->setPermissions($categoryName, $values['allow'], $values['deny'], roleInfo: $roleInfo); $msz->perms->setPermissions($categoryName, $values['allow'], $values['deny'], roleInfo: $roleInfo);
// could target all users with the role but ech // could target all users with the role but ech
$msz->getConfig()->setBoolean('perms.needsRecalc', true); $msz->config->setBoolean('perms.needsRecalc', true);
} }
Tools::redirect($msz->getUrls()->format('manage-role', ['role' => $roleInfo->getId()])); Tools::redirect($msz->urls->format('manage-role', ['role' => $roleInfo->id]));
return; return;
} }

View file

@ -1,10 +1,10 @@
<?php <?php
namespace Misuzu; namespace Misuzu;
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_ROLES_MANAGE)) if(!$msz->authInfo->getPerms('user')->check(Perm::U_ROLES_MANAGE))
Template::throwError(403); Template::throwError(403);
$roles = $msz->getUsersContext()->getRoles(); $roles = $msz->usersCtx->roles;
$pagination = new Pagination($roles->countRoles(), 10); $pagination = new Pagination($roles->countRoles(), 10);
if(!$pagination->hasValidOffset()) if(!$pagination->hasValidOffset())

View file

@ -7,18 +7,11 @@ use Misuzu\Perm;
use Misuzu\Auth\AuthTokenCookie; use Misuzu\Auth\AuthTokenCookie;
use Misuzu\Users\User; use Misuzu\Users\User;
$authInfo = $msz->getAuthInfo(); $viewerPerms = $msz->authInfo->getPerms('user');
$viewerPerms = $authInfo->getPerms('user'); if(!$msz->authInfo->isLoggedIn)
if(!$authInfo->isLoggedIn())
Template::throwError(403); Template::throwError(403);
$urls = $msz->getUrls(); $currentUser = $msz->authInfo->userInfo;
$usersCtx = $msz->getUsersContext();
$users = $usersCtx->getUsers();
$roles = $usersCtx->getRoles();
$perms = $msz->getPerms();
$currentUser = $authInfo->getUserInfo();
$canManageUsers = $viewerPerms->check(Perm::U_USERS_MANAGE); $canManageUsers = $viewerPerms->check(Perm::U_USERS_MANAGE);
$canManagePerms = $viewerPerms->check(Perm::U_PERMS_MANAGE); $canManagePerms = $viewerPerms->check(Perm::U_PERMS_MANAGE);
@ -26,7 +19,7 @@ $canManageNotes = $viewerPerms->check(Perm::U_NOTES_MANAGE);
$canManageWarnings = $viewerPerms->check(Perm::U_WARNINGS_MANAGE); $canManageWarnings = $viewerPerms->check(Perm::U_WARNINGS_MANAGE);
$canManageBans = $viewerPerms->check(Perm::U_BANS_MANAGE); $canManageBans = $viewerPerms->check(Perm::U_BANS_MANAGE);
$canImpersonate = $viewerPerms->check(Perm::U_CAN_IMPERSONATE); $canImpersonate = $viewerPerms->check(Perm::U_CAN_IMPERSONATE);
$canSendTestMail = $currentUser->isSuperUser(); $canSendTestMail = $currentUser->super;
$hasAccess = $canManageUsers || $canManageNotes || $canManageWarnings || $canManageBans; $hasAccess = $canManageUsers || $canManageNotes || $canManageWarnings || $canManageBans;
if(!$hasAccess) if(!$hasAccess)
@ -36,18 +29,18 @@ $notices = [];
$userId = (int)filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT); $userId = (int)filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT);
try { try {
$userInfo = $users->getUser($userId, 'id'); $userInfo = $msz->usersCtx->users->getUser($userId, 'id');
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
$currentUserRank = $users->getUserRank($currentUser); $currentUserRank = $msz->usersCtx->users->getUserRank($currentUser);
$userRank = $users->getUserRank($userInfo); $userRank = $msz->usersCtx->users->getUserRank($userInfo);
$canEdit = $canManageUsers && ($currentUser->isSuperUser() || (string)$currentUser->getId() === $userInfo->getId() || $currentUserRank > $userRank); $canEdit = $canManageUsers && ($currentUser->super || (string)$currentUser->getId() === $userInfo->getId() || $currentUserRank > $userRank);
$canEditPerms = $canEdit && $canManagePerms; $canEditPerms = $canEdit && $canManagePerms;
$permsInfos = $perms->getPermissionInfo(userInfo: $userInfo, categoryNames: Perm::INFO_FOR_USER); $permsInfos = $msz->perms->getPermissionInfo(userInfo: $userInfo, categoryNames: Perm::INFO_FOR_USER);
$permsLists = Perm::createList(Perm::LISTS_FOR_USER); $permsLists = Perm::createList(Perm::LISTS_FOR_USER);
$permsNeedRecalc = false; $permsNeedRecalc = false;
@ -58,22 +51,22 @@ if(CSRF::validateRequest() && $canEdit) {
} elseif(!is_string($_POST['impersonate_user']) || $_POST['impersonate_user'] !== 'meow') { } elseif(!is_string($_POST['impersonate_user']) || $_POST['impersonate_user'] !== 'meow') {
$notices[] = 'You didn\'t say the magic word.'; $notices[] = 'You didn\'t say the magic word.';
} else { } else {
$allowToImpersonate = $currentUser->isSuperUser(); $allowToImpersonate = $currentUser->super;
if(!$allowToImpersonate) { if(!$allowToImpersonate) {
$allowImpersonateUsers = $msz->getConfig()->getArray(sprintf('impersonate.allow.u%s', $currentUser->getId())); $allowImpersonateUsers = $msz->config->getArray(sprintf('impersonate.allow.u%s', $currentUser->getId()));
$allowToImpersonate = in_array($userInfo->getId(), $allowImpersonateUsers, true); $allowToImpersonate = in_array($userInfo->getId(), $allowImpersonateUsers, true);
} }
if($allowToImpersonate) { if($allowToImpersonate) {
$msz->createAuditLog('USER_IMPERSONATE', [$userInfo->getId(), $userInfo->getName()]); $msz->createAuditLog('USER_IMPERSONATE', [$userInfo->getId(), $userInfo->name]);
$tokenBuilder = $authInfo->getTokenInfo()->toBuilder(); $tokenBuilder = $msz->authInfo->tokenInfo->toBuilder();
$tokenBuilder->setImpersonatedUserId($userInfo->getId()); $tokenBuilder->setImpersonatedUserId($userInfo->getId());
$tokenInfo = $tokenBuilder->toInfo(); $tokenInfo = $tokenBuilder->toInfo();
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo)); AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
Tools::redirect($urls->format('index')); Tools::redirect($msz->urls->format('index'));
return; return;
} else $notices[] = 'You aren\'t allowed to impersonate this user.'; } else $notices[] = 'You aren\'t allowed to impersonate this user.';
} }
@ -86,7 +79,7 @@ if(CSRF::validateRequest() && $canEdit) {
$notices[] = 'Invalid request thing shut the fuck up.'; $notices[] = 'Invalid request thing shut the fuck up.';
} else { } else {
$testMail = Mailer::sendMessage( $testMail = Mailer::sendMessage(
[$userInfo->getEMailAddress() => $userInfo->getName()], [$userInfo->emailAddress => $userInfo->name],
'Flashii Test E-mail', 'Flashii Test E-mail',
'You were sent this e-mail to validate if you can receive e-mails from Flashii. You may discard it.' 'You were sent this e-mail to validate if you can receive e-mails from Flashii. You may discard it.'
); );
@ -106,13 +99,13 @@ if(CSRF::validateRequest() && $canEdit) {
} }
$existingRoles = []; $existingRoles = [];
foreach(iterator_to_array($roles->getRoles(userInfo: $userInfo)) as $roleInfo) foreach(iterator_to_array($msz->usersCtx->roles->getRoles(userInfo: $userInfo)) as $roleInfo)
$existingRoles[$roleInfo->getId()] = $roleInfo; $existingRoles[$roleInfo->id] = $roleInfo;
$removeRoles = []; $removeRoles = [];
foreach($existingRoles as $roleInfo) { foreach($existingRoles as $roleInfo) {
if($roleInfo->isDefault() || !($currentUser->isSuperUser() || $userRank > $roleInfo->getRank())) if($roleInfo->default || !($currentUser->super || $userRank > $roleInfo->rank))
continue; continue;
if(!in_array($roleInfo->getId(), $applyRoles)) if(!in_array($roleInfo->getId(), $applyRoles))
@ -120,18 +113,18 @@ if(CSRF::validateRequest() && $canEdit) {
} }
if(!empty($removeRoles)) if(!empty($removeRoles))
$users->removeRoles($userInfo, $removeRoles); $msz->usersCtx->users->removeRoles($userInfo, $removeRoles);
$addRoles = []; $addRoles = [];
foreach($applyRoles as $roleId) { foreach($applyRoles as $roleId) {
try { try {
$roleInfo = $existingRoles[$roleId] ?? $roles->getRole($roleId); $roleInfo = $existingRoles[$roleId] ?? $msz->usersCtx->roles->getRole($roleId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
continue; continue;
} }
if(!$currentUser->isSuperUser() && $userRank <= $roleInfo->getRank()) if(!$currentUser->super && $userRank <= $roleInfo->rank)
continue; continue;
if(!in_array($roleInfo, $existingRoles)) if(!in_array($roleInfo, $existingRoles))
@ -139,7 +132,7 @@ if(CSRF::validateRequest() && $canEdit) {
} }
if(!empty($addRoles)) if(!empty($addRoles))
$users->addRoles($userInfo, $addRoles); $msz->usersCtx->users->addRoles($userInfo, $addRoles);
if(!empty($addRoles) || !empty($removeRoles)) if(!empty($addRoles) || !empty($removeRoles))
$permsNeedRecalc = true; $permsNeedRecalc = true;
@ -150,7 +143,7 @@ if(CSRF::validateRequest() && $canEdit) {
$setTitle = (string)($_POST['user']['title'] ?? ''); $setTitle = (string)($_POST['user']['title'] ?? '');
$displayRole = (string)($_POST['user']['display_role'] ?? 0); $displayRole = (string)($_POST['user']['display_role'] ?? 0);
if(!$users->hasRole($userInfo, $displayRole)) if(!$msz->usersCtx->users->hasRole($userInfo, $displayRole))
$notices[] = 'User does not have the role you\'re trying to assign as primary.'; $notices[] = 'User does not have the role you\'re trying to assign as primary.';
$countryValidation = strlen($setCountry) === 2 $countryValidation = strlen($setCountry) === 2
@ -164,7 +157,7 @@ if(CSRF::validateRequest() && $canEdit) {
$notices[] = 'User title was invalid.'; $notices[] = 'User title was invalid.';
if(empty($notices)) { if(empty($notices)) {
$users->updateUser( $msz->usersCtx->users->updateUser(
userInfo: $userInfo, userInfo: $userInfo,
displayRoleInfo: $displayRole, displayRoleInfo: $displayRole,
countryCode: (string)($_POST['user']['country'] ?? 'XX'), countryCode: (string)($_POST['user']['country'] ?? 'XX'),
@ -183,7 +176,7 @@ if(CSRF::validateRequest() && $canEdit) {
} }
if(empty($notices)) if(empty($notices))
$users->updateUser(userInfo: $userInfo, colour: $setColour); $msz->usersCtx->users->updateUser(userInfo: $userInfo, colour: $setColour);
} }
if(!empty($_POST['password']) && is_array($_POST['password'])) { if(!empty($_POST['password']) && is_array($_POST['password'])) {
@ -194,13 +187,13 @@ if(CSRF::validateRequest() && $canEdit) {
if($passwordNewValue !== $passwordConfirmValue) if($passwordNewValue !== $passwordConfirmValue)
$notices[] = 'Confirm password does not match.'; $notices[] = 'Confirm password does not match.';
else { else {
$passwordValidation = $users->validatePassword($passwordNewValue); $passwordValidation = $msz->usersCtx->users->validatePassword($passwordNewValue);
if($passwordValidation !== '') if($passwordValidation !== '')
$notices[] = $users->validatePasswordText($passwordValidation); $notices[] = $msz->usersCtx->users->validatePasswordText($passwordValidation);
} }
if(empty($notices)) if(empty($notices))
$users->updateUser(userInfo: $userInfo, password: $passwordNewValue); $msz->usersCtx->users->updateUser(userInfo: $userInfo, password: $passwordNewValue);
} }
} }
@ -211,23 +204,23 @@ if(CSRF::validateRequest() && $canEdit) {
); );
foreach($permsApply as $categoryName => $values) foreach($permsApply as $categoryName => $values)
$perms->setPermissions($categoryName, $values['allow'], $values['deny'], userInfo: $userInfo); $msz->perms->setPermissions($categoryName, $values['allow'], $values['deny'], userInfo: $userInfo);
$permsNeedRecalc = true; $permsNeedRecalc = true;
} }
if($permsNeedRecalc) if($permsNeedRecalc)
$perms->precalculatePermissions( $msz->perms->precalculatePermissions(
$msz->getForumContext()->getCategories(), $msz->forumCtx->categories,
[$userInfo->getId()] [$userInfo->getId()]
); );
Tools::redirect($urls->format('manage-user', ['user' => $userInfo->getId()])); Tools::redirect($msz->urls->format('manage-user', ['user' => $userInfo->getId()]));
return; return;
} }
$rolesAll = iterator_to_array($roles->getRoles()); $rolesAll = iterator_to_array($msz->usersCtx->roles->getRoles());
$userRoleIds = $users->hasRoles($userInfo, $rolesAll); $userRoleIds = $msz->usersCtx->users->hasRoles($userInfo, $rolesAll);
Template::render('manage.users.user', [ Template::render('manage.users.user', [
'user_info' => $userInfo, 'user_info' => $userInfo,

View file

@ -3,49 +3,43 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
$authInfo = $msz->getAuthInfo(); if(!$msz->authInfo->getPerms('user')->check(Perm::U_WARNINGS_MANAGE))
if(!$authInfo->getPerms('user')->check(Perm::U_WARNINGS_MANAGE))
Template::throwError(403); Template::throwError(403);
$urls = $msz->getUrls();
$usersCtx = $msz->getUsersContext();
$users = $usersCtx->getUsers();
$warns = $usersCtx->getWarnings();
if($_SERVER['REQUEST_METHOD'] === 'GET' && filter_has_var(INPUT_GET, 'delete')) { if($_SERVER['REQUEST_METHOD'] === 'GET' && filter_has_var(INPUT_GET, 'delete')) {
if(!CSRF::validateRequest()) if(!CSRF::validateRequest())
Template::throwError(403); Template::throwError(403);
try { try {
$warnInfo = $warns->getWarning((string)filter_input(INPUT_GET, 'w')); $warnInfo = $msz->usersCtx->warnings->getWarning((string)filter_input(INPUT_GET, 'w'));
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
$warns->deleteWarnings($warnInfo); $msz->usersCtx->warnings->deleteWarnings($warnInfo);
$msz->createAuditLog('WARN_DELETE', [$warnInfo->getId(), $warnInfo->getUserId()]); $msz->createAuditLog('WARN_DELETE', [$warnInfo->id, $warnInfo->userId]);
Tools::redirect($urls->format('manage-users-warnings', ['user' => $warnInfo->getUserId()])); Tools::redirect($msz->urls->format('manage-users-warnings', ['user' => $warnInfo->userId]));
return; return;
} }
try { try {
$userInfo = $users->getUser(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT), 'id'); $userInfo = $msz->usersCtx->users->getUser(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT), 'id');
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
$modInfo = $authInfo->getUserInfo(); $modInfo = $msz->authInfo->userInfo;
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) { while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$body = trim((string)filter_input(INPUT_POST, 'uw_body')); $body = trim((string)filter_input(INPUT_POST, 'uw_body'));
Template::set('warn_value_body', $body); Template::set('warn_value_body', $body);
$warnInfo = $warns->createWarning( $warnInfo = $msz->usersCtx->warnings->createWarning(
$userInfo, $body, modInfo: $modInfo $userInfo, $body, modInfo: $modInfo
); );
$msz->createAuditLog('WARN_CREATE', [$warnInfo->getId(), $userInfo->getId()]); $msz->createAuditLog('WARN_CREATE', [$warnInfo->id, $userInfo->getId()]);
Tools::redirect($urls->format('manage-users-warnings', ['user' => $userInfo->getId()])); Tools::redirect($msz->urls->format('manage-users-warnings', ['user' => $userInfo->getId()]));
return; return;
} }

View file

@ -3,40 +3,37 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_WARNINGS_MANAGE)) if(!$msz->authInfo->getPerms('user')->check(Perm::U_WARNINGS_MANAGE))
Template::throwError(403); Template::throwError(403);
$usersCtx = $msz->getUsersContext();
$warns = $usersCtx->getWarnings();
$filterUser = null; $filterUser = null;
if(filter_has_var(INPUT_GET, 'u')) { if(filter_has_var(INPUT_GET, 'u')) {
$filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT); $filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT);
try { try {
$filterUser = $usersCtx->getUserInfo($filterUserId); $filterUser = $msz->usersCtx->getUserInfo($filterUserId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
Template::throwError(404); Template::throwError(404);
} }
} }
$pagination = new Pagination($warns->countWarnings(userInfo: $filterUser), 10); $pagination = new Pagination($msz->usersCtx->warnings->countWarnings(userInfo: $filterUser), 10);
if(!$pagination->hasValidOffset()) if(!$pagination->hasValidOffset())
Template::throwError(404); Template::throwError(404);
$warnList = []; $warnList = [];
$warnInfos = $warns->getWarnings(userInfo: $filterUser, pagination: $pagination); $warnInfos = $msz->usersCtx->warnings->getWarnings(userInfo: $filterUser, pagination: $pagination);
foreach($warnInfos as $warnInfo) { foreach($warnInfos as $warnInfo) {
$userInfo = $usersCtx->getUserInfo($warnInfo->getUserId()); $userInfo = $msz->usersCtx->getUserInfo($warnInfo->userId);
$userColour = $usersCtx->getUserColour($userInfo); $userColour = $msz->usersCtx->getUserColour($userInfo);
if(!$warnInfo->hasModId()) { if($warnInfo->modId === null) {
$modInfo = null; $modInfo = null;
$modColour = null; $modColour = null;
} else { } else {
$modInfo = $usersCtx->getUserInfo($warnInfo->getModId()); $modInfo = $msz->usersCtx->getUserInfo($warnInfo->modId);
$modColour = $usersCtx->getUserColour($modInfo); $modColour = $msz->usersCtx->getUserColour($modInfo);
} }
$warnList[] = [ $warnList[] = [

View file

@ -3,17 +3,11 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
$authInfo = $msz->getAuthInfo(); if(!$msz->authInfo->isLoggedIn)
if(!$authInfo->isLoggedIn())
Template::throwError(403); Template::throwError(403);
// TODO: restore forum-topics and forum-posts orderings // TODO: restore forum-topics and forum-posts orderings
$forumCtx = $msz->getForumContext();
$usersCtx = $msz->getUsersContext();
$users = $usersCtx->getUsers();
$roles = $usersCtx->getRoles();
$roleId = filter_has_var(INPUT_GET, 'r') ? (string)filter_input(INPUT_GET, 'r') : null; $roleId = filter_has_var(INPUT_GET, 'r') ? (string)filter_input(INPUT_GET, 'r') : null;
$orderBy = strtolower((string)filter_input(INPUT_GET, 'ss')); $orderBy = strtolower((string)filter_input(INPUT_GET, 'ss'));
$orderDir = strtolower((string)filter_input(INPUT_GET, 'sd')); $orderDir = strtolower((string)filter_input(INPUT_GET, 'sd'));
@ -66,7 +60,7 @@ if(empty($orderDir)) {
} }
if($roleId === null) { if($roleId === null) {
$roleInfo = $roles->getDefaultRole(); $roleInfo = $msz->usersCtx->roles->getDefaultRole();
} else { } else {
try { try {
$roleInfo = $roles->getRole($roleId); $roleInfo = $roles->getRole($roleId);
@ -75,14 +69,14 @@ if($roleId === null) {
} }
} }
$canManageUsers = $authInfo->getPerms('user')->check(Perm::U_USERS_MANAGE); $canManageUsers = $msz->authInfo->getPerms('user')->check(Perm::U_USERS_MANAGE);
$deleted = $canManageUsers ? null : false; $deleted = $canManageUsers ? null : false;
$rolesAll = $roles->getRoles(hidden: false); $rolesAll = $msz->usersCtx->roles->getRoles(hidden: false);
$pagination = new Pagination($users->countUsers(roleInfo: $roleInfo, deleted: $deleted), 15); $pagination = new Pagination($msz->usersCtx->users->countUsers(roleInfo: $roleInfo, deleted: $deleted), 15);
$userList = []; $userList = [];
$userInfos = $users->getUsers( $userInfos = $msz->usersCtx->users->getUsers(
roleInfo: $roleInfo, roleInfo: $roleInfo,
deleted: $deleted, deleted: $deleted,
orderBy: $orderBy, orderBy: $orderBy,
@ -93,9 +87,9 @@ $userInfos = $users->getUsers(
foreach($userInfos as $userInfo) foreach($userInfos as $userInfo)
$userList[] = [ $userList[] = [
'info' => $userInfo, 'info' => $userInfo,
'colour' => $usersCtx->getUserColour($userInfo), 'colour' => $msz->usersCtx->getUserColour($userInfo),
'ftopics' => $forumCtx->countTotalUserTopics($userInfo), 'ftopics' => $msz->forumCtx->countTotalUserTopics($userInfo),
'fposts' => $forumCtx->countTotalUserPosts($userInfo), 'fposts' => $msz->forumCtx->countTotalUserPosts($userInfo),
]; ];
if(empty($userList)) if(empty($userList))

View file

@ -14,21 +14,12 @@ $userId = !empty($_GET['u']) && is_string($_GET['u']) ? trim($_GET['u']) : 0;
$profileMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : ''; $profileMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
$isEditing = !empty($_GET['edit']) && is_string($_GET['edit']) ? (bool)$_GET['edit'] : !empty($_POST) && is_array($_POST); $isEditing = !empty($_GET['edit']) && is_string($_GET['edit']) ? (bool)$_GET['edit'] : !empty($_POST) && is_array($_POST);
$urls = $msz->getUrls(); $viewerInfo = $msz->authInfo->userInfo;
$usersCtx = $msz->getUsersContext();
$users = $usersCtx->getUsers();
$forumCtx = $msz->getForumContext();
$forumCategories = $forumCtx->getCategories();
$forumTopics = $forumCtx->getTopics();
$forumPosts = $forumCtx->getPosts();
$authInfo = $msz->getAuthInfo();
$viewerInfo = $authInfo->getUserInfo();
$viewingAsGuest = $viewerInfo === null; $viewingAsGuest = $viewerInfo === null;
$viewerId = $viewingAsGuest ? '0' : $viewerInfo->getId(); $viewerId = $viewingAsGuest ? '0' : $viewerInfo->getId();
try { try {
$userInfo = $usersCtx->getUserInfo($userId, 'profile'); $userInfo = $msz->usersCtx->getUserInfo($userId, 'profile');
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
http_response_code(404); http_response_code(404);
Template::render('profile.index', [ Template::render('profile.index', [
@ -39,7 +30,7 @@ try {
return; return;
} }
if($userInfo->isDeleted()) { if($userInfo->deleted) {
http_response_code(404); http_response_code(404);
Template::render('profile.index', [ Template::render('profile.index', [
'profile_is_guest' => $viewingAsGuest, 'profile_is_guest' => $viewingAsGuest,
@ -54,11 +45,11 @@ switch($profileMode) {
Template::throwError(404); Template::throwError(404);
case 'forum-topics': case 'forum-topics':
Tools::redirect($urls->format('search-query', ['query' => sprintf('type:forum:topic author:%s', $userInfo->getName()), 'section' => 'topics'])); Tools::redirect($msz->urls->format('search-query', ['query' => sprintf('type:forum:topic author:%s', $userInfo->name), 'section' => 'topics']));
return; return;
case 'forum-posts': case 'forum-posts':
Tools::redirect($urls->format('search-query', ['query' => sprintf('type:forum:post author:%s', $userInfo->getName()), 'section' => 'posts'])); Tools::redirect($msz->urls->format('search-query', ['query' => sprintf('type:forum:post author:%s', $userInfo->name), 'section' => 'posts']));
return; return;
case '': case '':
@ -67,18 +58,17 @@ switch($profileMode) {
$notices = []; $notices = [];
$userRank = $usersCtx->getUserRank($userInfo); $userRank = $msz->usersCtx->getUserRank($userInfo);
$viewerRank = $usersCtx->getUserRank($viewerInfo); $viewerRank = $msz->usersCtx->getUserRank($viewerInfo);
$viewerPermsGlobal = $authInfo->getPerms('global'); $viewerPermsGlobal = $msz->authInfo->getPerms('global');
$viewerPermsUser = $authInfo->getPerms('user'); $viewerPermsUser = $msz->authInfo->getPerms('user');
$activeBanInfo = $usersCtx->tryGetActiveBan($userInfo); $activeBanInfo = $msz->usersCtx->tryGetActiveBan($userInfo);
$isBanned = $activeBanInfo !== null; $isBanned = $activeBanInfo !== null;
$profileFields = $msz->getProfileFields();
$viewingOwnProfile = (string)$viewerId === $userInfo->getId(); $viewingOwnProfile = (string)$viewerId === $userInfo->getId();
$canManageWarnings = $viewerPermsUser->check(Perm::U_WARNINGS_MANAGE); $canManageWarnings = $viewerPermsUser->check(Perm::U_WARNINGS_MANAGE);
$canEdit = !$viewingAsGuest && ((!$isBanned && $viewingOwnProfile) || $viewerInfo->isSuperUser() || ( $canEdit = !$viewingAsGuest && ((!$isBanned && $viewingOwnProfile) || $viewerInfo->super || (
$viewerPermsUser->check(Perm::U_USERS_MANAGE) && ($viewingOwnProfile || $viewerRank > $userRank) $viewerPermsUser->check(Perm::U_USERS_MANAGE) && ($viewingOwnProfile || $viewerRank > $userRank)
)); ));
$avatarInfo = new UserAvatarAsset($userInfo); $avatarInfo = new UserAvatarAsset($userInfo);
@ -112,13 +102,13 @@ if($isEditing) {
if(!$perms->edit_profile) { if(!$perms->edit_profile) {
$notices[] = 'You\'re not allowed to edit your profile'; $notices[] = 'You\'re not allowed to edit your profile';
} else { } else {
$profileFieldInfos = iterator_to_array($profileFields->getFields()); $profileFieldInfos = iterator_to_array($msz->profileFields->getFields());
$profileFieldsSetInfos = []; $profileFieldsSetInfos = [];
$profileFieldsSetValues = []; $profileFieldsSetValues = [];
$profileFieldsRemove = []; $profileFieldsRemove = [];
foreach($profileFieldInfos as $fieldInfo) { foreach($profileFieldInfos as $fieldInfo) {
$fieldName = $fieldInfo->getName(); $fieldName = $fieldInfo->name;
$fieldValue = empty($profileFieldsSubmit[$fieldName]) ? '' : (string)filter_var($profileFieldsSubmit[$fieldName]); $fieldValue = empty($profileFieldsSubmit[$fieldName]) ? '' : (string)filter_var($profileFieldsSubmit[$fieldName]);
if(empty($profileFieldsSubmit[$fieldName])) { if(empty($profileFieldsSubmit[$fieldName])) {
@ -130,15 +120,15 @@ if($isEditing) {
$profileFieldsSetInfos[] = $fieldInfo; $profileFieldsSetInfos[] = $fieldInfo;
$profileFieldsSetValues[] = $fieldValue; $profileFieldsSetValues[] = $fieldValue;
} else } else
$notices[] = sprintf('%s isn\'t properly formatted.', $fieldInfo->getTitle()); $notices[] = sprintf('%s isn\'t properly formatted.', $fieldInfo->title);
unset($fieldName, $fieldValue, $fieldInfo); unset($fieldName, $fieldValue, $fieldInfo);
} }
if(!empty($profileFieldsRemove)) if(!empty($profileFieldsRemove))
$profileFields->removeFieldValues($userInfo, $profileFieldsRemove); $msz->profileFields->removeFieldValues($userInfo, $profileFieldsRemove);
if(!empty($profileFieldsSetInfos)) if(!empty($profileFieldsSetInfos))
$profileFields->setFieldValues($userInfo, $profileFieldsSetInfos, $profileFieldsSetValues); $msz->profileFields->setFieldValues($userInfo, $profileFieldsSetInfos, $profileFieldsSetValues);
} }
} }
@ -148,12 +138,12 @@ if($isEditing) {
} else { } else {
$aboutText = (string)($_POST['about']['text'] ?? ''); $aboutText = (string)($_POST['about']['text'] ?? '');
$aboutParse = (int)($_POST['about']['parser'] ?? Parser::PLAIN); $aboutParse = (int)($_POST['about']['parser'] ?? Parser::PLAIN);
$aboutValid = $users->validateProfileAbout($aboutParse, $aboutText); $aboutValid = $msz->usersCtx->users->validateProfileAbout($aboutParse, $aboutText);
if($aboutValid === '') if($aboutValid === '')
$users->updateUser($userInfo, aboutContent: $aboutText, aboutParser: $aboutParse); $msz->usersCtx->users->updateUser($userInfo, aboutBody: $aboutText, aboutBodyParser: $aboutParse);
else else
$notices[] = $users->validateProfileAboutText($aboutValid); $notices[] = $msz->usersCtx->users->validateProfileAboutText($aboutValid);
} }
} }
@ -163,12 +153,12 @@ if($isEditing) {
} else { } else {
$sigText = (string)($_POST['signature']['text'] ?? ''); $sigText = (string)($_POST['signature']['text'] ?? '');
$sigParse = (int)($_POST['signature']['parser'] ?? Parser::PLAIN); $sigParse = (int)($_POST['signature']['parser'] ?? Parser::PLAIN);
$sigValid = $users->validateForumSignature($sigParse, $sigText); $sigValid = $msz->usersCtx->users->validateForumSignature($sigParse, $sigText);
if($sigValid === '') if($sigValid === '')
$users->updateUser($userInfo, signatureContent: $sigText, signatureParser: $sigParse); $msz->usersCtx->users->updateUser($userInfo, signatureBody: $sigText, signatureBodyParser: $sigParse);
else else
$notices[] = $users->validateForumSignatureText($sigValid); $notices[] = $msz->usersCtx->users->validateForumSignatureText($sigValid);
} }
} }
@ -179,12 +169,12 @@ if($isEditing) {
$birthYear = (int)($_POST['birthdate']['year'] ?? 0); $birthYear = (int)($_POST['birthdate']['year'] ?? 0);
$birthMonth = (int)($_POST['birthdate']['month'] ?? 0); $birthMonth = (int)($_POST['birthdate']['month'] ?? 0);
$birthDay = (int)($_POST['birthdate']['day'] ?? 0); $birthDay = (int)($_POST['birthdate']['day'] ?? 0);
$birthValid = $users->validateBirthdate($birthYear, $birthMonth, $birthDay); $birthValid = $msz->usersCtx->users->validateBirthdate($birthYear, $birthMonth, $birthDay);
if($birthValid === '') if($birthValid === '')
$users->updateUser($userInfo, birthYear: $birthYear, birthMonth: $birthMonth, birthDay: $birthDay); $msz->usersCtx->users->updateUser($userInfo, birthYear: $birthYear, birthMonth: $birthMonth, birthDay: $birthDay);
else else
$notices[] = $users->validateBirthdateText($birthValid); $notices[] = $msz->usersCtx->users->validateBirthdateText($birthValid);
} }
} }
@ -281,7 +271,7 @@ if($isEditing) {
} }
} }
$users->updateUser($userInfo, backgroundSettings: $backgroundInfo->getSettings()); $msz->usersCtx->users->updateUser($userInfo, backgroundSettings: $backgroundInfo->getSettings());
} }
} }
@ -293,12 +283,12 @@ if($isEditing) {
// TODO: create user counters so these can be statically kept // TODO: create user counters so these can be statically kept
$profileStats = new stdClass; $profileStats = new stdClass;
$profileStats->forum_topic_count = $forumCtx->countTotalUserTopics($userInfo); $profileStats->forum_topic_count = $msz->forumCtx->countTotalUserTopics($userInfo);
$profileStats->forum_post_count = $forumCtx->countTotalUserPosts($userInfo); $profileStats->forum_post_count = $msz->forumCtx->countTotalUserPosts($userInfo);
$profileStats->comments_count = $msz->getComments()->countPosts(userInfo: $userInfo, deleted: false); $profileStats->comments_count = $msz->comments->countPosts(userInfo: $userInfo, deleted: false);
if(!$viewingAsGuest) { if(!$viewingAsGuest) {
Template::set('profile_warnings', iterator_to_array($usersCtx->getWarnings()->getWarningsWithDefaultBacklog($userInfo))); Template::set('profile_warnings', iterator_to_array($msz->usersCtx->warnings->getWarningsWithDefaultBacklog($userInfo)));
if((!$isBanned || $canEdit)) { if((!$isBanned || $canEdit)) {
$unranked = $cfg->getValues([ $unranked = $cfg->getValues([
@ -306,25 +296,25 @@ if(!$viewingAsGuest) {
'forum_leader.unranked.topic:a', 'forum_leader.unranked.topic:a',
]); ]);
$activeCategoryStats = $forumCategories->getMostActiveCategoryInfo( $activeCategoryStats = $msz->forumCtx->categories->getMostActiveCategoryInfo(
$userInfo, $userInfo,
$unranked['forum_leader.unranked.forum'], $unranked['forum_leader.unranked.forum'],
$unranked['forum_leader.unranked.topic'], $unranked['forum_leader.unranked.topic'],
deleted: false deleted: false
); );
$activeCategoryInfo = $activeCategoryStats->success ? $forumCategories->getCategory(categoryId: $activeCategoryStats->categoryId) : null; $activeCategoryInfo = $activeCategoryStats->success ? $msz->forumCtx->categories->getCategory(categoryId: $activeCategoryStats->categoryId) : null;
$activeTopicStats = $forumTopics->getMostActiveTopicInfo( $activeTopicStats = $msz->forumCtx->topics->getMostActiveTopicInfo(
$userInfo, $userInfo,
$unranked['forum_leader.unranked.forum'], $unranked['forum_leader.unranked.forum'],
$unranked['forum_leader.unranked.topic'], $unranked['forum_leader.unranked.topic'],
deleted: false deleted: false
); );
$activeTopicInfo = $activeTopicStats->success ? $forumTopics->getTopic(topicId: $activeTopicStats->topicId) : null; $activeTopicInfo = $activeTopicStats->success ? $msz->forumCtx->topics->getTopic(topicId: $activeTopicStats->topicId) : null;
$profileFieldValues = iterator_to_array($profileFields->getFieldValues($userInfo)); $profileFieldValues = iterator_to_array($msz->profileFields->getFieldValues($userInfo));
$profileFieldInfos = $profileFieldInfos ?? iterator_to_array($profileFields->getFields(fieldValueInfos: $isEditing ? null : $profileFieldValues)); $profileFieldInfos = $profileFieldInfos ?? iterator_to_array($msz->profileFields->getFields(fieldValueInfos: $isEditing ? null : $profileFieldValues));
$profileFieldFormats = iterator_to_array($profileFields->getFieldFormats(fieldValueInfos: $profileFieldValues)); $profileFieldFormats = iterator_to_array($msz->profileFields->getFieldFormats(fieldValueInfos: $profileFieldValues));
$profileFieldRawValues = []; $profileFieldRawValues = [];
$profileFieldLinkValues = []; $profileFieldLinkValues = [];
@ -335,24 +325,24 @@ if(!$viewingAsGuest) {
unset($fieldValue); unset($fieldValue);
foreach($profileFieldValues as $fieldValueTest) foreach($profileFieldValues as $fieldValueTest)
if($fieldValueTest->getFieldId() === $fieldInfo->getId()) { if($fieldValueTest->fieldId === $fieldInfo->id) {
$fieldValue = $fieldValueTest; $fieldValue = $fieldValueTest;
break; break;
} }
$fieldName = $fieldInfo->getName(); $fieldName = $fieldInfo->name;
if(isset($fieldValue)) { if(isset($fieldValue)) {
foreach($profileFieldFormats as $fieldFormatTest) foreach($profileFieldFormats as $fieldFormatTest)
if($fieldFormatTest->getId() === $fieldValue->getFormatId()) { if($fieldFormatTest->id === $fieldValue->formatId) {
$fieldFormat = $fieldFormatTest; $fieldFormat = $fieldFormatTest;
break; break;
} }
$profileFieldRawValues[$fieldName] = $fieldValue->getValue(); $profileFieldRawValues[$fieldName] = $fieldValue->value;
$profileFieldDisplayValues[$fieldName] = $fieldFormat->formatDisplay($fieldValue->getValue()); $profileFieldDisplayValues[$fieldName] = $fieldFormat->formatDisplay($fieldValue->value);
if($fieldFormat->hasLinkFormat()) if($fieldFormat->linkFormat !== null)
$profileFieldLinkValues[$fieldName] = $fieldFormat->formatLink($fieldValue->getValue()); $profileFieldLinkValues[$fieldName] = $fieldFormat->formatLink($fieldValue->value);
} }
} }
@ -372,7 +362,7 @@ if(!$viewingAsGuest) {
Template::render('profile.index', [ Template::render('profile.index', [
'profile_viewer' => $viewerInfo, 'profile_viewer' => $viewerInfo,
'profile_user' => $userInfo, 'profile_user' => $userInfo,
'profile_colour' => $usersCtx->getUserColour($userInfo), 'profile_colour' => $msz->usersCtx->getUserColour($userInfo),
'profile_stats' => $profileStats, 'profile_stats' => $profileStats,
'profile_mode' => $profileMode, 'profile_mode' => $profileMode,
'profile_notices' => $notices, 'profile_notices' => $notices,

View file

@ -6,8 +6,7 @@ use RuntimeException;
use Index\XArray; use Index\XArray;
use Misuzu\Comments\CommentsCategory; use Misuzu\Comments\CommentsCategory;
$authInfo = $msz->getAuthInfo(); if(!$msz->authInfo->isLoggedIn)
if(!$authInfo->isLoggedIn())
Template::throwError(403); Template::throwError(403);
$searchQuery = !empty($_GET['q']) && is_string($_GET['q']) ? $_GET['q'] : ''; $searchQuery = !empty($_GET['q']) && is_string($_GET['q']) ? $_GET['q'] : '';
@ -24,7 +23,7 @@ Template::addFunction('search_merge_query', function($attrs) use (&$searchQueryE
if(!empty($attrs['author'])) if(!empty($attrs['author']))
$existing[] = 'author:' . $attrs['author']; $existing[] = 'author:' . $attrs['author'];
elseif(!empty($searchQueryEvaluated['author'])) elseif(!empty($searchQueryEvaluated['author']))
$existing[] = 'author:' . $searchQueryEvaluated['author']->getName(); $existing[] = 'author:' . $searchQueryEvaluated['author']->name;
if(!empty($attrs['after'])) if(!empty($attrs['after']))
$existing[] = 'after:' . $attrs['after']; $existing[] = 'after:' . $attrs['after'];
@ -39,15 +38,6 @@ Template::addFunction('search_merge_query', function($attrs) use (&$searchQueryE
return implode(' ', $existing); return implode(' ', $existing);
}); });
if(!empty($searchQuery)) { if(!empty($searchQuery)) {
$usersCtx = $msz->getUsersContext();
$users = $usersCtx->getUsers();
$forumCtx = $msz->getForumContext();
$forumCategories = $forumCtx->getCategories();
$forumTopics = $forumCtx->getTopics();
$forumPosts = $forumCtx->getPosts();
$news = $msz->getNews();
$comments = $msz->getComments();
$searchQueryAttributes = ['type', 'author', 'after']; $searchQueryAttributes = ['type', 'author', 'after'];
$searchQueryParts = explode(' ', $searchQuery); $searchQueryParts = explode(' ', $searchQuery);
foreach($searchQueryParts as $queryPart) { foreach($searchQueryParts as $queryPart) {
@ -72,60 +62,60 @@ if(!empty($searchQuery)) {
if(!empty($searchQueryEvaluated['author'])) if(!empty($searchQueryEvaluated['author']))
try { try {
$searchQueryEvaluated['author'] = $usersCtx->getUserInfo($searchQueryEvaluated['author'], 'search'); $searchQueryEvaluated['author'] = $msz->usersCtx->getUserInfo($searchQueryEvaluated['author'], 'search');
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
unset($searchQueryEvaluated['author']); unset($searchQueryEvaluated['author']);
} }
if(empty($searchQueryEvaluated['type']) || str_starts_with($searchQueryEvaluated['type'], 'forum')) { if(empty($searchQueryEvaluated['type']) || str_starts_with($searchQueryEvaluated['type'], 'forum')) {
$currentUser = $authInfo->getUserInfo(); $currentUser = $msz->authInfo->userInfo;
$currentUserId = $currentUser === null ? 0 : (int)$currentUser->getId(); $currentUserId = $currentUser === null ? 0 : (int)$currentUser->getId();
$forumCategoryIds = XArray::where( $forumCategoryIds = XArray::where(
$forumCategories->getCategories(hidden: false), $msz->forumCtx->categories->getCategories(hidden: false),
fn($categoryInfo) => $categoryInfo->mayHaveTopics() && $authInfo->getPerms('forum', $categoryInfo)->check(Perm::F_CATEGORY_VIEW) fn($categoryInfo) => $categoryInfo->mayHaveTopics && $msz->authInfo->getPerms('forum', $categoryInfo)->check(Perm::F_CATEGORY_VIEW)
); );
$forumTopicInfos = $forumTopics->getTopics(categoryInfo: $forumCategoryIds, deleted: false, searchQuery: $searchQueryEvaluated); $forumTopicInfos = $msz->forumCtx->topics->getTopics(categoryInfo: $forumCategoryIds, deleted: false, searchQuery: $searchQueryEvaluated);
$ftopics = []; $ftopics = [];
foreach($forumTopicInfos as $topicInfo) { foreach($forumTopicInfos as $topicInfo) {
$ftopics[] = $topic = new stdClass; $ftopics[] = $topic = new stdClass;
$topic->info = $topicInfo; $topic->info = $topicInfo;
$topic->unread = $forumTopics->checkTopicUnread($topicInfo, $currentUser); $topic->unread = $msz->forumCtx->topics->checkTopicUnread($topicInfo, $currentUser);
$topic->participated = $forumTopics->checkTopicParticipated($topicInfo, $currentUser); $topic->participated = $msz->forumCtx->topics->checkTopicParticipated($topicInfo, $currentUser);
$topic->lastPost = new stdClass; $topic->lastPost = new stdClass;
if($topicInfo->hasUserId()) { if($topicInfo->userId !== null) {
$topic->user = $usersCtx->getUserInfo($topicInfo->getUserId(), 'id'); $topic->user = $msz->usersCtx->getUserInfo($topicInfo->userId, 'id');
$topic->colour = $usersCtx->getUserColour($topic->user); $topic->colour = $msz->usersCtx->getUserColour($topic->user);
} }
try { try {
$topic->lastPost->info = $lastPostInfo = $forumPosts->getPost( $topic->lastPost->info = $lastPostInfo = $msz->forumCtx->posts->getPost(
topicInfo: $topicInfo, topicInfo: $topicInfo,
getLast: true, getLast: true,
deleted: $topicInfo->isDeleted() ? null : false, deleted: $topicInfo->deleted ? null : false,
); );
if($lastPostInfo->hasUserId()) { if($lastPostInfo->userId !== null) {
$topic->lastPost->user = $usersCtx->getUserInfo($lastPostInfo->getUserId(), 'id'); $topic->lastPost->user = $msz->usersCtx->getUserInfo($lastPostInfo->userId, 'id');
$topic->lastPost->colour = $usersCtx->getUserColour($topic->lastPost->user); $topic->lastPost->colour = $msz->usersCtx->getUserColour($topic->lastPost->user);
} }
} catch(RuntimeException $ex) {} } catch(RuntimeException $ex) {}
} }
$forumPostInfos = $forumPosts->getPosts(categoryInfo: $forumCategoryIds, searchQuery: $searchQueryEvaluated); $forumPostInfos = $msz->forumCtx->posts->getPosts(categoryInfo: $forumCategoryIds, searchQuery: $searchQueryEvaluated);
$fposts = []; $fposts = [];
foreach($forumPostInfos as $postInfo) { foreach($forumPostInfos as $postInfo) {
$fposts[] = $post = new stdClass; $fposts[] = $post = new stdClass;
$post->info = $postInfo; $post->info = $postInfo;
if($postInfo->hasUserId()) { if($postInfo->userId !== null) {
$post->user = $usersCtx->getUserInfo($postInfo->getUserId(), 'id'); $post->user = $msz->usersCtx->getUserInfo($postInfo->userId, 'id');
$post->colour = $usersCtx->getUserColour($post->user); $post->colour = $msz->usersCtx->getUserColour($post->user);
$post->postsCount = $forumCtx->countTotalUserPosts($post->user); $post->postsCount = $msz->forumCtx->countTotalUserPosts($post->user);
} }
// can't be bothered sorry // can't be bothered sorry
@ -135,22 +125,21 @@ if(!empty($searchQuery)) {
} }
$newsPosts = []; $newsPosts = [];
$newsPostInfos = empty($searchQueryEvaluated['type']) || $searchQueryEvaluated['type'] === 'news' ? $news->getPosts(searchQuery: $searchQuery) : []; $newsPostInfos = empty($searchQueryEvaluated['type']) || $searchQueryEvaluated['type'] === 'news' ? $msz->news->getPosts(searchQuery: $searchQuery) : [];
$newsCategoryInfos = []; $newsCategoryInfos = [];
foreach($newsPostInfos as $postInfo) { foreach($newsPostInfos as $postInfo) {
$userId = $postInfo->getUserId(); $categoryId = $postInfo->categoryId;
$categoryId = $postInfo->getCategoryId(); $userInfo = $postInfo->userId !== null ? $msz->usersCtx->getUserInfo($postInfo->userId) : null;
$userInfo = $postInfo->hasUserId() ? $usersCtx->getUserInfo($postInfo->getUserId()) : null; $userColour = $msz->usersCtx->getUserColour($userInfo);
$userColour = $usersCtx->getUserColour($userInfo);
if(array_key_exists($categoryId, $newsCategoryInfos)) if(array_key_exists($categoryId, $newsCategoryInfos))
$categoryInfo = $newsCategoryInfos[$categoryId]; $categoryInfo = $newsCategoryInfos[$categoryId];
else else
$newsCategoryInfos[$categoryId] = $categoryInfo = $news->getCategory(postInfo: $postInfo); $newsCategoryInfos[$categoryId] = $categoryInfo = $msz->news->getCategory(postInfo: $postInfo);
$commentsCount = $postInfo->hasCommentsCategoryId() $commentsCount = $postInfo->commentsSectionId !== null
? $comments->countPosts(categoryInfo: $postInfo->getCommentsCategoryId(), deleted: false) : 0; ? $msz->comments->countPosts(categoryInfo: $postInfo->commentsSectionId, deleted: false) : 0;
$newsPosts[] = [ $newsPosts[] = [
'post' => $postInfo, 'post' => $postInfo,
@ -162,14 +151,14 @@ if(!empty($searchQuery)) {
} }
$members = []; $members = [];
$memberInfos = $users->getUsers(searchQuery: $searchQueryEvaluated); $memberInfos = $msz->usersCtx->users->getUsers(searchQuery: $searchQueryEvaluated);
foreach($memberInfos as $memberInfo) { foreach($memberInfos as $memberInfo) {
$members[] = $member = new stdClass; $members[] = $member = new stdClass;
$member->info = $memberInfo; $member->info = $memberInfo;
$member->colour = $usersCtx->getUserColour($memberInfo); $member->colour = $msz->usersCtx->getUserColour($memberInfo);
$member->ftopics = $forumCtx->countTotalUserTopics($memberInfo); $member->ftopics = $msz->forumCtx->countTotalUserTopics($memberInfo);
$member->fposts = $forumCtx->countTotalUserPosts($memberInfo); $member->fposts = $msz->forumCtx->countTotalUserPosts($memberInfo);
} }
} }

View file

@ -6,39 +6,35 @@ use Misuzu\Users\User;
use chillerlan\QRCode\QRCode; use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions; use chillerlan\QRCode\QROptions;
$authInfo = $msz->getAuthInfo(); if(!$msz->authInfo->isLoggedIn)
if(!$authInfo->isLoggedIn())
Template::throwError(401); Template::throwError(401);
$errors = []; $errors = [];
$usersCtx = $msz->getUsersContext(); $userInfo = $msz->authInfo->userInfo;
$users = $usersCtx->getUsers(); $isRestricted = $msz->usersCtx->hasActiveBan($userInfo);
$roles = $usersCtx->getRoles();
$userInfo = $authInfo->getUserInfo();
$isRestricted = $usersCtx->hasActiveBan($userInfo);
$isVerifiedRequest = CSRF::validateRequest(); $isVerifiedRequest = CSRF::validateRequest();
if(!$isRestricted && $isVerifiedRequest && !empty($_POST['role'])) { if(!$isRestricted && $isVerifiedRequest && !empty($_POST['role'])) {
try { try {
$roleInfo = $roles->getRole(($_POST['role']['id'] ?? 0)); $roleInfo = $msz->usersCtx->roles->getRole(($_POST['role']['id'] ?? 0));
} catch(RuntimeException $ex) {} } catch(RuntimeException $ex) {}
if(empty($roleInfo) || !$users->hasRole($userInfo, $roleInfo)) if(empty($roleInfo) || !$msz->usersCtx->users->hasRole($userInfo, $roleInfo))
$errors[] = "You're trying to modify a role that hasn't been assigned to you."; $errors[] = "You're trying to modify a role that hasn't been assigned to you.";
else { else {
switch($_POST['role']['mode'] ?? '') { switch($_POST['role']['mode'] ?? '') {
case 'display': case 'display':
$users->updateUser( $msz->usersCtx->users->updateUser(
$userInfo, $userInfo,
displayRoleInfo: $roleInfo displayRoleInfo: $roleInfo
); );
break; break;
case 'leave': case 'leave':
if($roleInfo->isLeavable()) { if($roleInfo->leavable) {
$users->removeRoles($userInfo, $roleInfo); $msz->usersCtx->users->removeRoles($userInfo, $roleInfo);
$msz->getPerms()->precalculatePermissions( $msz->perms->precalculatePermissions(
$msz->getForumContext()->getCategories(), $msz->forumCtx->categories,
[$userInfo->getId()] [$userInfo->getId()]
); );
} else } else
@ -48,17 +44,17 @@ if(!$isRestricted && $isVerifiedRequest && !empty($_POST['role'])) {
} }
} }
if($isVerifiedRequest && isset($_POST['tfa']['enable']) && $userInfo->hasTOTPKey() !== (bool)$_POST['tfa']['enable']) { if($isVerifiedRequest && isset($_POST['tfa']['enable']) && $userInfo->hasTOTP !== (bool)$_POST['tfa']['enable']) {
$totpKey = ''; $totpKey = '';
if((bool)$_POST['tfa']['enable']) { if((bool)$_POST['tfa']['enable']) {
$totpKey = TOTPGenerator::generateKey(); $totpKey = TOTPGenerator::generateKey();
$totpIssuer = $msz->getSiteInfo()->getName(); $totpIssuer = $msz->siteInfo->name;
$totpQrcode = (new QRCode(new QROptions([ $totpQrcode = (new QRCode(new QROptions([
'version' => 5, 'version' => 5,
'outputType' => QRCode::OUTPUT_IMAGE_JPG, 'outputType' => QRCode::OUTPUT_IMAGE_JPG,
'eccLevel' => QRCode::ECC_L, 'eccLevel' => QRCode::ECC_L,
])))->render(sprintf('otpauth://totp/%s:%s?%s', $totpIssuer, $userInfo->getName(), http_build_query([ ])))->render(sprintf('otpauth://totp/%s:%s?%s', $totpIssuer, $userInfo->name, http_build_query([
'secret' => $totpKey, 'secret' => $totpKey,
'issuer' => $totpIssuer, 'issuer' => $totpIssuer,
]))); ])));
@ -69,7 +65,7 @@ if($isVerifiedRequest && isset($_POST['tfa']['enable']) && $userInfo->hasTOTPKey
]); ]);
} }
$users->updateUser(userInfo: $userInfo, totpKey: $totpKey); $msz->usersCtx->users->updateUser(userInfo: $userInfo, totpKey: $totpKey);
} }
if($isVerifiedRequest && !empty($_POST['current_password'])) { if($isVerifiedRequest && !empty($_POST['current_password'])) {
@ -80,15 +76,15 @@ if($isVerifiedRequest && !empty($_POST['current_password'])) {
if(!empty($_POST['email']['new'])) { if(!empty($_POST['email']['new'])) {
if(empty($_POST['email']['confirm']) || $_POST['email']['new'] !== $_POST['email']['confirm']) { if(empty($_POST['email']['confirm']) || $_POST['email']['new'] !== $_POST['email']['confirm']) {
$errors[] = 'The addresses you entered did not match each other.'; $errors[] = 'The addresses you entered did not match each other.';
} elseif($userInfo->getEMailAddress() === mb_strtolower($_POST['email']['confirm'])) { } elseif($userInfo->emailAddress === mb_strtolower($_POST['email']['confirm'])) {
$errors[] = 'This is already your e-mail address!'; $errors[] = 'This is already your e-mail address!';
} else { } else {
$checkMail = $users->validateEMailAddress($_POST['email']['new']); $checkMail = $msz->usersCtx->users->validateEMailAddress($_POST['email']['new']);
if($checkMail !== '') { if($checkMail !== '') {
$errors[] = $users->validateEMailAddressText($checkMail); $errors[] = $msz->usersCtx->users->validateEMailAddressText($checkMail);
} else { } else {
$users->updateUser(userInfo: $userInfo, emailAddr: $_POST['email']['new']); $msz->usersCtx->users->updateUser(userInfo: $userInfo, emailAddr: $_POST['email']['new']);
$msz->createAuditLog('PERSONAL_EMAIL_CHANGE', [$_POST['email']['new']]); $msz->createAuditLog('PERSONAL_EMAIL_CHANGE', [$_POST['email']['new']]);
} }
} }
@ -99,12 +95,12 @@ if($isVerifiedRequest && !empty($_POST['current_password'])) {
if(empty($_POST['password']['confirm']) || $_POST['password']['new'] !== $_POST['password']['confirm']) { if(empty($_POST['password']['confirm']) || $_POST['password']['new'] !== $_POST['password']['confirm']) {
$errors[] = 'The new passwords you entered did not match each other.'; $errors[] = 'The new passwords you entered did not match each other.';
} else { } else {
$checkPassword = $users->validatePassword($_POST['password']['new']); $checkPassword = $msz->usersCtx->users->validatePassword($_POST['password']['new']);
if($checkPassword !== '') { if($checkPassword !== '') {
$errors[] = $users->validatePasswordText($checkPassword); $errors[] = $msz->usersCtx->users->validatePasswordText($checkPassword);
} else { } else {
$users->updateUser(userInfo: $userInfo, password: $_POST['password']['new']); $msz->usersCtx->users->updateUser(userInfo: $userInfo, password: $_POST['password']['new']);
$msz->createAuditLog('PERSONAL_PASSWORD_CHANGE'); $msz->createAuditLog('PERSONAL_PASSWORD_CHANGE');
} }
} }
@ -114,9 +110,9 @@ if($isVerifiedRequest && !empty($_POST['current_password'])) {
// reload $userInfo object // reload $userInfo object
if($_SERVER['REQUEST_METHOD'] === 'POST' && $isVerifiedRequest) if($_SERVER['REQUEST_METHOD'] === 'POST' && $isVerifiedRequest)
$userInfo = $users->getUser($userInfo->getId(), 'id'); $userInfo = $msz->usersCtx->users->getUser($userInfo->getId(), 'id');
$userRoles = iterator_to_array($roles->getRoles(userInfo: $userInfo)); $userRoles = iterator_to_array($msz->usersCtx->roles->getRoles(userInfo: $userInfo));
Template::render('settings.account', [ Template::render('settings.account', [
'errors' => $errors, 'errors' => $errors,

View file

@ -5,11 +5,10 @@ use ZipArchive;
use Index\XString; use Index\XString;
use Misuzu\Users\UserInfo; use Misuzu\Users\UserInfo;
$authInfo = $msz->getAuthInfo(); if(!$msz->authInfo->isLoggedIn)
if(!$authInfo->isLoggedIn())
Template::throwError(401); Template::throwError(401);
$dbConn = $msz->getDbConn(); $dbConn = $msz->dbConn;
function db_to_zip(ZipArchive $archive, UserInfo $userInfo, string $baseName, array $fieldInfos, string $userIdField = 'user_id'): string { function db_to_zip(ZipArchive $archive, UserInfo $userInfo, string $baseName, array $fieldInfos, string $userIdField = 'user_id'): string {
global $dbConn; global $dbConn;
@ -98,7 +97,7 @@ function db_to_zip(ZipArchive $archive, UserInfo $userInfo, string $baseName, ar
} }
$errors = []; $errors = [];
$userInfo = $authInfo->getUserInfo(); $userInfo = $msz->authInfo->userInfo;
if(isset($_POST['action']) && is_string($_POST['action'])) { if(isset($_POST['action']) && is_string($_POST['action'])) {
if(isset($_POST['password']) && is_string($_POST['password']) if(isset($_POST['password']) && is_string($_POST['password'])

View file

@ -3,20 +3,15 @@ namespace Misuzu;
use Misuzu\Pagination; use Misuzu\Pagination;
$authInfo = $msz->getAuthInfo(); $currentUser = $msz->authInfo->userInfo;
$currentUser = $authInfo->getUserInfo();
if($currentUser === null) if($currentUser === null)
Template::throwError(401); Template::throwError(401);
$authCtx = $msz->getAuthContext(); $loginHistoryPagination = new Pagination($msz->authCtx->loginAttempts->countAttempts(userInfo: $currentUser), 5, 'hp');
$loginAttempts = $authCtx->getLoginAttempts(); $accountLogPagination = new Pagination($msz->auditLog->countLogs(userInfo: $currentUser), 10, 'ap');
$auditLog = $msz->getAuditLog();
$loginHistoryPagination = new Pagination($loginAttempts->countAttempts(userInfo: $currentUser), 5, 'hp'); $loginHistory = iterator_to_array($msz->authCtx->loginAttempts->getAttempts(userInfo: $currentUser, pagination: $loginHistoryPagination));
$accountLogPagination = new Pagination($auditLog->countLogs(userInfo: $currentUser), 10, 'ap'); $auditLogs = iterator_to_array($msz->auditLog->getLogs(userInfo: $currentUser, pagination: $accountLogPagination));
$loginHistory = iterator_to_array($loginAttempts->getAttempts(userInfo: $currentUser, pagination: $loginHistoryPagination));
$auditLogs = iterator_to_array($auditLog->getLogs(userInfo: $currentUser, pagination: $accountLogPagination));
Template::render('settings.logs', [ Template::render('settings.logs', [
'login_history_list' => $loginHistory, 'login_history_list' => $loginHistory,

View file

@ -3,15 +3,12 @@ namespace Misuzu;
use RuntimeException; use RuntimeException;
$authInfo = $msz->getAuthInfo(); if(!$msz->authInfo->isLoggedIn)
if(!$authInfo->isLoggedIn())
Template::throwError(401); Template::throwError(401);
$errors = []; $errors = [];
$authCtx = $msz->getAuthContext(); $currentUser = $msz->authInfo->userInfo;
$sessions = $authCtx->getSessions(); $activeSessionId = $msz->authInfo->sessionId;
$currentUser = $authInfo->getUserInfo();
$activeSessionId = $authInfo->getSessionId();
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) { while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$sessionId = (string)filter_input(INPUT_POST, 'session'); $sessionId = (string)filter_input(INPUT_POST, 'session');
@ -19,38 +16,38 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
if($sessionId === 'all') { if($sessionId === 'all') {
$activeSessionKilled = true; $activeSessionKilled = true;
$sessions->deleteSessions(userInfos: $currentUser); $msz->authCtx->sessions->deleteSessions(userInfos: $currentUser);
$msz->createAuditLog('PERSONAL_SESSION_DESTROY_ALL'); $msz->createAuditLog('PERSONAL_SESSION_DESTROY_ALL');
} else { } else {
try { try {
$sessionInfo = $sessions->getSession(sessionId: $sessionId); $sessionInfo = $msz->authCtx->sessions->getSession(sessionId: $sessionId);
} catch(RuntimeException $ex) {} } catch(RuntimeException $ex) {}
if(empty($sessionInfo) || $sessionInfo->getUserId() !== $currentUser->getId()) { if(empty($sessionInfo) || $sessionInfo->userId !== $currentUser->getId()) {
$errors[] = "That session doesn't exist."; $errors[] = "That session doesn't exist.";
break; break;
} }
$activeSessionKilled = $sessionInfo->getId() === $activeSessionId; $activeSessionKilled = $sessionInfo->id === $activeSessionId;
$sessions->deleteSessions(sessionInfos: $sessionInfo); $msz->authCtx->sessions->deleteSessions(sessionInfos: $sessionInfo);
$msz->createAuditLog('PERSONAL_SESSION_DESTROY', [$sessionInfo->getId()]); $msz->createAuditLog('PERSONAL_SESSION_DESTROY', [$sessionInfo->id]);
} }
if($activeSessionKilled) { if($activeSessionKilled) {
Tools::redirect($msz->getUrls()->format('index')); Tools::redirect($msz->urls->format('index'));
return; return;
} else break; } else break;
} }
$pagination = new Pagination($sessions->countSessions(userInfo: $currentUser), 10); $pagination = new Pagination($msz->authCtx->sessions->countSessions(userInfo: $currentUser), 10);
$sessionList = []; $sessionList = [];
$sessionInfos = $sessions->getSessions(userInfo: $currentUser, pagination: $pagination); $sessionInfos = $msz->authCtx->sessions->getSessions(userInfo: $currentUser, pagination: $pagination);
foreach($sessionInfos as $sessionInfo) foreach($sessionInfos as $sessionInfo)
$sessionList[] = [ $sessionList[] = [
'info' => $sessionInfo, 'info' => $sessionInfo,
'active' => $sessionInfo->getId() === $activeSessionId, 'active' => $sessionInfo->id === $activeSessionId,
]; ];
Template::render('settings.sessions', [ Template::render('settings.sessions', [

View file

@ -38,7 +38,7 @@ if(file_exists(MSZ_ROOT . '/.migrating')) {
exit; exit;
} }
$tokenPacker = $msz->getAuthContext()->createAuthTokenPacker(); $tokenPacker = $msz->authCtx->createAuthTokenPacker();
if(filter_has_var(INPUT_COOKIE, 'msz_auth')) if(filter_has_var(INPUT_COOKIE, 'msz_auth'))
$tokenInfo = $tokenPacker->unpack(filter_input(INPUT_COOKIE, 'msz_auth')); $tokenInfo = $tokenPacker->unpack(filter_input(INPUT_COOKIE, 'msz_auth'));
@ -55,32 +55,30 @@ $userInfo = null;
$sessionInfo = null; $sessionInfo = null;
$userInfoReal = null; $userInfoReal = null;
if($tokenInfo->hasUserId() && $tokenInfo->hasSessionToken()) { if($tokenInfo->hasUserId && $tokenInfo->hasSessionToken) {
$users = $msz->getUsersContext()->getUsers();
$sessions = $msz->getAuthContext()->getSessions();
$tokenBuilder = new AuthTokenBuilder($tokenInfo); $tokenBuilder = new AuthTokenBuilder($tokenInfo);
try { try {
$sessionInfo = $sessions->getSession(sessionToken: $tokenInfo->getSessionToken()); $sessionInfo = $msz->authCtx->sessions->getSession(sessionToken: $tokenInfo->sessionToken);
if($sessionInfo->hasExpired()) { if($sessionInfo->expired) {
$tokenBuilder->removeUserId(); $tokenBuilder->removeUserId();
$tokenBuilder->removeSessionToken(); $tokenBuilder->removeSessionToken();
} elseif($sessionInfo->getUserId() === $tokenInfo->getUserId()) { } elseif($sessionInfo->userId === $tokenInfo->userId) {
$userInfo = $users->getUser($tokenInfo->getUserId(), 'id'); $userInfo = $msz->usersCtx->users->getUser($tokenInfo->userId, 'id');
if($userInfo->isDeleted()) { if($userInfo->deleted) {
$tokenBuilder->removeUserId(); $tokenBuilder->removeUserId();
$tokenBuilder->removeSessionToken(); $tokenBuilder->removeSessionToken();
} else { } else {
$users->recordUserActivity($userInfo, remoteAddr: $_SERVER['REMOTE_ADDR']); $msz->usersCtx->users->recordUserActivity($userInfo, remoteAddr: $_SERVER['REMOTE_ADDR']);
$sessions->recordSessionActivity(sessionInfo: $sessionInfo, remoteAddr: $_SERVER['REMOTE_ADDR']); $msz->authCtx->sessions->recordSessionActivity(sessionInfo: $sessionInfo, remoteAddr: $_SERVER['REMOTE_ADDR']);
if($sessionInfo->shouldBumpExpires()) if($sessionInfo->shouldBumpExpires)
$tokenBuilder->setEdited(); $tokenBuilder->setEdited();
if($tokenInfo->hasImpersonatedUserId()) { if($tokenInfo->hasImpersonatedUserId) {
$allowToImpersonate = $userInfo->isSuperUser(); $allowToImpersonate = $userInfo->super;
$impersonatedUserId = $tokenInfo->getImpersonatedUserId(); $impersonatedUserId = $tokenInfo->impersonatedUserId;
if(!$allowToImpersonate) { if(!$allowToImpersonate) {
$allowImpersonateUsers = $cfg->getArray(sprintf('impersonate.allow.u%s', $userInfo->getId())); $allowImpersonateUsers = $cfg->getArray(sprintf('impersonate.allow.u%s', $userInfo->getId()));
@ -91,7 +89,7 @@ if($tokenInfo->hasUserId() && $tokenInfo->hasSessionToken()) {
$userInfoReal = $userInfo; $userInfoReal = $userInfo;
try { try {
$userInfo = $users->getUser($impersonatedUserId, 'id'); $userInfo = $msz->usersCtx->users->getUser($impersonatedUserId, 'id');
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
$userInfo = $userInfoReal; $userInfo = $userInfoReal;
$userInfoReal = null; $userInfoReal = null;
@ -116,12 +114,11 @@ if($tokenInfo->hasUserId() && $tokenInfo->hasSessionToken()) {
} }
} }
$authInfo = $msz->getAuthInfo(); $msz->authInfo->setInfo($tokenInfo, $userInfo, $sessionInfo, $userInfoReal);
$authInfo->setInfo($tokenInfo, $userInfo, $sessionInfo, $userInfoReal);
CSRF::init( CSRF::init(
$cfg->getString('csrf.secret', 'soup'), $cfg->getString('csrf.secret', 'soup'),
($authInfo->isLoggedIn() ? $sessionInfo->getToken() : $_SERVER['REMOTE_ADDR']) ($msz->authInfo->isLoggedIn ? $sessionInfo->token : $_SERVER['REMOTE_ADDR'])
); );
// order for these two currently matters i think: it shouldn't. // order for these two currently matters i think: it shouldn't.

View file

@ -7,12 +7,12 @@ use Index\Db\DbResult;
class AuditLogInfo { class AuditLogInfo {
public function __construct( public function __construct(
private ?string $userId, public private(set) ?string $userId,
private string $action, public private(set) string $action,
private array $params, public private(set) array $params,
private int $created, public private(set) int $createdTime,
private string $address, public private(set) string $remoteAddress,
private string $country, public private(set) string $countryCode,
) {} ) {}
public static function fromResult(DbResult $result): AuditLogInfo { public static function fromResult(DbResult $result): AuditLogInfo {
@ -20,51 +20,25 @@ class AuditLogInfo {
userId: $result->getStringOrNull(0), userId: $result->getStringOrNull(0),
action: $result->getString(1), action: $result->getString(1),
params: json_decode($result->getString(2)), params: json_decode($result->getString(2)),
created: $result->getInteger(3), createdTime: $result->getInteger(3),
address: $result->isNull(4) ? '::1' : $result->getString(4), // apparently this being NULL is possible? remoteAddress: $result->isNull(4) ? '::1' : $result->getString(4), // apparently this being NULL is possible?
country: $result->getString(5), countryCode: $result->getString(5),
); );
} }
public function hasUserId(): bool { public CarbonImmutable $createdAt {
return $this->userId !== null; get => CarbonImmutable::createFromTimestampUTC($this->createdTime);
} }
public function getUserId(): ?string { public string $formatted {
return $this->userId; get {
} if(array_key_exists($this->action, self::FORMATS))
try {
return vsprintf(self::FORMATS[$this->action], $this->params);
} catch(ValueError $ex) {}
public function getAction(): string { return sprintf('%s(%s)', $this->action, implode(', ', $this->params));
return $this->action; }
}
public function getParams(): array {
return $this->params;
}
public function getCreatedTime(): int {
return $this->created;
}
public function getCreatedAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->created);
}
public function getRemoteAddress(): string {
return $this->address;
}
public function getCountryCode(): string {
return $this->country;
}
public function getFormatted(): string {
if(array_key_exists($this->action, self::FORMATS))
try {
return vsprintf(self::FORMATS[$this->action], $this->params);
} catch(ValueError $ex) {}
return sprintf('%s(%s)', $this->action, implode(', ', $this->params));
} }
public const FORMATS = [ public const FORMATS = [

View file

@ -5,10 +5,10 @@ use Index\Config\Config;
use Index\Db\DbConnection; use Index\Db\DbConnection;
class AuthContext { class AuthContext {
private Sessions $sessions; public private(set) Sessions $sessions;
private LoginAttempts $loginAttempts; public private(set) LoginAttempts $loginAttempts;
private RecoveryTokens $recoveryTokens; public private(set) RecoveryTokens $recoveryTokens;
private TwoFactorAuthSessions $tfaSessions; public private(set) TwoFactorAuthSessions $tfaSessions;
private Config $config; private Config $config;
@ -20,22 +20,6 @@ class AuthContext {
$this->tfaSessions = new TwoFactorAuthSessions($dbConn); $this->tfaSessions = new TwoFactorAuthSessions($dbConn);
} }
public function getSessions(): Sessions {
return $this->sessions;
}
public function getLoginAttempts(): LoginAttempts {
return $this->loginAttempts;
}
public function getRecoveryTokens(): RecoveryTokens {
return $this->recoveryTokens;
}
public function getTwoFactorAuthSessions(): TwoFactorAuthSessions {
return $this->tfaSessions;
}
public function createAuthTokenPacker(): AuthTokenPacker { public function createAuthTokenPacker(): AuthTokenPacker {
return new AuthTokenPacker($this->config->getString('secret', 'meow')); return new AuthTokenPacker($this->config->getString('secret', 'meow'));
} }

View file

@ -8,16 +8,16 @@ use Misuzu\Perms\Permissions;
use Misuzu\Users\UserInfo; use Misuzu\Users\UserInfo;
class AuthInfo { class AuthInfo {
private Permissions $permissions; public private(set) AuthTokenInfo $tokenInfo;
private AuthTokenInfo $tokenInfo; public private(set) ?UserInfo $userInfo;
private ?UserInfo $userInfo; public private(set) ?SessionInfo $sessionInfo;
private ?SessionInfo $sessionInfo; public private(set) ?UserInfo $realUserInfo;
private ?UserInfo $realUserInfo; public private(set) array $perms;
private array $perms;
public function __construct(Permissions $permissions) { public function __construct(
$this->permissions = $permissions; private Permissions $permissions
$this->setInfo(AuthTokenInfo::empty()); ) {
$this->removeInfo();
} }
public function setInfo( public function setInfo(
@ -37,40 +37,24 @@ class AuthInfo {
$this->setInfo(AuthTokenInfo::empty()); $this->setInfo(AuthTokenInfo::empty());
} }
public function getTokenInfo(): AuthTokenInfo { public bool $isLoggedIn {
return $this->tokenInfo; get => $this->userInfo !== null;
} }
public function isLoggedIn(): bool { public ?string $userId {
return $this->userInfo !== null; get => $this->userInfo?->getId();
} }
public function getUserId(): ?string { public ?string $sessionId {
return $this->userInfo?->getId(); get => $this->sessionInfo?->id;
} }
public function getUserInfo(): ?UserInfo { public bool $isImpersonating {
return $this->userInfo; get => $this->realUserInfo !== null;
} }
public function getSessionId(): ?string { public ?string $realUserId {
return $this->sessionInfo?->getId(); get => $this->realUserInfo?->getId();
}
public function getSessionInfo(): ?SessionInfo {
return $this->sessionInfo;
}
public function isImpersonating(): bool {
return $this->realUserInfo !== null;
}
public function getRealUserId(): ?string {
return $this->realUserInfo?->getId();
}
public function getRealUserInfo(): ?UserInfo {
return $this->realUserInfo;
} }
public function getPerms( public function getPerms(
@ -79,7 +63,7 @@ class AuthInfo {
): IPermissionResult { ): IPermissionResult {
$cacheKey = $category; $cacheKey = $category;
if($forumCategoryInfo !== null) if($forumCategoryInfo !== null)
$cacheKey .= '|' . ($forumCategoryInfo instanceof ForumCategoryInfo ? $forumCategoryInfo->getId() : $forumCategoryInfo); $cacheKey .= '|' . ($forumCategoryInfo instanceof ForumCategoryInfo ? $forumCategoryInfo->id : $forumCategoryInfo);
if(array_key_exists($cacheKey, $this->perms)) if(array_key_exists($cacheKey, $this->perms))
return $this->perms[$cacheKey]; return $this->perms[$cacheKey];

View file

@ -16,7 +16,7 @@ final class AuthRpcHandler implements RpcHandler {
) {} ) {}
private function canImpersonateUserId(UserInfo $impersonator, string $targetId): bool { private function canImpersonateUserId(UserInfo $impersonator, string $targetId): bool {
if($impersonator->isSuperUser()) if($impersonator->super)
return true; return true;
$whitelist = $this->impersonateConfig->getArray(sprintf('allow.u%s', $impersonator->getId())); $whitelist = $this->impersonateConfig->getArray(sprintf('allow.u%s', $impersonator->getId()));
@ -26,30 +26,28 @@ final class AuthRpcHandler implements RpcHandler {
#[RpcAction('misuzu:auth:attemptMisuzuAuth')] #[RpcAction('misuzu:auth:attemptMisuzuAuth')]
public function procAttemptMisuzuAuth(string $remoteAddr, string $token): array { public function procAttemptMisuzuAuth(string $remoteAddr, string $token): array {
$tokenInfo = $this->authCtx->createAuthTokenPacker()->unpack($token); $tokenInfo = $this->authCtx->createAuthTokenPacker()->unpack($token);
if(!$tokenInfo->isEmpty()) if(!$tokenInfo->isEmpty)
$token = $tokenInfo->getSessionToken(); $token = $tokenInfo->sessionToken;
$sessions = $this->authCtx->getSessions();
try { try {
$sessionInfo = $sessions->getSession(sessionToken: $token); $sessionInfo = $this->authCtx->sessions->getSession(sessionToken: $token);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
return ['method' => 'misuzu', 'error' => 'token']; return ['method' => 'misuzu', 'error' => 'token'];
} }
if($sessionInfo->hasExpired()) { if($sessionInfo->expired) {
$sessions->deleteSessions(sessionInfos: $sessionInfo); $this->authCtx->sessions->deleteSessions(sessionInfos: $sessionInfo);
return ['method' => 'misuzu', 'error' => 'expired']; return ['method' => 'misuzu', 'error' => 'expired'];
} }
$sessions->recordSessionActivity(sessionInfo: $sessionInfo, remoteAddr: $remoteAddr); $this->authCtx->sessions->recordSessionActivity(sessionInfo: $sessionInfo, remoteAddr: $remoteAddr);
$users = $this->usersCtx->getUsers(); $userInfo = $this->usersCtx->users->getUser($sessionInfo->userId, 'id');
$userInfo = $users->getUser($sessionInfo->getUserId(), 'id'); if($tokenInfo->hasImpersonatedUserId && $this->canImpersonateUserId($userInfo, $tokenInfo->impersonatedUserId)) {
if($tokenInfo->hasImpersonatedUserId() && $this->canImpersonateUserId($userInfo, $tokenInfo->getImpersonatedUserId())) {
$userInfoReal = $userInfo; $userInfoReal = $userInfo;
try { try {
$userInfo = $users->getUser($tokenInfo->getImpersonatedUserId(), 'id'); $userInfo = $this->usersCtx->users->getUser($tokenInfo->impersonatedUserId, 'id');
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
$userInfo = $userInfoReal; $userInfo = $userInfoReal;
} }
@ -59,7 +57,7 @@ final class AuthRpcHandler implements RpcHandler {
'method' => 'misuzu', 'method' => 'misuzu',
'type' => 'user', 'type' => 'user',
'user' => $userInfo->getId(), 'user' => $userInfo->getId(),
'expires' => $sessionInfo->getExpiresTime(), 'expires' => $sessionInfo->expiresTime,
]; ];
} }
} }

View file

@ -46,7 +46,7 @@ class AuthTokenBuilder {
public function setSessionToken(SessionInfo|string $sessionKey): void { public function setSessionToken(SessionInfo|string $sessionKey): void {
if($sessionKey instanceof SessionInfo) if($sessionKey instanceof SessionInfo)
$sessionKey = $sessionKey->getToken(); $sessionKey = $sessionKey->token;
$this->setProperty(AuthTokenInfo::SESSION_TOKEN, $sessionKey); $this->setProperty(AuthTokenInfo::SESSION_TOKEN, $sessionKey);
} }

View file

@ -10,18 +10,10 @@ class AuthTokenInfo {
public const IMPERSONATED_USER_ID = 'i'; // Impersonated user ID public const IMPERSONATED_USER_ID = 'i'; // Impersonated user ID
public function __construct( public function __construct(
private int $timestamp = 0, public private(set) int $timestamp = 0,
private array $props = [] private array $props = []
) {} ) {}
public function isEmpty(): bool {
return $this->timestamp === 0 && empty($this->props);
}
public function getTimestamp(): int {
return $this->timestamp;
}
public function getProperties(): array { public function getProperties(): array {
return $this->props; return $this->props;
} }
@ -38,35 +30,41 @@ class AuthTokenInfo {
return $this->props[$name] ?? ''; return $this->props[$name] ?? '';
} }
public function hasUserId(): bool { public bool $isEmpty {
return $this->hasProperty(self::USER_ID); get => $this->timestamp === 0 && empty($this->props);
} }
public function getUserId(): string { public bool $hasUserId {
return $this->getProperty(self::USER_ID); get => $this->hasProperty(self::USER_ID);
} }
public function hasSessionToken(): bool { public string $userId {
return $this->hasProperty(self::SESSION_TOKEN) get => $this->getProperty(self::USER_ID);
}
public bool $hasSessionToken {
get => $this->hasProperty(self::SESSION_TOKEN)
|| $this->hasProperty(self::SESSION_TOKEN_HEX); || $this->hasProperty(self::SESSION_TOKEN_HEX);
} }
public function getSessionToken(): string { public string $sessionToken {
if($this->hasProperty(self::SESSION_TOKEN)) get {
return $this->getProperty(self::SESSION_TOKEN); if($this->hasProperty(self::SESSION_TOKEN))
return $this->getProperty(self::SESSION_TOKEN);
if($this->hasProperty(self::SESSION_TOKEN_HEX)) if($this->hasProperty(self::SESSION_TOKEN_HEX))
return bin2hex($this->getProperty(self::SESSION_TOKEN_HEX)); return bin2hex($this->getProperty(self::SESSION_TOKEN_HEX));
return ''; return '';
}
} }
public function hasImpersonatedUserId(): bool { public bool $hasImpersonatedUserId {
return $this->hasProperty(self::IMPERSONATED_USER_ID); get => $this->hasProperty(self::IMPERSONATED_USER_ID);
} }
public function getImpersonatedUserId(): string { public string $impersonatedUserId {
return $this->getProperty(self::IMPERSONATED_USER_ID); get => $this->getProperty(self::IMPERSONATED_USER_ID);
} }
private static AuthTokenInfo $empty; private static AuthTokenInfo $empty;

View file

@ -11,7 +11,7 @@ class AuthTokenPacker {
public function pack(AuthTokenBuilder|AuthTokenInfo $tokenInfo): string { public function pack(AuthTokenBuilder|AuthTokenInfo $tokenInfo): string {
$props = $tokenInfo->getProperties(); $props = $tokenInfo->getProperties();
$timestamp = $tokenInfo instanceof AuthTokenInfo ? $tokenInfo->getTimestamp() : time(); $timestamp = $tokenInfo instanceof AuthTokenInfo ? $tokenInfo->timestamp : time();
$data = ''; $data = '';

View file

@ -7,64 +7,32 @@ use Index\Db\DbResult;
class LoginAttemptInfo { class LoginAttemptInfo {
public function __construct( public function __construct(
private ?string $userId, public private(set) ?string $userId,
private bool $success, public private(set) bool $success,
private string $remoteAddr, public private(set) string $remoteAddress,
private string $countryCode, public private(set) string $countryCode,
private int $created, public private(set) int $createdTime,
private string $userAgent, public private(set) string $userAgentString,
private string $clientInfo, public private(set) string $clientInfoJson,
) {} ) {}
public static function fromResult(DbResult $result): LoginAttemptInfo { public static function fromResult(DbResult $result): LoginAttemptInfo {
return new LoginAttemptInfo( return new LoginAttemptInfo(
userId: $result->getStringOrNull(0), userId: $result->getStringOrNull(0),
success: $result->getBoolean(1), success: $result->getBoolean(1),
remoteAddr: $result->getString(2), remoteAddress: $result->getString(2),
countryCode: $result->getString(3), countryCode: $result->getString(3),
created: $result->getInteger(4), createdTime: $result->getInteger(4),
userAgent: $result->getString(5), userAgentString: $result->getString(5),
clientInfo: $result->getString(6), clientInfoJson: $result->getString(6),
); );
} }
public function hasUserId(): bool { public CarbonImmutable $createdAt {
return $this->userId !== null; get => CarbonImmutable::createFromTimestampUTC($this->createdTime);
} }
public function getUserId(): ?string { public ClientInfo $clientInfo {
return $this->userId; get => ClientInfo::decode($this->clientInfoJson);
}
public function isSuccess(): bool {
return $this->success;
}
public function getRemoteAddress(): string {
return $this->remoteAddr;
}
public function getCountryCode(): string {
return $this->countryCode;
}
public function getCreatedTime(): int {
return $this->created;
}
public function getCreatedAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->created);
}
public function getUserAgentString(): string {
return $this->userAgent;
}
public function getClientInfoRaw(): string {
return $this->clientInfo;
}
public function getClientInfo(): ClientInfo {
return ClientInfo::decode($this->clientInfo);
} }
} }

View file

@ -7,55 +7,43 @@ use Index\Db\DbResult;
class RecoveryTokenInfo { class RecoveryTokenInfo {
public const LIFETIME = 60 * 60; public const LIFETIME = 60 * 60;
private string $userId; public function __construct(
private string $remoteAddr; public private(set) string $userId,
private int $created; public private(set) string $remoteAddress,
private ?string $code; public private(set) int $createdTime,
#[\SensitiveParameter] public private(set) ?string $code
) {}
public function __construct(DbResult $result) { public static function fromResult(DbResult $result): RecoveryTokenInfo {
$this->userId = (string)$result->getInteger(0); return new RecoveryTokenInfo(
$this->remoteAddr = $result->getString(1); userId: $result->getString(0),
$this->created = $result->getInteger(2); remoteAddress: $result->getString(1),
$this->code = $result->isNull(3) ? null : $result->getString(3); createdTime: $result->getInteger(2),
code: $result->getStringOrNull(3),
);
} }
public function getUserId(): string { public CarbonImmutable $createdAt {
return $this->userId; get => CarbonImmutable::createFromTimestampUTC($this->createdTime);
} }
public function getRemoteAddress(): string { public int $expiresTime {
return $this->remoteAddr; get => $this->createdTime + self::LIFETIME;
} }
public function getCreatedTime(): int { public CarbonImmutable $expiresAt {
return $this->created; get => CarbonImmutable::createFromTimestampUTC($this->expiresTime);
} }
public function getCreatedAt(): CarbonImmutable { public bool $expired {
return CarbonImmutable::createFromTimestampUTC($this->created); get => $this->expiresTime <= time();
} }
public function getExpiresTime(): int { public bool $hasCode {
return $this->created + self::LIFETIME; get => $this->code !== null;
} }
public function getExpiresAt(): CarbonImmutable { public bool $isValid {
return CarbonImmutable::createFromTimestampUTC($this->getExpiresTime()); get => $this->hasCode && !$this->expired;
}
public function hasExpired(): bool {
return $this->getExpiresTime() <= time();
}
public function hasCode(): bool {
return $this->code !== null;
}
public function getCode(): ?string {
return $this->code;
}
public function isValid(): bool {
return $this->hasCode() && !$this->hasExpired();
} }
} }

View file

@ -67,7 +67,7 @@ class RecoveryTokens {
if(!$result->next()) if(!$result->next())
throw new RuntimeException('Recovery token not found.'); throw new RuntimeException('Recovery token not found.');
return new RecoveryTokenInfo($result); return RecoveryTokenInfo::fromResult($result);
} }
public function createToken( public function createToken(
@ -89,12 +89,12 @@ class RecoveryTokens {
} }
public function invalidateToken(RecoveryTokenInfo $tokenInfo): void { public function invalidateToken(RecoveryTokenInfo $tokenInfo): void {
if(!$tokenInfo->hasCode()) if(!$tokenInfo->hasCode)
return; return;
$stmt = $this->cache->get('UPDATE msz_users_password_resets SET verification_code = NULL WHERE verification_code = ? AND user_id = ?'); $stmt = $this->cache->get('UPDATE msz_users_password_resets SET verification_code = NULL WHERE verification_code = ? AND user_id = ?');
$stmt->addParameter(1, $tokenInfo->getCode()); $stmt->addParameter(1, $tokenInfo->code);
$stmt->addParameter(2, $tokenInfo->getUserId()); $stmt->addParameter(2, $tokenInfo->userId);
$stmt->execute(); $stmt->execute();
} }
} }

View file

@ -7,18 +7,18 @@ use Index\Db\DbResult;
class SessionInfo { class SessionInfo {
public function __construct( public function __construct(
private string $id, public private(set) string $id,
private string $userId, public private(set) string $userId,
private string $token, #[\SensitiveParameter] public private(set) string $token,
private string $firstRemoteAddr, public private(set) string $firstRemoteAddress,
private ?string $lastRemoteAddr, public private(set) ?string $lastRemoteAddress,
private string $userAgent, public private(set) string $userAgentString,
private string $clientInfo, public private(set) string $clientInfoJson,
private string $countryCode, public private(set) string $countryCode,
private int $expires, public private(set) int $expiresTime,
private bool $bumpExpires, public private(set) bool $shouldBumpExpires,
private int $created, public private(set) int $createdTime,
private ?int $lastActive, public private(set) ?int $lastActiveTime,
) {} ) {}
public static function fromResult(DbResult $result): SessionInfo { public static function fromResult(DbResult $result): SessionInfo {
@ -26,91 +26,36 @@ class SessionInfo {
id: $result->getString(0), id: $result->getString(0),
userId: $result->getString(1), userId: $result->getString(1),
token: $result->getString(2), token: $result->getString(2),
firstRemoteAddr: $result->getString(3), firstRemoteAddress: $result->getString(3),
lastRemoteAddr: $result->getStringOrNull(4), lastRemoteAddress: $result->getStringOrNull(4),
userAgent: $result->getString(5), userAgentString: $result->getString(5),
clientInfo: $result->getString(6), clientInfoJson: $result->getString(6),
countryCode: $result->getString(7), countryCode: $result->getString(7),
expires: $result->getInteger(8), expiresTime: $result->getInteger(8),
bumpExpires: $result->getBoolean(9), shouldBumpExpires: $result->getBoolean(9),
created: $result->getInteger(10), createdTime: $result->getInteger(10),
lastActive: $result->getIntegerOrNull(11), lastActiveTime: $result->getIntegerOrNull(11),
); );
} }
public function getId(): string { public ClientInfo $clientInfo {
return $this->id; get => ClientInfo::decode($this->clientInfoJson);
} }
public function getUserId(): string { public CarbonImmutable $expiresAt {
return $this->userId; get => CarbonImmutable::createFromTimestampUTC($this->expiresTime);
} }
public function getToken(): string { public bool $expired {
return $this->token; get => $this->expiresTime < time();
} }
public function getFirstRemoteAddress(): string { public CarbonImmutable $createdAt {
return $this->firstRemoteAddr; get => CarbonImmutable::createFromTimestampUTC($this->createdTime);
} }
public function hasLastRemoteAddress(): bool { public ?CarbonImmutable $lastActiveAt {
return $this->lastRemoteAddr !== null; get => $this->lastActiveTime === null
} ? null : CarbonImmutable::createFromTimestampUTC($this->lastActiveTime);
public function getLastRemoteAddress(): ?string {
return $this->lastRemoteAddr;
}
public function getUserAgentString(): string {
return $this->userAgent;
}
public function getClientInfoRaw(): string {
return $this->clientInfo;
}
public function getClientInfo(): ClientInfo {
return ClientInfo::decode($this->clientInfo);
}
public function getCountryCode(): string {
return $this->countryCode;
}
public function getExpiresTime(): int {
return $this->expires;
}
public function getExpiresAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->expires);
}
public function shouldBumpExpires(): bool {
return $this->bumpExpires;
}
public function hasExpired(): bool {
return $this->expires < time();
}
public function getCreatedTime(): int {
return $this->created;
}
public function getCreatedAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->created);
}
public function hasLastActive(): bool {
return $this->lastActive !== null;
}
public function getLastActiveTime(): ?int {
return $this->lastActive;
}
public function getLastActiveAt(): ?CarbonImmutable {
return $this->lastActive === null ? null : CarbonImmutable::createFromTimestampUTC($this->lastActive);
} }
} }

View file

@ -207,7 +207,7 @@ class Sessions {
if($hasSessionInfos) if($hasSessionInfos)
foreach($sessionInfos as $sessionInfo) { foreach($sessionInfos as $sessionInfo) {
if($sessionInfo instanceof SessionInfo) if($sessionInfo instanceof SessionInfo)
$sessionInfo = $sessionInfo->getId(); $sessionInfo = $sessionInfo->id;
elseif(!is_string($sessionInfo)) elseif(!is_string($sessionInfo))
throw new InvalidArgumentException('$sessionInfos must be strings or instances of SessionInfo.'); throw new InvalidArgumentException('$sessionInfos must be strings or instances of SessionInfo.');
@ -245,7 +245,7 @@ class Sessions {
if($sessionInfo !== null && $sessionToken !== null) if($sessionInfo !== null && $sessionToken !== null)
throw new InvalidArgumentException('Only one of $sessionInfo and $sessionToken may be set at once.'); throw new InvalidArgumentException('Only one of $sessionInfo and $sessionToken may be set at once.');
if($sessionInfo instanceof SessionInfo) if($sessionInfo instanceof SessionInfo)
$sessionInfo = $sessionInfo->getId(); $sessionInfo = $sessionInfo->id;
$hasSessionInfo = $sessionInfo !== null; $hasSessionInfo = $sessionInfo !== null;
$hasSessionToken = $sessionToken !== null; $hasSessionToken = $sessionToken !== null;

View file

@ -6,74 +6,42 @@ use Index\Db\DbResult;
class ChangeInfo { class ChangeInfo {
public function __construct( public function __construct(
private string $id, public private(set) string $id,
private ?string $userId, public private(set) ?string $userId,
private int $action, public private(set) int $actionId,
private int $created, public private(set) int $createdTime,
private string $summary, public private(set) string $summary,
private string $body, public private(set) string $body,
) {} ) {}
public static function fromResult(DbResult $result): ChangeInfo { public static function fromResult(DbResult $result): ChangeInfo {
return new ChangeInfo( return new ChangeInfo(
id: $result->getString(0), id: $result->getString(0),
userId: $result->getStringOrNull(1), userId: $result->getStringOrNull(1),
action: $result->getInteger(2), actionId: $result->getInteger(2),
created: $result->getInteger(3), createdTime: $result->getInteger(3),
summary: $result->getString(4), summary: $result->getString(4),
body: $result->getString(5), body: $result->getString(5),
); );
} }
public function getId(): string { public string $action {
return $this->id; get => Changelog::convertFromActionId($this->actionId);
} }
public function hasUserId(): bool { public string $actionText {
return $this->userId !== null; get => Changelog::actionText($this->actionId);
} }
public function getUserId(): ?string { public CarbonImmutable $createdAt {
return $this->userId; get => CarbonImmutable::createFromTimestampUTC($this->createdTime);
} }
public function getActionId(): int { public string $date {
return $this->action; get => gmdate('Y-m-d', $this->createdTime);
} }
public function getAction(): string { public string $commentsCategoryName {
return Changelog::convertFromActionId($this->action); get => sprintf('changelog-date-%s', $this->date);
}
public function getActionText(): string {
return Changelog::actionText($this->action);
}
public function getCreatedTime(): int {
return $this->created;
}
public function getCreatedAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->created);
}
public function getDate(): string {
return gmdate('Y-m-d', $this->created);
}
public function getSummary(): string {
return $this->summary;
}
public function hasBody(): bool {
return !empty($this->body);
}
public function getBody(): string {
return $this->body;
}
public function getCommentsCategoryName(): string {
return sprintf('changelog-date-%s', $this->getDate());
} }
} }

View file

@ -6,56 +6,36 @@ use Carbon\CarbonImmutable;
use Index\Db\DbResult; use Index\Db\DbResult;
class ChangeTagInfo implements Stringable { class ChangeTagInfo implements Stringable {
private string $id; public function __construct(
private string $name; public private(set) string $id,
private string $description; public private(set) string $name,
private int $created; public private(set) string $description,
private int $archived; public private(set) int $createdTime,
private int $changes; public private(set) ?int $archivedTime,
public private(set) int $changesCount, // this is virtual!!!
) {}
public function __construct(DbResult $result) { public static function fromResult(DbResult $result): ChangeTagInfo {
$this->id = (string)$result->getInteger(0); return new ChangeTagInfo(
$this->name = $result->getString(1); id: $result->getString(0),
$this->description = $result->getString(2); name: $result->getString(1),
$this->created = $result->getInteger(3); description: $result->getString(2),
$this->archived = $result->getInteger(4); createdTime: $result->getInteger(3),
$this->changes = $result->getInteger(5); archivedTime: $result->getIntegerOrNull(4),
changesCount: $result->getInteger(5),
);
} }
public function getId(): string { public CarbonImmutable $createdAt {
return $this->id; get => CarbonImmutable::createFromTimestampUTC($this->createdTime);
} }
public function getName(): string { public CarbonImmutable $archivedAt {
return $this->name; get => CarbonImmutable::createFromTimestampUTC($this->archivedTime);
} }
public function getDescription(): string { public bool $archived {
return $this->description; get => $this->archivedTime !== null;
}
public function getCreatedTime(): int {
return $this->created;
}
public function getCreatedAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->created);
}
public function getArchivedTime(): int {
return $this->archived;
}
public function getArchivedAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->archived);
}
public function isArchived(): bool {
return $this->archived > 0;
}
public function getChangesCount(): int {
return $this->changes;
} }
public function __toString(): string { public function __toString(): string {

View file

@ -216,7 +216,7 @@ class Changelog {
public function deleteChange(ChangeInfo|string $infoOrId): void { public function deleteChange(ChangeInfo|string $infoOrId): void {
if($infoOrId instanceof ChangeInfo) if($infoOrId instanceof ChangeInfo)
$infoOrId = $infoOrId->getId(); $infoOrId = $infoOrId->id;
$stmt = $this->cache->get('DELETE FROM msz_changelog_changes WHERE change_id = ?'); $stmt = $this->cache->get('DELETE FROM msz_changelog_changes WHERE change_id = ?');
$stmt->addParameter(1, $infoOrId); $stmt->addParameter(1, $infoOrId);
@ -233,7 +233,7 @@ class Changelog {
DateTimeInterface|int|null $createdAt = null DateTimeInterface|int|null $createdAt = null
): void { ): void {
if($infoOrId instanceof ChangeInfo) if($infoOrId instanceof ChangeInfo)
$infoOrId = $infoOrId->getId(); $infoOrId = $infoOrId->id;
if(is_string($action)) if(is_string($action))
$action = self::convertToActionId($action); $action = self::convertToActionId($action);
@ -271,7 +271,7 @@ class Changelog {
ChangeInfo|string|null $changeInfo = null ChangeInfo|string|null $changeInfo = null
): array { ): array {
if($changeInfo instanceof ChangeInfo) if($changeInfo instanceof ChangeInfo)
$changeInfo = $changeInfo->getId(); $changeInfo = $changeInfo->id;
$hasChangeInfo = $changeInfo !== null; $hasChangeInfo = $changeInfo !== null;
@ -292,7 +292,7 @@ class Changelog {
if(array_key_exists($tagId, $this->tags)) if(array_key_exists($tagId, $this->tags))
$tags[] = $this->tags[$tagId]; $tags[] = $this->tags[$tagId];
else else
$tags[] = $this->tags[$tagId] = new ChangeTagInfo($result); $tags[] = $this->tags[$tagId] = ChangeTagInfo::fromResult($result);
} }
return $tags; return $tags;
@ -307,7 +307,7 @@ class Changelog {
if(!$result->next()) if(!$result->next())
throw new RuntimeException('No tag with that ID exists.'); throw new RuntimeException('No tag with that ID exists.');
return new ChangeTagInfo($result); return ChangeTagInfo::fromResult($result);
} }
public function createTag( public function createTag(
@ -334,7 +334,7 @@ class Changelog {
public function deleteTag(ChangeTagInfo|string $infoOrId): void { public function deleteTag(ChangeTagInfo|string $infoOrId): void {
if($infoOrId instanceof ChangeTagInfo) if($infoOrId instanceof ChangeTagInfo)
$infoOrId = $infoOrId->getId(); $infoOrId = $infoOrId->id;
$stmt = $this->cache->get('DELETE FROM msz_changelog_tags WHERE tag_id = ?'); $stmt = $this->cache->get('DELETE FROM msz_changelog_tags WHERE tag_id = ?');
$stmt->addParameter(1, $infoOrId); $stmt->addParameter(1, $infoOrId);
@ -348,7 +348,7 @@ class Changelog {
?bool $archived = null ?bool $archived = null
): void { ): void {
if($infoOrId instanceof ChangeTagInfo) if($infoOrId instanceof ChangeTagInfo)
$infoOrId = $infoOrId->getId(); $infoOrId = $infoOrId->id;
if($name !== null) { if($name !== null) {
$name = trim($name); $name = trim($name);
@ -377,9 +377,9 @@ class Changelog {
public function addTagToChange(ChangeInfo|string $change, ChangeTagInfo|string $tag): void { public function addTagToChange(ChangeInfo|string $change, ChangeTagInfo|string $tag): void {
if($change instanceof ChangeInfo) if($change instanceof ChangeInfo)
$change = $change->getId(); $change = $change->id;
if($tag instanceof ChangeTagInfo) if($tag instanceof ChangeTagInfo)
$tag = $tag->getId(); $tag = $tag->id;
$stmt = $this->cache->get('INSERT INTO msz_changelog_change_tags (change_id, tag_id) VALUES (?, ?)'); $stmt = $this->cache->get('INSERT INTO msz_changelog_change_tags (change_id, tag_id) VALUES (?, ?)');
$stmt->addParameter(1, $change); $stmt->addParameter(1, $change);
@ -389,9 +389,9 @@ class Changelog {
public function removeTagFromChange(ChangeInfo|string $change, ChangeTagInfo|string $tag): void { public function removeTagFromChange(ChangeInfo|string $change, ChangeTagInfo|string $tag): void {
if($change instanceof ChangeInfo) if($change instanceof ChangeInfo)
$change = $change->getId(); $change = $change->id;
if($tag instanceof ChangeTagInfo) if($tag instanceof ChangeTagInfo)
$tag = $tag->getId(); $tag = $tag->id;
$stmt = $this->cache->get('DELETE FROM msz_changelog_change_tags WHERE change_id = ? AND tag_id = ?'); $stmt = $this->cache->get('DELETE FROM msz_changelog_change_tags WHERE change_id = ? AND tag_id = ?');
$stmt->addParameter(1, $change); $stmt->addParameter(1, $change);

View file

@ -72,10 +72,10 @@ final class ChangelogRoutes implements RouteHandler, UrlSource {
$commentsCategoryName = null; $commentsCategoryName = null;
foreach($changeInfos as $changeInfo) { foreach($changeInfos as $changeInfo) {
$userInfo = $changeInfo->hasUserId() ? $this->usersCtx->getUserInfo($changeInfo->getUserId()) : null; $userInfo = $changeInfo->userId !== null ? $this->usersCtx->getUserInfo($changeInfo->userId) : null;
if($commentsCategoryName === null) if($commentsCategoryName === null)
$commentsCategoryName = $changeInfo->getCommentsCategoryName(); $commentsCategoryName = $changeInfo->commentsCategoryName;
$changes[] = [ $changes[] = [
'change' => $changeInfo, 'change' => $changeInfo,
@ -108,14 +108,14 @@ final class ChangelogRoutes implements RouteHandler, UrlSource {
} }
$tagInfos = $this->changelog->getTags(changeInfo: $changeInfo); $tagInfos = $this->changelog->getTags(changeInfo: $changeInfo);
$userInfo = $changeInfo->hasUserId() ? $this->usersCtx->getUserInfo($changeInfo->getUserId()) : null; $userInfo = $changeInfo->userId !== null ? $this->usersCtx->getUserInfo($changeInfo->userId) : null;
return Template::renderRaw('changelog.change', [ return Template::renderRaw('changelog.change', [
'change_info' => $changeInfo, 'change_info' => $changeInfo,
'change_tags' => $tagInfos, 'change_tags' => $tagInfos,
'change_user_info' => $userInfo, 'change_user_info' => $userInfo,
'change_user_colour' => $this->usersCtx->getUserColour($userInfo), 'change_user_colour' => $this->usersCtx->getUserColour($userInfo),
'comments_info' => $this->getCommentsInfo($changeInfo->getCommentsCategoryName()), 'comments_info' => $this->getCommentsInfo($changeInfo->commentsCategoryName),
]); ]);
} }
@ -124,8 +124,8 @@ final class ChangelogRoutes implements RouteHandler, UrlSource {
public function getFeed($response) { public function getFeed($response) {
$response->setContentType('application/rss+xml; charset=utf-8'); $response->setContentType('application/rss+xml; charset=utf-8');
$siteName = $this->siteInfo->getName(); $siteName = $this->siteInfo->name;
$siteUrl = $this->siteInfo->getURL(); $siteUrl = $this->siteInfo->url;
$changes = $this->changelog->getChanges(pagination: new Pagination(10)); $changes = $this->changelog->getChanges(pagination: new Pagination(10));
$feed = new FeedBuilder; $feed = new FeedBuilder;
@ -136,10 +136,10 @@ final class ChangelogRoutes implements RouteHandler, UrlSource {
foreach($changes as $change) foreach($changes as $change)
$feed->createEntry(function($item) use ($change, $siteUrl) { $feed->createEntry(function($item) use ($change, $siteUrl) {
$item->setTitle(sprintf('%s: %s', $change->getActionText(), $change->getSummary())); $item->setTitle(sprintf('%s: %s', $change->actionText, $change->summary));
$item->setCreatedAt($change->getCreatedTime()); $item->setCreatedAt($change->createdTime);
$item->setContentUrl($siteUrl . $this->urls->format('changelog-change', ['change' => $change->getId()])); $item->setContentUrl($siteUrl . $this->urls->format('changelog-change', ['change' => $change->id]));
$item->setCommentsUrl($siteUrl . $this->urls->format('changelog-change-comments', ['change' => $change->getId()])); $item->setCommentsUrl($siteUrl . $this->urls->format('changelog-change-comments', ['change' => $change->id]));
}); });
return $feed->toXmlString(); return $feed->toXmlString();

View file

@ -8,11 +8,11 @@ use Misuzu\Pagination;
use Misuzu\Users\UserInfo; use Misuzu\Users\UserInfo;
class Comments { class Comments {
private DbConnection $dbConn;
private DbStatementCache $cache; private DbStatementCache $cache;
public function __construct(DbConnection $dbConn) { public function __construct(
$this->dbConn = $dbConn; private DbConnection $dbConn
) {
$this->cache = new DbStatementCache($dbConn); $this->cache = new DbStatementCache($dbConn);
} }
@ -101,7 +101,7 @@ class Comments {
if($hasPostInfo) { if($hasPostInfo) {
if($postInfo instanceof CommentsPostInfo) { if($postInfo instanceof CommentsPostInfo) {
$query .= ' WHERE category_id = ?'; $query .= ' WHERE category_id = ?';
$value = $postInfo->getCategoryId(); $value = $postInfo->categoryId;
} else { } else {
$query .= ' WHERE category_id = (SELECT category_id FROM msz_comments_posts WHERE comment_id = ?)'; $query .= ' WHERE category_id = (SELECT category_id FROM msz_comments_posts WHERE comment_id = ?)';
$value = $postInfo; $value = $postInfo;
@ -157,7 +157,7 @@ class Comments {
public function deleteCategory(CommentsCategoryInfo|string $category): void { public function deleteCategory(CommentsCategoryInfo|string $category): void {
if($category instanceof CommentsCategoryInfo) if($category instanceof CommentsCategoryInfo)
$category = $category->getId(); $category = $category->id;
$stmt = $this->cache->get('DELETE FROM msz_comments_categories WHERE category_id = ?'); $stmt = $this->cache->get('DELETE FROM msz_comments_categories WHERE category_id = ?');
$stmt->addParameter(1, $category); $stmt->addParameter(1, $category);
@ -171,7 +171,7 @@ class Comments {
UserInfo|string|null $owner = null UserInfo|string|null $owner = null
): void { ): void {
if($category instanceof CommentsCategoryInfo) if($category instanceof CommentsCategoryInfo)
$category = $category->getId(); $category = $category->id;
if($owner instanceof UserInfo) if($owner instanceof UserInfo)
$owner = $owner->getId(); $owner = $owner->getId();
@ -191,7 +191,7 @@ class Comments {
public function lockCategory(CommentsCategoryInfo|string $category): void { public function lockCategory(CommentsCategoryInfo|string $category): void {
if($category instanceof CommentsCategoryInfo) if($category instanceof CommentsCategoryInfo)
$category = $category->getId(); $category = $category->id;
$stmt = $this->cache->get('UPDATE msz_comments_categories SET category_locked = COALESCE(category_locked, NOW()) WHERE category_id = ?'); $stmt = $this->cache->get('UPDATE msz_comments_categories SET category_locked = COALESCE(category_locked, NOW()) WHERE category_id = ?');
$stmt->addParameter(1, $category); $stmt->addParameter(1, $category);
@ -200,7 +200,7 @@ class Comments {
public function unlockCategory(CommentsCategoryInfo|string $category): void { public function unlockCategory(CommentsCategoryInfo|string $category): void {
if($category instanceof CommentsCategoryInfo) if($category instanceof CommentsCategoryInfo)
$category = $category->getId(); $category = $category->id;
$stmt = $this->cache->get('UPDATE msz_comments_categories SET category_locked = NULL WHERE category_id = ?'); $stmt = $this->cache->get('UPDATE msz_comments_categories SET category_locked = NULL WHERE category_id = ?');
$stmt->addParameter(1, $category); $stmt->addParameter(1, $category);
@ -215,9 +215,9 @@ class Comments {
?bool $deleted = null ?bool $deleted = null
): int { ): int {
if($categoryInfo instanceof CommentsCategoryInfo) if($categoryInfo instanceof CommentsCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
if($parentInfo instanceof CommentsPostInfo) if($parentInfo instanceof CommentsPostInfo)
$parentInfo = $parentInfo->getId(); $parentInfo = $parentInfo->id;
$hasCategoryInfo = $categoryInfo !== null; $hasCategoryInfo = $categoryInfo !== null;
$hasParentInfo = $parentInfo !== null; $hasParentInfo = $parentInfo !== null;
@ -270,9 +270,9 @@ class Comments {
bool $includeVotesCount = false bool $includeVotesCount = false
): iterable { ): iterable {
if($categoryInfo instanceof CommentsCategoryInfo) if($categoryInfo instanceof CommentsCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
if($parentInfo instanceof CommentsPostInfo) if($parentInfo instanceof CommentsPostInfo)
$parentInfo = $parentInfo->getId(); $parentInfo = $parentInfo->id;
$hasCategoryInfo = $categoryInfo !== null; $hasCategoryInfo = $categoryInfo !== null;
$hasParentInfo = $parentInfo !== null; $hasParentInfo = $parentInfo !== null;
@ -359,13 +359,13 @@ class Comments {
bool $pin = false bool $pin = false
): CommentsPostInfo { ): CommentsPostInfo {
if($category instanceof CommentsCategoryInfo) if($category instanceof CommentsCategoryInfo)
$category = $category->getId(); $category = $category->id;
if($parent instanceof CommentsPostInfo) { if($parent instanceof CommentsPostInfo) {
if($category === null) if($category === null)
$category = $parent->getCategoryId(); $category = $parent->categoryId;
elseif($category !== $parent->getCategoryId()) elseif($category !== $parent->categoryId)
throw new InvalidArgumentException('$parent belongs to a different category than where this post is attempted to be created.'); throw new InvalidArgumentException('$parent belongs to a different category than where this post is attempted to be created.');
$parent = $parent->getId(); $parent = $parent->id;
} }
if($category === null) if($category === null)
throw new InvalidArgumentException('$category is null; at least a $category or $parent must be specified.'); throw new InvalidArgumentException('$category is null; at least a $category or $parent must be specified.');
@ -387,7 +387,7 @@ class Comments {
public function deletePost(CommentsPostInfo|string $infoOrId): void { public function deletePost(CommentsPostInfo|string $infoOrId): void {
if($infoOrId instanceof CommentsPostInfo) if($infoOrId instanceof CommentsPostInfo)
$infoOrId = $infoOrId->getId(); $infoOrId = $infoOrId->id;
$stmt = $this->cache->get('UPDATE msz_comments_posts SET comment_deleted = COALESCE(comment_deleted, NOW()) WHERE comment_id = ?'); $stmt = $this->cache->get('UPDATE msz_comments_posts SET comment_deleted = COALESCE(comment_deleted, NOW()) WHERE comment_id = ?');
$stmt->addParameter(1, $infoOrId); $stmt->addParameter(1, $infoOrId);
@ -396,7 +396,7 @@ class Comments {
public function nukePost(CommentsPostInfo|string $infoOrId): void { public function nukePost(CommentsPostInfo|string $infoOrId): void {
if($infoOrId instanceof CommentsPostInfo) if($infoOrId instanceof CommentsPostInfo)
$infoOrId = $infoOrId->getId(); $infoOrId = $infoOrId->id;
$stmt = $this->cache->get('DELETE FROM msz_comments_posts WHERE comment_id = ?'); $stmt = $this->cache->get('DELETE FROM msz_comments_posts WHERE comment_id = ?');
$stmt->addParameter(1, $infoOrId); $stmt->addParameter(1, $infoOrId);
@ -405,7 +405,7 @@ class Comments {
public function restorePost(CommentsPostInfo|string $infoOrId): void { public function restorePost(CommentsPostInfo|string $infoOrId): void {
if($infoOrId instanceof CommentsPostInfo) if($infoOrId instanceof CommentsPostInfo)
$infoOrId = $infoOrId->getId(); $infoOrId = $infoOrId->id;
$stmt = $this->cache->get('UPDATE msz_comments_posts SET comment_deleted = NULL WHERE comment_id = ?'); $stmt = $this->cache->get('UPDATE msz_comments_posts SET comment_deleted = NULL WHERE comment_id = ?');
$stmt->addParameter(1, $infoOrId); $stmt->addParameter(1, $infoOrId);
@ -414,7 +414,7 @@ class Comments {
public function editPost(CommentsPostInfo|string $infoOrId, string $body): void { public function editPost(CommentsPostInfo|string $infoOrId, string $body): void {
if($infoOrId instanceof CommentsPostInfo) if($infoOrId instanceof CommentsPostInfo)
$infoOrId = $infoOrId->getId(); $infoOrId = $infoOrId->id;
if(empty(trim($body))) if(empty(trim($body)))
throw new InvalidArgumentException('$body may not be empty.'); throw new InvalidArgumentException('$body may not be empty.');
@ -427,7 +427,7 @@ class Comments {
public function pinPost(CommentsPostInfo|string $infoOrId): void { public function pinPost(CommentsPostInfo|string $infoOrId): void {
if($infoOrId instanceof CommentsPostInfo) if($infoOrId instanceof CommentsPostInfo)
$infoOrId = $infoOrId->getId(); $infoOrId = $infoOrId->id;
$stmt = $this->cache->get('UPDATE msz_comments_posts SET comment_pinned = COALESCE(comment_pinned, NOW()) WHERE comment_id = ?'); $stmt = $this->cache->get('UPDATE msz_comments_posts SET comment_pinned = COALESCE(comment_pinned, NOW()) WHERE comment_id = ?');
$stmt->addParameter(1, $infoOrId); $stmt->addParameter(1, $infoOrId);
@ -436,7 +436,7 @@ class Comments {
public function unpinPost(CommentsPostInfo|string $infoOrId): void { public function unpinPost(CommentsPostInfo|string $infoOrId): void {
if($infoOrId instanceof CommentsPostInfo) if($infoOrId instanceof CommentsPostInfo)
$infoOrId = $infoOrId->getId(); $infoOrId = $infoOrId->id;
$stmt = $this->cache->get('UPDATE msz_comments_posts SET comment_pinned = NULL WHERE comment_id = ?'); $stmt = $this->cache->get('UPDATE msz_comments_posts SET comment_pinned = NULL WHERE comment_id = ?');
$stmt->addParameter(1, $infoOrId); $stmt->addParameter(1, $infoOrId);
@ -448,7 +448,7 @@ class Comments {
UserInfo|string|null $user UserInfo|string|null $user
): CommentsPostVoteInfo { ): CommentsPostVoteInfo {
if($post instanceof CommentsPostInfo) if($post instanceof CommentsPostInfo)
$post = $post->getId(); $post = $post->id;
if($user instanceof UserInfo) if($user instanceof UserInfo)
$user = $user->getId(); $user = $user->getId();
@ -462,7 +462,7 @@ class Comments {
if(!$result->next()) if(!$result->next())
throw new RuntimeException('Failed to fetch vote info.'); throw new RuntimeException('Failed to fetch vote info.');
return new CommentsPostVoteInfo($result); return CommentsPostVoteInfo::fromResult($result);
} }
public function addPostVote( public function addPostVote(
@ -473,7 +473,7 @@ class Comments {
if($weight === 0) if($weight === 0)
return; return;
if($post instanceof CommentsPostInfo) if($post instanceof CommentsPostInfo)
$post = $post->getId(); $post = $post->id;
if($user instanceof UserInfo) if($user instanceof UserInfo)
$user = $user->getId(); $user = $user->getId();
@ -497,7 +497,7 @@ class Comments {
UserInfo|string $user UserInfo|string $user
): void { ): void {
if($post instanceof CommentsPostInfo) if($post instanceof CommentsPostInfo)
$post = $post->getId(); $post = $post->id;
if($user instanceof UserInfo) if($user instanceof UserInfo)
$user = $user->getId(); $user = $user->getId();

View file

@ -7,12 +7,12 @@ use Index\Db\DbResult;
class CommentsCategoryInfo { class CommentsCategoryInfo {
public function __construct( public function __construct(
private string $id, public private(set) string $id,
private string $name, public private(set) string $name,
private ?string $ownerId, public private(set) ?string $ownerId,
private int $created, public private(set) int $createdTime,
private ?int $locked, public private(set) ?int $lockedTime,
private int $comments, public private(set) int $commentsCount, // virtual!!
) {} ) {}
public static function fromResult(DbResult $result): CommentsCategoryInfo { public static function fromResult(DbResult $result): CommentsCategoryInfo {
@ -20,26 +20,22 @@ class CommentsCategoryInfo {
id: $result->getString(0), id: $result->getString(0),
name: $result->getString(1), name: $result->getString(1),
ownerId: $result->getStringOrNull(2), ownerId: $result->getStringOrNull(2),
created: $result->getInteger(3), createdTime: $result->getInteger(3),
locked: $result->getIntegerOrNull(4), lockedTime: $result->getIntegerOrNull(4),
comments: $result->getInteger(5), commentsCount: $result->getInteger(5),
); );
} }
public function getId(): string { public CarbonImmutable $createdAt {
return $this->id; get => CarbonImmutable::createFromTimestampUTC($this->createdTime);
} }
public function getName(): string { public ?CarbonImmutable $lockedAt {
return $this->name; get => $this->lockedTime === null ? null : CarbonImmutable::createFromTimestampUTC($this->lockedTime);
} }
public function hasOwnerId(): bool { public bool $locked {
return $this->ownerId !== null; get => $this->lockedTime !== null;
}
public function getOwnerId(): ?string {
return $this->ownerId;
} }
public function isOwner(UserInfo|string $user): bool { public function isOwner(UserInfo|string $user): bool {
@ -49,29 +45,4 @@ class CommentsCategoryInfo {
$user = $user->getId(); $user = $user->getId();
return $user === $this->ownerId; return $user === $this->ownerId;
} }
public function getCreatedTime(): int {
return $this->created;
}
public function getCreatedAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->created);
}
public function getLockedTime(): ?int {
return $this->locked;
}
public function getLockedAt(): ?CarbonImmutable {
return $this->locked === null ? null : CarbonImmutable::createFromTimestampUTC($this->locked);
}
public function isLocked(): bool {
return $this->locked !== null;
}
public function getCommentsCount(): int {
return $this->comments;
}
} }

View file

@ -20,8 +20,8 @@ class CommentsEx {
if(is_string($category)) if(is_string($category))
$category = $this->comments->ensureCategory($category); $category = $this->comments->ensureCategory($category);
$hasUser = $this->authInfo->isLoggedIn(); $hasUser = $this->authInfo->isLoggedIn;
$info->user = $hasUser ? $this->authInfo->getUserInfo() : null; $info->user = $hasUser ? $this->authInfo->userInfo : null;
$info->colour = $this->usersCtx->getUserColour($info->user); $info->colour = $this->usersCtx->getUserColour($info->user);
$info->perms = $this->authInfo->getPerms('global')->checkMany([ $info->perms = $this->authInfo->getPerms('global')->checkMany([
'can_post' => Perm::G_COMMENTS_CREATE, 'can_post' => Perm::G_COMMENTS_CREATE,
@ -42,7 +42,7 @@ class CommentsEx {
} }
public function decorateComment(CommentsPostInfo $postInfo): object { public function decorateComment(CommentsPostInfo $postInfo): object {
$userInfo = $postInfo->hasUserId() ? $this->usersCtx->getUserInfo($postInfo->getUserId()) : null; $userInfo = $postInfo->userId !== null ? $this->usersCtx->getUserInfo($postInfo->userId) : null;
$info = new stdClass; $info = new stdClass;
$info->post = $postInfo; $info->post = $postInfo;

View file

@ -6,19 +6,19 @@ use Index\Db\DbResult;
class CommentsPostInfo { class CommentsPostInfo {
public function __construct( public function __construct(
private string $id, public private(set) string $id,
private string $categoryId, public private(set) string $categoryId,
private ?string $userId, public private(set) ?string $userId,
private ?string $replyingTo, public private(set) ?string $replyingTo,
private string $body, public private(set) string $body,
private int $created, public private(set) int $createdTime,
private ?int $pinned, public private(set) ?int $pinnedTime,
private ?int $updated, public private(set) ?int $updatedTime,
private ?int $deleted, public private(set) ?int $deletedTime,
private int $replies, public private(set) int $repliesCount,
private int $votesTotal, public private(set) int $votesCount,
private int $votesPositive, public private(set) int $votesPositive,
private int $votesNegative, public private(set) int $votesNegative,
) {} ) {}
public static function fromResult( public static function fromResult(
@ -34,15 +34,15 @@ class CommentsPostInfo {
$args[] = $result->getStringOrNull(++$count); // userId $args[] = $result->getStringOrNull(++$count); // userId
$args[] = $result->getStringOrNull(++$count); // replyingTo $args[] = $result->getStringOrNull(++$count); // replyingTo
$args[] = $result->getString(++$count); // body $args[] = $result->getString(++$count); // body
$args[] = $result->getInteger(++$count); // created $args[] = $result->getInteger(++$count); // createdTime
$args[] = $result->getIntegerOrNull(++$count); // pinned $args[] = $result->getIntegerOrNull(++$count); // pinnedTime
$args[] = $result->getIntegerOrNull(++$count); // updated $args[] = $result->getIntegerOrNull(++$count); // updatedTime
$args[] = $result->getIntegerOrNull(++$count); // deleted $args[] = $result->getIntegerOrNull(++$count); // deletedTime
$args[] = $includeRepliesCount ? $result->getInteger(++$count) : 0; $args[] = $includeRepliesCount ? $result->getInteger(++$count) : 0;
if($includeVotesCount) { if($includeVotesCount) {
$args[] = $result->getInteger(++$count); // votesTotal $args[] = $result->getInteger(++$count); // votesCount
$args[] = $result->getInteger(++$count); // votesPositive $args[] = $result->getInteger(++$count); // votesPositive
$args[] = $result->getInteger(++$count); // votesNegative $args[] = $result->getInteger(++$count); // votesNegative
} else { } else {
@ -54,99 +54,35 @@ class CommentsPostInfo {
return new CommentsPostInfo(...$args); return new CommentsPostInfo(...$args);
} }
public function getId(): string { public bool $isReply {
return $this->id; get => $this->replyingTo !== null;
} }
public function getCategoryId(): string { public CarbonImmutable $createdAt {
return $this->categoryId; get => CarbonImmutable::createFromTimestampUTC($this->createdTime);
} }
public function hasUserId(): bool { public ?CarbonImmutable $pinnedAt {
return $this->userId !== null; get => $this->pinnedTime === null ? null : CarbonImmutable::createFromTimestampUTC($this->pinnedTime);
} }
public function getUserId(): ?string { public bool $pinned {
return $this->userId; get => $this->pinnedTime !== null;
} }
public function isReply(): bool { public ?CarbonImmutable $updatedAt {
return $this->replyingTo !== null; get => $this->updatedTime === null ? null : CarbonImmutable::createFromTimestampUTC($this->updatedTime);
} }
public function getReplyingTo(): ?string { public bool $edited {
return $this->replyingTo; get => $this->updatedTime !== null;
} }
public function getBody(): string { public ?CarbonImmutable $deletedAt {
return $this->body; get => $this->deletedTime === null ? null : CarbonImmutable::createFromTimestampUTC($this->deletedTime);
} }
public function getCreatedTime(): int { public bool $deleted {
return $this->created; get => $this->deletedTime !== null;
}
public function getCreatedAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->created);
}
public function getPinnedTime(): ?int {
return $this->pinned;
}
public function getPinnedAt(): ?CarbonImmutable {
return $this->pinned === null ? null : CarbonImmutable::createFromTimestampUTC($this->pinned);
}
public function isPinned(): bool {
return $this->pinned !== null;
}
public function getUpdatedTime(): ?int {
return $this->updated;
}
public function getUpdatedAt(): ?CarbonImmutable {
return $this->updated === null ? null : CarbonImmutable::createFromTimestampUTC($this->updated);
}
public function isEdited(): bool {
return $this->updated !== null;
}
public function getDeletedTime(): ?int {
return $this->deleted;
}
public function getDeletedAt(): ?CarbonImmutable {
return $this->deleted === null ? null : CarbonImmutable::createFromTimestampUTC($this->deleted);
}
public function isDeleted(): bool {
return $this->deleted !== null;
}
public function hasRepliesCount(): bool {
return $this->replies > 0;
}
public function getRepliesCount(): int {
return $this->replies;
}
public function hasVotesCount(): bool {
return $this->votesTotal > 0;
}
public function getVotesTotal(): int {
return $this->votesTotal;
}
public function getVotesPositive(): int {
return $this->votesPositive;
}
public function getVotesNegative(): int {
return $this->votesNegative;
} }
} }

View file

@ -4,25 +4,17 @@ namespace Misuzu\Comments;
use Index\Db\DbResult; use Index\Db\DbResult;
class CommentsPostVoteInfo { class CommentsPostVoteInfo {
private string $commentId; public function __construct(
private string $userId; public private(set) string $commentId,
private int $weight; public private(set) string $userId,
public private(set) int $weight
) {}
public function __construct(DbResult $result) { public static function fromResult(DbResult $result): CommentsPostVoteInfo {
$this->commentId = (string)$result->getInteger(0); return new CommentsPostVoteInfo(
$this->userId = (string)$result->getInteger(1); commentId: $result->getString(0),
$this->weight = $result->getInteger(2); userId: $result->getString(1),
} weight: $result->getInteger(2),
);
public function getCommentId(): string {
return $this->commentId;
}
public function getUserId(): string {
return $this->userId;
}
public function getWeight(): int {
return $this->weight;
} }
} }

View file

@ -6,32 +6,20 @@ use Index\Db\DbResult;
class CounterInfo { class CounterInfo {
public function __construct( public function __construct(
private string $name, public private(set) string $name,
private int $value, public private(set) int $value,
private int $updated, public private(set) int $updatedTime,
) {} ) {}
public static function fromResult(DbResult $result): CounterInfo { public static function fromResult(DbResult $result): CounterInfo {
return new CounterInfo( return new CounterInfo(
name: $result->getString(0), name: $result->getString(0),
value: $result->getInteger(1), value: $result->getInteger(1),
updated: $result->getInteger(2), updatedTime: $result->getInteger(2),
); );
} }
public function getName(): string { public CarbonImmutable $updatedAt {
return $this->name; get => CarbonImmutable::createFromTimestampUTC($this->updatedTime);
}
public function getValue(): int {
return $this->value;
}
public function getUpdatedTime(): int {
return $this->updated;
}
public function getUpdatedAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->updated);
} }
} }

View file

@ -6,37 +6,21 @@ use Index\Db\DbResult;
class EmoteInfo implements Stringable { class EmoteInfo implements Stringable {
public function __construct( public function __construct(
private string $id, public private(set) string $id,
private int $order, public private(set) int $order,
private int $rank, public private(set) int $minRank,
private string $url, public private(set) string $url,
) {} ) {}
public static function fromResult(DbResult $result): EmoteInfo { public static function fromResult(DbResult $result): EmoteInfo {
return new EmoteInfo( return new EmoteInfo(
id: $result->getString(0), id: $result->getString(0),
order: $result->getInteger(1), order: $result->getInteger(1),
rank: $result->getInteger(2), minRank: $result->getInteger(2),
url: $result->getString(3), url: $result->getString(3),
); );
} }
public function getId(): string {
return $this->id;
}
public function getOrder(): int {
return $this->order;
}
public function getMinRank(): int {
return $this->rank;
}
public function getUrl(): string {
return $this->url;
}
public function __toString(): string { public function __toString(): string {
return $this->url; return $this->url;
} }

View file

@ -6,9 +6,9 @@ use Index\Db\DbResult;
class EmoteStringInfo implements Stringable { class EmoteStringInfo implements Stringable {
public function __construct( public function __construct(
private string $emoteId, public private(set) string $emoteId,
private int $order, public private(set) int $order,
private string $string, public private(set) string $string,
) {} ) {}
public static function fromResult(DbResult $result): EmoteStringInfo { public static function fromResult(DbResult $result): EmoteStringInfo {
@ -19,18 +19,6 @@ class EmoteStringInfo implements Stringable {
); );
} }
public function getEmoteId(): string {
return $this->emoteId;
}
public function getOrder(): int {
return $this->order;
}
public function getString(): string {
return $this->string;
}
public function __toString(): string { public function __toString(): string {
return $this->string; return $this->string;
} }

View file

@ -12,11 +12,11 @@ class Emotes {
'rank' => 'emote_hierarchy', 'rank' => 'emote_hierarchy',
]; ];
private DbConnection $dbConn;
private DbStatementCache $cache; private DbStatementCache $cache;
public function __construct(DbConnection $dbConn) { public function __construct(
$this->dbConn = $dbConn; private DbConnection $dbConn
) {
$this->cache = new DbStatementCache($dbConn); $this->cache = new DbStatementCache($dbConn);
} }
@ -108,7 +108,7 @@ class Emotes {
public function deleteEmote(EmoteInfo|string $infoOrId): void { public function deleteEmote(EmoteInfo|string $infoOrId): void {
if($infoOrId instanceof EmoteInfo) if($infoOrId instanceof EmoteInfo)
$infoOrId = $infoOrId->getId(); $infoOrId = $infoOrId->id;
$stmt = $this->cache->get('DELETE FROM msz_emoticons WHERE emote_id = ?'); $stmt = $this->cache->get('DELETE FROM msz_emoticons WHERE emote_id = ?');
$stmt->addParameter(1, $infoOrId); $stmt->addParameter(1, $infoOrId);
@ -128,7 +128,7 @@ class Emotes {
} }
if($infoOrId instanceof EmoteInfo) if($infoOrId instanceof EmoteInfo)
$infoOrId = $infoOrId->getId(); $infoOrId = $infoOrId->id;
$stmt = $this->cache->get('UPDATE msz_emoticons SET emote_order = COALESCE(?, emote_order), emote_hierarchy = COALESCE(?, emote_hierarchy), emote_url = COALESCE(?, emote_url) WHERE emote_id = ?'); $stmt = $this->cache->get('UPDATE msz_emoticons SET emote_order = COALESCE(?, emote_order), emote_hierarchy = COALESCE(?, emote_hierarchy), emote_url = COALESCE(?, emote_url) WHERE emote_id = ?');
$stmt->addParameter(1, $order); $stmt->addParameter(1, $order);
@ -141,7 +141,7 @@ class Emotes {
public function updateEmoteOrderOffset(EmoteInfo|string $infoOrId, int $offset): void { public function updateEmoteOrderOffset(EmoteInfo|string $infoOrId, int $offset): void {
if($offset === 0) return; if($offset === 0) return;
if($infoOrId instanceof EmoteInfo) if($infoOrId instanceof EmoteInfo)
$infoOrId = $infoOrId->getId(); $infoOrId = $infoOrId->id;
$stmt = $this->cache->get('UPDATE msz_emoticons SET emote_order = emote_order + ? WHERE emote_id = ?'); $stmt = $this->cache->get('UPDATE msz_emoticons SET emote_order = emote_order + ? WHERE emote_id = ?');
$stmt->addParameter(1, $offset); $stmt->addParameter(1, $offset);
@ -151,7 +151,7 @@ class Emotes {
public function getEmoteStrings(EmoteInfo|string $infoOrId): iterable { public function getEmoteStrings(EmoteInfo|string $infoOrId): iterable {
if($infoOrId instanceof EmoteInfo) if($infoOrId instanceof EmoteInfo)
$infoOrId = $infoOrId->getId(); $infoOrId = $infoOrId->id;
$stmt = $this->cache->get('SELECT emote_id, emote_string_order, emote_string FROM msz_emoticons_strings WHERE emote_id = ? ORDER BY emote_string_order'); $stmt = $this->cache->get('SELECT emote_id, emote_string_order, emote_string FROM msz_emoticons_strings WHERE emote_id = ? ORDER BY emote_string_order');
$stmt->addParameter(1, $infoOrId); $stmt->addParameter(1, $infoOrId);
@ -195,7 +195,7 @@ class Emotes {
throw new InvalidArgumentException('$string is not correctly formatted: ' . $check); throw new InvalidArgumentException('$string is not correctly formatted: ' . $check);
if($infoOrId instanceof EmoteInfo) if($infoOrId instanceof EmoteInfo)
$infoOrId = $infoOrId->getId(); $infoOrId = $infoOrId->id;
$stmt = $this->cache->get('INSERT INTO msz_emoticons_strings (emote_id, emote_string, emote_string_order) SELECT ? AS target_emote_id, ?, COALESCE(?, (SELECT MAX(emote_string_order) + 1 FROM msz_emoticons_strings WHERE emote_id = target_emote_id), 1)'); $stmt = $this->cache->get('INSERT INTO msz_emoticons_strings (emote_id, emote_string, emote_string_order) SELECT ? AS target_emote_id, ?, COALESCE(?, (SELECT MAX(emote_string_order) + 1 FROM msz_emoticons_strings WHERE emote_id = target_emote_id), 1)');
$stmt->addParameter(1, $infoOrId); $stmt->addParameter(1, $infoOrId);
@ -206,7 +206,7 @@ class Emotes {
public function removeEmoteString(EmoteStringInfo|string $infoOrString): void { public function removeEmoteString(EmoteStringInfo|string $infoOrString): void {
if($infoOrString instanceof EmoteStringInfo) if($infoOrString instanceof EmoteStringInfo)
$infoOrString = $infoOrString->getString(); $infoOrString = $infoOrString->string;
$stmt = $this->cache->get('DELETE FROM msz_emoticons_strings WHERE emote_string = ?'); $stmt = $this->cache->get('DELETE FROM msz_emoticons_strings WHERE emote_string = ?');
$stmt->addParameter(1, $infoOrString); $stmt->addParameter(1, $infoOrString);

View file

@ -17,20 +17,20 @@ final class EmotesRpcHandler implements RpcHandler {
$this->emotes->getEmotes(orderBy: 'order'), $this->emotes->getEmotes(orderBy: 'order'),
function($emote) use ($includeId, $includeOrder) { function($emote) use ($includeId, $includeOrder) {
$info = [ $info = [
'url' => $emote->getUrl(), 'url' => $emote->url,
'strings' => XArray::select( 'strings' => XArray::select(
$this->emotes->getEmoteStrings($emote), $this->emotes->getEmoteStrings($emote),
fn($string) => $string->getString() fn($string) => $string->string
), ),
]; ];
if($includeId) if($includeId)
$info['id'] = $emote->getId(); $info['id'] = $emote->id;
if($includeOrder) if($includeOrder)
$info['order'] = $emote->getOrder(); $info['order'] = $emote->order;
$rank = $emote->getMinRank(); $rank = $emote->minRank;
if($rank != 0) if($rank !== 0)
$info['min_rank'] = $rank; $info['min_rank'] = $rank;
return $info; return $info;

View file

@ -28,19 +28,19 @@ class ForumCategories {
$tree = []; $tree = [];
$predicate = $parentInfo $predicate = $parentInfo
? fn($catInfo) => $catInfo->isDirectChildOf($parentInfo) ? fn($catInfo) => $catInfo->isDirectChildOf($parentInfo)
: fn($catInfo) => !$catInfo->hasParent(); : fn($catInfo) => !$catInfo->hasParent;
foreach($catInfos as $catInfo) { foreach($catInfos as $catInfo) {
if(!$predicate($catInfo)) if(!$predicate($catInfo))
continue; continue;
$tree[$catInfo->getId()] = $item = new stdClass; $tree[$catInfo->id] = $item = new stdClass;
$item->info = $catInfo; $item->info = $catInfo;
$item->colour = $catInfo->hasColour() ? $catInfo->getColour() : $colour; $item->colour = $catInfo->hasColour ? $catInfo->colour : $colour;
$item->children = self::convertCategoryListToTree($catInfos, $catInfo, $item->colour); $item->children = self::convertCategoryListToTree($catInfos, $catInfo, $item->colour);
$item->childIds = []; $item->childIds = [];
foreach($item->children as $child) { foreach($item->children as $child) {
$item->childIds[] = $child->info->getId(); $item->childIds[] = $child->info->id;
$item->childIds += $child->childIds; $item->childIds += $child->childIds;
} }
} }
@ -54,7 +54,7 @@ class ForumCategories {
?bool $hidden = null ?bool $hidden = null
): int { ): int {
if($parentInfo instanceof ForumCategoryInfo) if($parentInfo instanceof ForumCategoryInfo)
$parentInfo = $parentInfo->getId(); $parentInfo = $parentInfo->id;
$isRootParent = false; $isRootParent = false;
$hasParentInfo = $parentInfo !== false; $hasParentInfo = $parentInfo !== false;
@ -142,7 +142,7 @@ class ForumCategories {
$stmt = $this->cache->get($query); $stmt = $this->cache->get($query);
if($hasParentInfo && !$isRootParent) { if($hasParentInfo && !$isRootParent) {
if($parentInfo instanceof ForumCategoryInfo) if($parentInfo instanceof ForumCategoryInfo)
$stmt->addParameter(++$args, $parentInfo->getId()); $stmt->addParameter(++$args, $parentInfo->id);
else else
$stmt->addParameter(++$args, $parentInfo); $stmt->addParameter(++$args, $parentInfo);
} }
@ -187,7 +187,7 @@ class ForumCategories {
if($hasTopicInfo) { if($hasTopicInfo) {
if($topicInfo instanceof ForumTopicInfo) { if($topicInfo instanceof ForumTopicInfo) {
$query .= ' WHERE forum_id = ?'; $query .= ' WHERE forum_id = ?';
$value = $topicInfo->getCategoryId(); $value = $topicInfo->categoryId;
} else { } else {
$query .= ' WHERE forum_id = (SELECT forum_id FROM msz_forum_topics WHERE topic_id = ?)'; $query .= ' WHERE forum_id = (SELECT forum_id FROM msz_forum_topics WHERE topic_id = ?)';
$value = $topicInfo; $value = $topicInfo;
@ -196,7 +196,7 @@ class ForumCategories {
if($hasPostInfo) { if($hasPostInfo) {
if($postInfo instanceof ForumPostInfo) { if($postInfo instanceof ForumPostInfo) {
$query .= ' WHERE forum_id = ?'; $query .= ' WHERE forum_id = ?';
$value = $postInfo->getCategoryId(); $value = $postInfo->categoryId;
} else { } else {
$query .= ' WHERE forum_id = (SELECT forum_id FROM msz_forum_posts WHERE post_id = ?)'; $query .= ' WHERE forum_id = (SELECT forum_id FROM msz_forum_posts WHERE post_id = ?)';
$value = $postInfo; $value = $postInfo;
@ -218,17 +218,17 @@ class ForumCategories {
ForumCategoryInfo|string $categoryInfo ForumCategoryInfo|string $categoryInfo
): void { ): void {
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
} }
public function deleteCategory(ForumCategoryInfo|string $categoryInfo): void { public function deleteCategory(ForumCategoryInfo|string $categoryInfo): void {
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
} }
public function incrementCategoryClicks(ForumCategoryInfo|string $categoryInfo): void { public function incrementCategoryClicks(ForumCategoryInfo|string $categoryInfo): void {
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
// previous implementation also WHERE'd for forum_type = link but i don't think there's any other way to get here anyhow // previous implementation also WHERE'd for forum_type = link but i don't think there's any other way to get here anyhow
$stmt = $this->cache->get('UPDATE msz_forum_categories SET forum_link_clicks = forum_link_clicks + 1 WHERE forum_id = ? AND forum_link_clicks IS NOT NULL'); $stmt = $this->cache->get('UPDATE msz_forum_categories SET forum_link_clicks = forum_link_clicks + 1 WHERE forum_id = ? AND forum_link_clicks IS NOT NULL');
@ -238,7 +238,7 @@ class ForumCategories {
public function incrementCategoryTopics(ForumCategoryInfo|string $categoryInfo): void { public function incrementCategoryTopics(ForumCategoryInfo|string $categoryInfo): void {
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
$stmt = $this->cache->get('UPDATE msz_forum_categories SET forum_count_topics = forum_count_topics + 1 WHERE forum_id = ?'); $stmt = $this->cache->get('UPDATE msz_forum_categories SET forum_count_topics = forum_count_topics + 1 WHERE forum_id = ?');
$stmt->addParameter(1, $categoryInfo); $stmt->addParameter(1, $categoryInfo);
@ -247,7 +247,7 @@ class ForumCategories {
public function incrementCategoryPosts(ForumCategoryInfo|string $categoryInfo): void { public function incrementCategoryPosts(ForumCategoryInfo|string $categoryInfo): void {
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
$stmt = $this->cache->get('UPDATE msz_forum_categories SET forum_count_posts = forum_count_posts + 1 WHERE forum_id = ?'); $stmt = $this->cache->get('UPDATE msz_forum_categories SET forum_count_posts = forum_count_posts + 1 WHERE forum_id = ?');
$stmt->addParameter(1, $categoryInfo); $stmt->addParameter(1, $categoryInfo);
@ -258,9 +258,9 @@ class ForumCategories {
ForumCategoryInfo|ForumTopicInfo|ForumPostInfo|string $categoryInfo ForumCategoryInfo|ForumTopicInfo|ForumPostInfo|string $categoryInfo
): iterable { ): iterable {
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
elseif($categoryInfo instanceof ForumTopicInfo || $categoryInfo instanceof ForumPostInfo) elseif($categoryInfo instanceof ForumTopicInfo || $categoryInfo instanceof ForumPostInfo)
$categoryInfo = $categoryInfo->getCategoryId(); $categoryInfo = $categoryInfo->categoryId;
$query = 'WITH RECURSIVE msz_cte_ancestry AS (' $query = 'WITH RECURSIVE msz_cte_ancestry AS ('
. 'SELECT forum_id, forum_order, forum_parent, forum_name, forum_type, forum_description, forum_icon, forum_colour, forum_link, forum_link_clicks, UNIX_TIMESTAMP(forum_created), forum_archived, forum_hidden, forum_count_topics, forum_count_posts FROM msz_forum_categories WHERE forum_id = ?' . 'SELECT forum_id, forum_order, forum_parent, forum_name, forum_type, forum_description, forum_icon, forum_colour, forum_link, forum_link_clicks, UNIX_TIMESTAMP(forum_created), forum_archived, forum_hidden, forum_count_topics, forum_count_posts FROM msz_forum_categories WHERE forum_id = ?'
@ -282,7 +282,7 @@ class ForumCategories {
bool $asTree = false bool $asTree = false
): iterable { ): iterable {
if($parentInfo instanceof ForumCategoryInfo) if($parentInfo instanceof ForumCategoryInfo)
$parentInfo = $parentInfo->getId(); $parentInfo = $parentInfo->id;
$hasHidden = $hidden !== null; $hasHidden = $hidden !== null;
@ -336,7 +336,7 @@ class ForumCategories {
$stmt->addParameter(++$args, $userInfo); $stmt->addParameter(++$args, $userInfo);
foreach($categoryInfos as $categoryInfo) { foreach($categoryInfos as $categoryInfo) {
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$stmt->addParameter(++$args, $categoryInfo->getId()); $stmt->addParameter(++$args, $categoryInfo->id);
elseif(is_string($categoryInfo) || is_int($categoryInfo)) elseif(is_string($categoryInfo) || is_int($categoryInfo))
$stmt->addParameter(++$args, $categoryInfo); $stmt->addParameter(++$args, $categoryInfo);
else else
@ -357,8 +357,8 @@ class ForumCategories {
if($userInfo instanceof UserInfo) if($userInfo instanceof UserInfo)
$userInfo = $userInfo->getId(); $userInfo = $userInfo->getId();
if($categoryInfo instanceof $categoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
$stmt = $this->cache->get('REPLACE INTO msz_forum_topics_track (user_id, topic_id, forum_id, track_last_read) SELECT ?, topic_id, forum_id, NOW() FROM msz_forum_topics WHERE forum_id = ? AND topic_bumped >= NOW() - INTERVAL 1 MONTH'); $stmt = $this->cache->get('REPLACE INTO msz_forum_topics_track (user_id, topic_id, forum_id, track_last_read) SELECT ?, topic_id, forum_id, NOW() FROM msz_forum_topics WHERE forum_id = ? AND topic_bumped >= NOW() - INTERVAL 1 MONTH');
$stmt->addParameter(1, $userInfo); $stmt->addParameter(1, $userInfo);
@ -370,9 +370,9 @@ class ForumCategories {
ForumCategoryInfo|ForumTopicInfo|ForumPostInfo|string $categoryInfo ForumCategoryInfo|ForumTopicInfo|ForumPostInfo|string $categoryInfo
): Colour { ): Colour {
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
elseif($categoryInfo instanceof ForumTopicInfo || $categoryInfo instanceof ForumPostInfo) elseif($categoryInfo instanceof ForumTopicInfo || $categoryInfo instanceof ForumPostInfo)
$categoryInfo = $categoryInfo->getCategoryId(); $categoryInfo = $categoryInfo->categoryId;
$query = 'WITH RECURSIVE msz_cte_colours AS (' $query = 'WITH RECURSIVE msz_cte_colours AS ('
. 'SELECT forum_id, forum_parent, forum_colour FROM msz_forum_categories WHERE forum_id = ?' . 'SELECT forum_id, forum_parent, forum_colour FROM msz_forum_categories WHERE forum_id = ?'
@ -415,7 +415,7 @@ class ForumCategories {
$stmt->addParameter(++$args, $userInfo); $stmt->addParameter(++$args, $userInfo);
foreach($exceptCategoryInfos as $categoryInfo) { foreach($exceptCategoryInfos as $categoryInfo) {
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$stmt->addParameter(++$args, $categoryInfo->getId()); $stmt->addParameter(++$args, $categoryInfo->id);
elseif(is_string($categoryInfo) || is_int($categoryInfo)) elseif(is_string($categoryInfo) || is_int($categoryInfo))
$stmt->addParameter(++$args, (string)$categoryInfo); $stmt->addParameter(++$args, (string)$categoryInfo);
else else
@ -423,7 +423,7 @@ class ForumCategories {
} }
foreach($exceptTopicInfos as $topicInfo) { foreach($exceptTopicInfos as $topicInfo) {
if($topicInfo instanceof ForumTopicInfo) if($topicInfo instanceof ForumTopicInfo)
$stmt->addParameter(++$args, $topicInfo->getId()); $stmt->addParameter(++$args, $topicInfo->id);
elseif(is_string($topicInfo) || is_int($topicInfo)) elseif(is_string($topicInfo) || is_int($topicInfo))
$stmt->addParameter(++$args, (string)$topicInfo); $stmt->addParameter(++$args, (string)$topicInfo);
else else
@ -447,7 +447,7 @@ class ForumCategories {
bool $updateCounters = true bool $updateCounters = true
): object { ): object {
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
elseif($categoryInfo === null) elseif($categoryInfo === null)
$categoryInfo = '0'; $categoryInfo = '0';

View file

@ -27,21 +27,21 @@ class ForumCategoryInfo {
]; ];
public function __construct( public function __construct(
private string $id, public private(set) string $id,
private int $order, public private(set) int $order,
private ?string $parentId, public private(set) ?string $parentId,
private string $name, public private(set) string $name,
private int $type, public private(set) int $type,
private ?string $desc, public private(set) ?string $description,
private ?string $icon, public private(set) ?string $icon,
private ?int $colour, public private(set) ?int $colourRaw,
private ?string $link, public private(set) ?string $linkTarget,
private ?int $clicks, public private(set) ?int $linkClicks,
private int $created, public private(set) int $createdTime,
private bool $archived, public private(set) bool $archived,
private bool $hidden, public private(set) bool $hidden,
private int $topicsCount, public private(set) int $topicsCount,
private int $postsCount, public private(set) int $postsCount,
) {} ) {}
public static function fromResult(DbResult $result): ForumCategoryInfo { public static function fromResult(DbResult $result): ForumCategoryInfo {
@ -51,12 +51,12 @@ class ForumCategoryInfo {
parentId: $result->getStringOrNull(2), parentId: $result->getStringOrNull(2),
name: $result->getString(3), name: $result->getString(3),
type: $result->getInteger(4), type: $result->getInteger(4),
desc: $result->getStringOrNull(5), description: $result->getStringOrNull(5),
icon: $result->getStringOrNull(6), icon: $result->getStringOrNull(6),
colour: $result->getIntegerOrNull(7), colourRaw: $result->getIntegerOrNull(7),
link: $result->getStringOrNull(8), linkTarget: $result->getStringOrNull(8),
clicks: $result->getIntegerOrNull(9), linkClicks: $result->getIntegerOrNull(9),
created: $result->getInteger(10), createdTime: $result->getInteger(10),
archived: $result->getBoolean(11), archived: $result->getBoolean(11),
hidden: $result->getBoolean(12), hidden: $result->getBoolean(12),
topicsCount: $result->getInteger(13), topicsCount: $result->getInteger(13),
@ -64,135 +64,62 @@ class ForumCategoryInfo {
); );
} }
public function getId(): string { public bool $hasParent {
return $this->id; get => $this->parentId !== null && $this->parentId !== '0';
}
public function getOrder(): int {
return $this->order;
}
public function hasParent(): bool {
return $this->parentId !== null && $this->parentId !== '0';
}
public function getParentId(): ?string {
return $this->parentId;
} }
public function isDirectChildOf(ForumCategoryInfo|string $parentInfo): bool { public function isDirectChildOf(ForumCategoryInfo|string $parentInfo): bool {
if($parentInfo instanceof ForumCategoryInfo) if($parentInfo instanceof ForumCategoryInfo)
$parentInfo = $parentInfo->getId(); $parentInfo = $parentInfo->id;
return $this->hasParent() && $this->getParentId() === $parentInfo;
return $this->hasParent && $this->parentId === $parentInfo;
} }
public function getName(): string { public bool $isDiscussion {
return $this->name; get => $this->type === self::TYPE_DISCUSSION;
} }
public function getType(): int { public bool $isListing {
return $this->type; get => $this->type === self::TYPE_LISTING;
} }
public function isDiscussion(): bool { public bool $isLink {
return $this->type === self::TYPE_DISCUSSION; get => $this->type === self::TYPE_LINK;
} }
public function isListing(): bool { public bool $mayHaveChildren {
return $this->type === self::TYPE_LISTING; get => in_array($this->type, self::MAY_HAVE_CHILDREN);
} }
public function isLink(): bool { public bool $mayHaveTopics {
return $this->type === self::TYPE_LINK; get => in_array($this->type, self::MAY_HAVE_TOPICS);
} }
public function mayHaveChildren(): bool { public string $iconForDisplay {
return in_array($this->type, self::MAY_HAVE_CHILDREN); get {
if(!empty($this->icon))
return $this->icon;
if($this->archived)
return 'fas fa-archive fa-fw';
return match($this->type) {
self::TYPE_LISTING => 'fas fa-folder fa-fw',
self::TYPE_LINK => 'fas fa-link fa-fw',
default => 'fas fa-comments fa-fw',
};
}
} }
public function mayHaveTopics(): bool { public bool $hasColour {
return in_array($this->type, self::MAY_HAVE_TOPICS); get => $this->colourRaw !== null && ($this->colourRaw & 0x40000000) === 0;
} }
public function hasDescription(): bool { public Colour $colour {
return $this->desc !== null && $this->desc !== ''; get => $this->colourRaw === null ? Colour::none() : Colour::fromMisuzu($this->colourRaw);
} }
public function getDescription(): ?string { public CarbonImmutable $createdAt {
return $this->desc; get => CarbonImmutable::createFromTimestampUTC($this->createdTime);
}
public function hasIcon(): bool {
return $this->icon !== null && $this->icon !== '';
}
public function getIcon(): ?string {
return $this->icon;
}
public function getIconForDisplay(): string {
if($this->hasIcon())
return $this->getIcon();
if($this->isArchived())
return 'fas fa-archive fa-fw';
return match($this->type) {
self::TYPE_LISTING => 'fas fa-folder fa-fw',
self::TYPE_LINK => 'fas fa-link fa-fw',
default => 'fas fa-comments fa-fw',
};
}
public function hasColour(): bool {
return $this->colour !== null && ($this->colour & 0x40000000) === 0;
}
public function getColourRaw(): ?int {
return $this->colour;
}
public function getColour(): Colour {
return $this->colour === null ? Colour::none() : Colour::fromMisuzu($this->colour);
}
public function hasLinkTarget(): bool {
return $this->link !== null && $this->link !== '';
}
public function getLinkTarget(): ?string {
return $this->link;
}
public function hasLinkClicks(): bool {
return $this->clicks !== null;
}
public function getLinkClicks(): ?int {
return $this->clicks;
}
public function getCreatedTime(): int {
return $this->created;
}
public function getCreatedAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->created);
}
public function isArchived(): bool {
return $this->archived;
}
public function isHidden(): bool {
return $this->hidden;
}
public function getTopicsCount(): int {
return $this->topicsCount;
}
public function getPostsCount(): int {
return $this->postsCount;
} }
} }

View file

@ -6,10 +6,10 @@ use Index\Db\DbConnection;
use Misuzu\Users\UserInfo; use Misuzu\Users\UserInfo;
class ForumContext { class ForumContext {
private ForumCategories $categories; public private(set) ForumCategories $categories;
private ForumTopics $topics; public private(set) ForumTopics $topics;
private ForumTopicRedirects $topicRedirects; public private(set) ForumTopicRedirects $topicRedirects;
private ForumPosts $posts; public private(set) ForumPosts $posts;
private array $totalUserTopics = []; private array $totalUserTopics = [];
private array $totalUserPosts = []; private array $totalUserPosts = [];
@ -21,22 +21,6 @@ class ForumContext {
$this->posts = new ForumPosts($dbConn); $this->posts = new ForumPosts($dbConn);
} }
public function getCategories(): ForumCategories {
return $this->categories;
}
public function getTopics(): ForumTopics {
return $this->topics;
}
public function getTopicRedirects(): ForumTopicRedirects {
return $this->topicRedirects;
}
public function getPosts(): ForumPosts {
return $this->posts;
}
// should be replaced by a static counter // should be replaced by a static counter
public function countTotalUserTopics(UserInfo|string|null $userInfo): int { public function countTotalUserTopics(UserInfo|string|null $userInfo): int {
if($userInfo === null) if($userInfo === null)

View file

@ -8,17 +8,17 @@ use Index\Db\DbResult;
class ForumPostInfo { class ForumPostInfo {
public function __construct( public function __construct(
private string $id, public private(set) string $id,
private string $topicId, public private(set) string $topicId,
private string $categoryId, public private(set) string $categoryId,
private ?string $userId, public private(set) ?string $userId,
private string $remoteAddr, public private(set) string $remoteAddress,
private string $body, public private(set) string $body,
private int $parser, public private(set) int $parser,
private bool $displaySignature, public private(set) bool $shouldDisplaySignature,
private int $created, public private(set) int $createdTime,
private ?int $edited, public private(set) ?int $editedTime,
private ?int $deleted, public private(set) ?int $deletedTime,
) {} ) {}
public static function fromResult(DbResult $result): ForumPostInfo { public static function fromResult(DbResult $result): ForumPostInfo {
@ -27,111 +27,65 @@ class ForumPostInfo {
topicId: $result->getString(1), topicId: $result->getString(1),
categoryId: $result->getString(2), categoryId: $result->getString(2),
userId: $result->getStringOrNull(3), userId: $result->getStringOrNull(3),
remoteAddr: $result->getString(4), remoteAddress: $result->getString(4),
body: $result->getString(5), body: $result->getString(5),
parser: $result->getInteger(6), parser: $result->getInteger(6),
displaySignature: $result->getBoolean(7), shouldDisplaySignature: $result->getBoolean(7),
created: $result->getInteger(8), createdTime: $result->getInteger(8),
edited: $result->getIntegerOrNull(9), editedTime: $result->getIntegerOrNull(9),
deleted: $result->getIntegerOrNull(10), deletedTime: $result->getIntegerOrNull(10),
); );
} }
public function getId(): string { public bool $isBodyPlain {
return $this->id; get => $this->parser === Parser::PLAIN;
} }
public function getTopicId(): string { public bool $isBodyBBCode {
return $this->topicId; get => $this->parser === Parser::BBCODE;
} }
public function getCategoryId(): string { public bool $isBodyMarkdown {
return $this->categoryId; get => $this->parser === Parser::MARKDOWN;
} }
public function hasUserId(): bool { public CarbonImmutable $createdAt {
return $this->userId !== null; get => CarbonImmutable::createFromTimestampUTC($this->createdTime);
}
public function getUserId(): ?string {
return $this->userId;
}
public function getRemoteAddress(): string {
return $this->remoteAddr;
}
public function getBody(): string {
return $this->body;
}
public function getParser(): int {
return $this->parser;
}
public function isBodyPlain(): bool {
return $this->parser === Parser::PLAIN;
}
public function isBodyBBCode(): bool {
return $this->parser === Parser::BBCODE;
}
public function isBodyMarkdown(): bool {
return $this->parser === Parser::MARKDOWN;
}
public function shouldDisplaySignature(): bool {
return $this->displaySignature;
}
public function getCreatedTime(): int {
return $this->created;
}
public function getCreatedAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->created);
} }
private static ?CarbonImmutable $markAsEditedThreshold = null; private static ?CarbonImmutable $markAsEditedThreshold = null;
public function shouldMarkAsEdited(): bool { public bool $shouldMarkAsEdited {
if(self::$markAsEditedThreshold === null) get {
self::$markAsEditedThreshold = new CarbonImmutable('-5 minutes'); self::$markAsEditedThreshold ??= new CarbonImmutable('-5 minutes');
return XDateTime::compare($this->getCreatedAt(), self::$markAsEditedThreshold) < 0; return XDateTime::compare($this->createdAt, self::$markAsEditedThreshold) < 0;
}
} }
public function isEdited(): bool { public bool $edited {
return $this->edited !== null; get => $this->editedTime !== null;
} }
public function getEditedTime(): ?int { public ?CarbonImmutable $editedAt {
return $this->edited; get => $this->editedTime === null ? null : CarbonImmutable::createFromTimestampUTC($this->editedTime);
}
public function getEditedAt(): ?CarbonImmutable {
return $this->edited === null ? null : CarbonImmutable::createFromTimestampUTC($this->edited);
} }
private static ?CarbonImmutable $canBeDeletedThreshold = null; private static ?CarbonImmutable $canBeDeletedThreshold = null;
public function canBeDeleted(): bool { public bool $canBeDeleted {
if(self::$canBeDeletedThreshold === null) get {
self::$canBeDeletedThreshold = new CarbonImmutable('-1 week'); self::$canBeDeletedThreshold ??= new CarbonImmutable('-1 week');
return XDateTime::compare($this->getCreatedAt(), self::$canBeDeletedThreshold) >= 0; return XDateTime::compare($this->createdAt, self::$canBeDeletedThreshold) >= 0;
}
} }
public function isDeleted(): bool { public bool $deleted {
return $this->deleted !== null; get => $this->deletedTime !== null;
} }
public function getDeletedTime(): ?int { public ?CarbonImmutable $deletedAt {
return $this->deleted; get => $this->deletedTime === null ? null : CarbonImmutable::createFromTimestampUTC($this->deletedTime);
}
public function getDeletedAt(): ?CarbonImmutable {
return $this->deleted === null ? null : CarbonImmutable::createFromTimestampUTC($this->deleted);
} }
} }

View file

@ -10,11 +10,11 @@ use Misuzu\Pagination;
use Misuzu\Users\UserInfo; use Misuzu\Users\UserInfo;
class ForumPosts { class ForumPosts {
private DbConnection $dbConn;
private DbStatementCache $cache; private DbStatementCache $cache;
public function __construct(DbConnection $dbConn) { public function __construct(
$this->dbConn = $dbConn; private DbConnection $dbConn
) {
$this->cache = new DbStatementCache($dbConn); $this->cache = new DbStatementCache($dbConn);
} }
@ -26,13 +26,13 @@ class ForumPosts {
?bool $deleted = null ?bool $deleted = null
): int { ): int {
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
if($topicInfo instanceof ForumTopicInfo) if($topicInfo instanceof ForumTopicInfo)
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
if($userInfo instanceof UserInfo) if($userInfo instanceof UserInfo)
$userInfo = $userInfo->getId(); $userInfo = $userInfo->getId();
if($upToPostInfo instanceof ForumPostInfo) if($upToPostInfo instanceof ForumPostInfo)
$upToPostInfo = $upToPostInfo->getId(); $upToPostInfo = $upToPostInfo->id;
$hasCategoryInfo = $categoryInfo !== null; $hasCategoryInfo = $categoryInfo !== null;
$hasTopicInfo = $topicInfo !== null; $hasTopicInfo = $topicInfo !== null;
@ -109,15 +109,15 @@ class ForumPosts {
} }
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
if($topicInfo instanceof ForumTopicInfo) if($topicInfo instanceof ForumTopicInfo)
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
if($userInfo instanceof UserInfo) if($userInfo instanceof UserInfo)
$userInfo = $userInfo->getId(); $userInfo = $userInfo->getId();
if($upToPostInfo instanceof ForumPostInfo) if($upToPostInfo instanceof ForumPostInfo)
$upToPostInfo = $upToPostInfo->getId(); $upToPostInfo = $upToPostInfo->id;
if($afterPostInfo instanceof ForumPostInfo) if($afterPostInfo instanceof ForumPostInfo)
$afterPostInfo = $afterPostInfo->getId(); $afterPostInfo = $afterPostInfo->id;
$hasCategoryInfo = $categoryInfo !== null; $hasCategoryInfo = $categoryInfo !== null;
$hasTopicInfo = $topicInfo !== null; $hasTopicInfo = $topicInfo !== null;
@ -164,7 +164,7 @@ class ForumPosts {
if($hasCategoryInfo) { if($hasCategoryInfo) {
if(is_array($categoryInfo)) { if(is_array($categoryInfo)) {
foreach($categoryInfo as $categoryInfoEntry) foreach($categoryInfo as $categoryInfoEntry)
$stmt->addParameter(++$args, $categoryInfoEntry instanceof ForumCategoryInfo ? $categoryInfoEntry->getId() : (string)$categoryInfoEntry); $stmt->addParameter(++$args, $categoryInfoEntry instanceof ForumCategoryInfo ? $categoryInfoEntry->id : (string)$categoryInfoEntry);
} else } else
$stmt->addParameter(++$args, $categoryInfo); $stmt->addParameter(++$args, $categoryInfo);
} }
@ -217,7 +217,7 @@ class ForumPosts {
$query .= sprintf(' ORDER BY post_id %s', $getLast ? 'DESC' : 'ASC'); $query .= sprintf(' ORDER BY post_id %s', $getLast ? 'DESC' : 'ASC');
} elseif($hasTopicInfo) { } elseif($hasTopicInfo) {
if($topicInfo instanceof ForumTopicInfo) if($topicInfo instanceof ForumTopicInfo)
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
$query .= sprintf(' WHERE post_id = (SELECT %s(post_id) FROM msz_forum_posts WHERE topic_id = ?', $getLast ? 'MAX' : 'MIN'); $query .= sprintf(' WHERE post_id = (SELECT %s(post_id) FROM msz_forum_posts WHERE topic_id = ?', $getLast ? 'MAX' : 'MIN');
if($hasDeleted) if($hasDeleted)
@ -240,7 +240,7 @@ class ForumPosts {
foreach($categoryInfos as $categoryInfo) { foreach($categoryInfos as $categoryInfo) {
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$values[] = $categoryInfo->getId(); $values[] = $categoryInfo->id;
elseif(is_string($categoryInfo) || is_int($categoryInfo)) elseif(is_string($categoryInfo) || is_int($categoryInfo))
$values[] = (string)$categoryInfo; $values[] = (string)$categoryInfo;
else else
@ -271,11 +271,11 @@ class ForumPosts {
ForumCategoryInfo|string|null $categoryInfo = null ForumCategoryInfo|string|null $categoryInfo = null
): ForumPostInfo { ): ForumPostInfo {
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
if($topicInfo instanceof ForumTopicInfo) { if($topicInfo instanceof ForumTopicInfo) {
$categoryInfo ??= $topicInfo->getCategoryId(); $categoryInfo ??= $topicInfo->categoryId;
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
} elseif($categoryInfo === null) } elseif($categoryInfo === null)
throw new InvalidArgumentException('$categoryInfo may only be null if $topicInfo is an instance of ForumTopicInfo.'); throw new InvalidArgumentException('$categoryInfo may only be null if $topicInfo is an instance of ForumTopicInfo.');
@ -304,7 +304,7 @@ class ForumPosts {
bool $bumpEdited = true bool $bumpEdited = true
): void { ): void {
if($postInfo instanceof ForumPostInfo) if($postInfo instanceof ForumPostInfo)
$postInfo = $postInfo->getId(); $postInfo = $postInfo->id;
$fields = []; $fields = [];
$values = []; $values = [];
@ -345,7 +345,7 @@ class ForumPosts {
public function deletePost(ForumPostInfo|string $postInfo): void { public function deletePost(ForumPostInfo|string $postInfo): void {
if($postInfo instanceof ForumPostInfo) if($postInfo instanceof ForumPostInfo)
$postInfo = $postInfo->getId(); $postInfo = $postInfo->id;
$stmt = $this->cache->get('UPDATE msz_forum_posts SET post_deleted = COALESCE(post_deleted, NOW()) WHERE post_id = ?'); $stmt = $this->cache->get('UPDATE msz_forum_posts SET post_deleted = COALESCE(post_deleted, NOW()) WHERE post_id = ?');
$stmt->addParameter(1, $postInfo); $stmt->addParameter(1, $postInfo);
@ -354,7 +354,7 @@ class ForumPosts {
public function restorePost(ForumPostInfo|string $postInfo): void { public function restorePost(ForumPostInfo|string $postInfo): void {
if($postInfo instanceof ForumPostInfo) if($postInfo instanceof ForumPostInfo)
$postInfo = $postInfo->getId(); $postInfo = $postInfo->id;
$stmt = $this->cache->get('UPDATE msz_forum_posts SET post_deleted = NULL WHERE post_id = ?'); $stmt = $this->cache->get('UPDATE msz_forum_posts SET post_deleted = NULL WHERE post_id = ?');
$stmt->addParameter(1, $postInfo); $stmt->addParameter(1, $postInfo);
@ -363,7 +363,7 @@ class ForumPosts {
public function nukePost(ForumPostInfo|string $postInfo): void { public function nukePost(ForumPostInfo|string $postInfo): void {
if($postInfo instanceof ForumPostInfo) if($postInfo instanceof ForumPostInfo)
$postInfo = $postInfo->getId(); $postInfo = $postInfo->id;
$stmt = $this->cache->get('DELETE FROM msz_forum_posts WHERE post_id = ?'); $stmt = $this->cache->get('DELETE FROM msz_forum_posts WHERE post_id = ?');
$stmt->addParameter(1, $postInfo); $stmt->addParameter(1, $postInfo);
@ -418,9 +418,9 @@ class ForumPosts {
$args = 0; $args = 0;
$stmt = $this->cache->get($query); $stmt = $this->cache->get($query);
foreach($exceptCategoryInfos as $exceptCategoryInfo) foreach($exceptCategoryInfos as $exceptCategoryInfo)
$stmt->addParameter(++$args, $exceptCategoryInfo instanceof ForumCategoryInfo ? $exceptCategoryInfo->getId() : $exceptCategoryInfo); $stmt->addParameter(++$args, $exceptCategoryInfo instanceof ForumCategoryInfo ? $exceptCategoryInfo->id : $exceptCategoryInfo);
foreach($exceptTopicInfos as $exceptTopicInfo) foreach($exceptTopicInfos as $exceptTopicInfo)
$stmt->addParameter(++$args, $exceptTopicInfo instanceof ForumTopicInfo ? $exceptTopicInfo->getId() : $exceptTopicInfo); $stmt->addParameter(++$args, $exceptTopicInfo instanceof ForumTopicInfo ? $exceptTopicInfo->id : $exceptTopicInfo);
$stmt->execute(); $stmt->execute();
$result = $stmt->getResult(); $result = $stmt->getResult();

View file

@ -19,18 +19,18 @@ class ForumTopicInfo {
]; ];
public function __construct( public function __construct(
private string $id, public private(set) string $id,
private string $categoryId, public private(set) string $categoryId,
private ?string $userId, public private(set) ?string $userId,
private int $type, public private(set) int $type,
private string $title, public private(set) string $title,
private int $postsCount, public private(set) int $viewsCount,
private int $deletedPostsCount, public private(set) int $createdTime,
private int $viewsCount, public private(set) int $bumpedTime,
private int $created, public private(set) ?int $deletedTime,
private int $bumped, public private(set) ?int $lockedTime,
private ?int $deleted, public private(set) int $postsCount,
private ?int $locked, public private(set) int $deletedPostsCount,
) {} ) {}
public static function fromResult(DbResult $result): ForumTopicInfo { public static function fromResult(DbResult $result): ForumTopicInfo {
@ -41,37 +41,17 @@ class ForumTopicInfo {
type: $result->getInteger(3), type: $result->getInteger(3),
title: $result->getString(4), title: $result->getString(4),
viewsCount: $result->getInteger(5), viewsCount: $result->getInteger(5),
created: $result->getInteger(6), createdTime: $result->getInteger(6),
bumped: $result->getInteger(7), bumpedTime: $result->getInteger(7),
deleted: $result->getIntegerOrNull(8), deletedTime: $result->getIntegerOrNull(8),
locked: $result->getIntegerOrNull(9), lockedTime: $result->getIntegerOrNull(9),
postsCount: $result->getInteger(10), postsCount: $result->getInteger(10),
deletedPostsCount: $result->getInteger(11), deletedPostsCount: $result->getInteger(11),
); );
} }
public function getId(): string { public string $typeString {
return $this->id; get => match($this->type) {
}
public function getCategoryId(): string {
return $this->categoryId;
}
public function hasUserId(): bool {
return $this->userId !== null;
}
public function getUserId(): ?string {
return $this->userId;
}
public function getType(): int {
return $this->type;
}
public function getTypeString(): string {
return match($this->type) {
self::TYPE_GLOBAL => 'global', self::TYPE_GLOBAL => 'global',
self::TYPE_ANNOUNCE => 'announce', self::TYPE_ANNOUNCE => 'announce',
self::TYPE_STICKY => 'sticky', self::TYPE_STICKY => 'sticky',
@ -79,106 +59,75 @@ class ForumTopicInfo {
}; };
} }
public function isDiscussion(): bool { public bool $isDiscussion {
return $this->type === self::TYPE_DISCUSSION; get => $this->type === self::TYPE_DISCUSSION;
} }
public function isSticky(): bool { public bool $isSticky {
return $this->type === self::TYPE_STICKY; get => $this->type === self::TYPE_STICKY;
} }
public function isAnnouncement(): bool { public bool $isAnnouncement {
return $this->type === self::TYPE_ANNOUNCE; get => $this->type === self::TYPE_ANNOUNCE;
} }
public function isGlobalAnnouncement(): bool { public bool $isGlobalAnnouncement {
return $this->type === self::TYPE_GLOBAL; get => $this->type === self::TYPE_GLOBAL;
} }
public function isImportant(): bool { public bool $isImportant {
return $this->isSticky() get => $this->isSticky
|| $this->isAnnouncement() || $this->isAnnouncement
|| $this->isGlobalAnnouncement(); || $this->isGlobalAnnouncement;
} }
public function getIconForDisplay(bool $unread = false): string { public function getIconForDisplay(bool $unread = false): string {
if($this->isDeleted()) if($this->deleted)
return 'fas fa-trash-alt fa-fw'; return 'fas fa-trash-alt fa-fw';
if($this->isAnnouncement() || $this->isGlobalAnnouncement()) if($this->isAnnouncement || $this->isGlobalAnnouncement)
return 'fas fa-bullhorn fa-fw'; return 'fas fa-bullhorn fa-fw';
if($this->isSticky()) if($this->isSticky)
return 'fas fa-thumbtack fa-fw'; return 'fas fa-thumbtack fa-fw';
if($this->isLocked()) if($this->locked)
return 'fas fa-lock fa-fw'; return 'fas fa-lock fa-fw';
return sprintf('%s fa-comment fa-fw', $unread ? 'fas' : 'far'); return sprintf('%s fa-comment fa-fw', $unread ? 'fas' : 'far');
} }
public function getTitle(): string { public int $totalPostsCount {
return $this->title; get => $this->postsCount + $this->deletedPostsCount;
} }
public function getPostsCount(): int { public CarbonImmutable $createdAt {
return $this->postsCount; get => CarbonImmutable::createFromTimestampUTC($this->createdTime);
}
public function getDeletedPostsCount(): int {
return $this->deletedPostsCount;
}
public function getTotalPostsCount(): int {
return $this->postsCount + $this->deletedPostsCount;
}
public function getViewsCount(): int {
return $this->viewsCount;
}
public function getCreatedTime(): int {
return $this->created;
}
public function getCreatedAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->created);
} }
private static ?CarbonImmutable $lastActiveAt = null; private static ?CarbonImmutable $lastActiveAt = null;
public function isActive(): bool { public bool $active {
if(self::$lastActiveAt === null) get {
self::$lastActiveAt = new CarbonImmutable('-1 month'); self::$lastActiveAt ??= new CarbonImmutable('-1 month');
return XDateTime::compare($this->getBumpedAt(), self::$lastActiveAt) >= 0; return XDateTime::compare($this->bumpedAt, self::$lastActiveAt) >= 0;
}
} }
public function getBumpedTime(): int { public CarbonImmutable $bumpedAt {
return $this->bumped; get => CarbonImmutable::createFromTimestampUTC($this->bumpedTime);
} }
public function getBumpedAt(): CarbonImmutable { public bool $deleted {
return CarbonImmutable::createFromTimestampUTC($this->bumped); get => $this->deletedTime !== null;
} }
public function isDeleted(): bool { public ?CarbonImmutable $deletedAt {
return $this->deleted !== null; get => $this->deletedTime === null ? null : CarbonImmutable::createFromTimestampUTC($this->deletedTime);
} }
public function getDeletedTime(): ?int { public bool $locked {
return $this->deleted; get => $this->lockedTime !== null;
} }
public function getDeletedAt(): ?CarbonImmutable { public ?CarbonImmutable $lockedAt {
return $this->deleted === null ? null : CarbonImmutable::createFromTimestampUTC($this->deleted); get => $this->lockedTime === null ? null : CarbonImmutable::createFromTimestampUTC($this->lockedTime);
}
public function isLocked(): bool {
return $this->locked !== null;
}
public function getLockedTime(): ?int {
return $this->locked;
}
public function getLockedAt(): ?CarbonImmutable {
return $this->locked === null ? null : CarbonImmutable::createFromTimestampUTC($this->locked);
} }
} }

View file

@ -6,42 +6,22 @@ use Index\Db\DbResult;
class ForumTopicRedirectInfo { class ForumTopicRedirectInfo {
public function __construct( public function __construct(
private string $topicId, public private(set) string $topicId,
private ?string $userId, public private(set) ?string $userId,
private string $link, public private(set) string $linkTarget,
private int $created, public private(set) int $createdTime,
) {} ) {}
public static function fromResult(DbResult $result): ForumTopicRedirectInfo { public static function fromResult(DbResult $result): ForumTopicRedirectInfo {
return new ForumTopicRedirectInfo( return new ForumTopicRedirectInfo(
topicId: $result->getString(0), topicId: $result->getString(0),
userId: $result->getStringOrNull(1), userId: $result->getStringOrNull(1),
link: $result->getString(2), linkTarget: $result->getString(2),
created: $result->getInteger(3), createdTime: $result->getInteger(3),
); );
} }
public function getTopicId(): string { public CarbonImmutable $createdAt {
return $this->topicId; get => CarbonImmutable::createFromTimestampUTC($this->createdTime);
}
public function hasUserId(): bool {
return $this->userId !== null;
}
public function getUserId(): ?string {
return $this->userId;
}
public function getLinkTarget(): string {
return $this->link;
}
public function getCreatedTime(): int {
return $this->created;
}
public function getCreatedAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->created);
} }
} }

View file

@ -65,7 +65,7 @@ class ForumTopicRedirects {
public function hasTopicRedirect(ForumTopicInfo|string $topicInfo): bool { public function hasTopicRedirect(ForumTopicInfo|string $topicInfo): bool {
if($topicInfo instanceof ForumTopicInfo) if($topicInfo instanceof ForumTopicInfo)
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
$stmt = $this->cache->get('SELECT COUNT(*) FROM msz_forum_topics_redirects WHERE topic_id = ?'); $stmt = $this->cache->get('SELECT COUNT(*) FROM msz_forum_topics_redirects WHERE topic_id = ?');
$stmt->addParameter(1, $topicInfo); $stmt->addParameter(1, $topicInfo);
@ -80,7 +80,7 @@ class ForumTopicRedirects {
public function getTopicRedirect(ForumTopicInfo|string $topicInfo): ForumTopicRedirectInfo { public function getTopicRedirect(ForumTopicInfo|string $topicInfo): ForumTopicRedirectInfo {
if($topicInfo instanceof ForumTopicInfo) if($topicInfo instanceof ForumTopicInfo)
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
$stmt = $this->cache->get('SELECT topic_id, user_id, topic_redir_url, UNIX_TIMESTAMP(topic_redir_created) FROM msz_forum_topics_redirects WHERE topic_id = ?'); $stmt = $this->cache->get('SELECT topic_id, user_id, topic_redir_url, UNIX_TIMESTAMP(topic_redir_created) FROM msz_forum_topics_redirects WHERE topic_id = ?');
$stmt->addParameter(1, $topicInfo); $stmt->addParameter(1, $topicInfo);
@ -99,7 +99,7 @@ class ForumTopicRedirects {
string $linkTarget string $linkTarget
): ForumTopicRedirectInfo { ): ForumTopicRedirectInfo {
if($topicInfo instanceof ForumTopicInfo) if($topicInfo instanceof ForumTopicInfo)
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
if($userInfo instanceof UserInfo) if($userInfo instanceof UserInfo)
$userInfo = $userInfo->getId(); $userInfo = $userInfo->getId();
@ -114,9 +114,9 @@ class ForumTopicRedirects {
public function deleteTopicRedirect(ForumTopicRedirectInfo|ForumTopicInfo|string $topicInfo): void { public function deleteTopicRedirect(ForumTopicRedirectInfo|ForumTopicInfo|string $topicInfo): void {
if($topicInfo instanceof ForumTopicRedirectInfo) if($topicInfo instanceof ForumTopicRedirectInfo)
$topicInfo = $topicInfo->getTopicId(); $topicInfo = $topicInfo->topicId;
elseif($topicInfo instanceof ForumTopicInfo) elseif($topicInfo instanceof ForumTopicInfo)
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
$stmt = $this->cache->get('DELETE FROM msz_forum_topics_redirects WHERE topic_id = ?'); $stmt = $this->cache->get('DELETE FROM msz_forum_topics_redirects WHERE topic_id = ?');
$stmt->addParameter(1, $topicInfo); $stmt->addParameter(1, $topicInfo);

View file

@ -24,7 +24,7 @@ class ForumTopics {
?bool $deleted = null ?bool $deleted = null
): int { ): int {
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
if($userInfo instanceof UserInfo) if($userInfo instanceof UserInfo)
$userInfo = $userInfo->getId(); $userInfo = $userInfo->getId();
@ -70,7 +70,7 @@ class ForumTopics {
if($hasCategoryInfo) { if($hasCategoryInfo) {
if(is_array($categoryInfo)) { if(is_array($categoryInfo)) {
foreach($categoryInfo as $categoryInfoEntry) foreach($categoryInfo as $categoryInfoEntry)
$stmt->addParameter(++$args, $categoryInfoEntry instanceof ForumCategoryInfo ? $categoryInfoEntry->getId() : (string)$categoryInfoEntry); $stmt->addParameter(++$args, $categoryInfoEntry instanceof ForumCategoryInfo ? $categoryInfoEntry->id : (string)$categoryInfoEntry);
} else } else
$stmt->addParameter(++$args, $categoryInfo); $stmt->addParameter(++$args, $categoryInfo);
} }
@ -118,7 +118,7 @@ class ForumTopics {
} }
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
if($userInfo instanceof UserInfo) if($userInfo instanceof UserInfo)
$userInfo = $userInfo->getId(); $userInfo = $userInfo->getId();
@ -176,7 +176,7 @@ class ForumTopics {
if($hasCategoryInfo) { if($hasCategoryInfo) {
if(is_array($categoryInfo)) { if(is_array($categoryInfo)) {
foreach($categoryInfo as $categoryInfoEntry) foreach($categoryInfo as $categoryInfoEntry)
$stmt->addParameter(++$args, $categoryInfoEntry instanceof ForumCategoryInfo ? $categoryInfoEntry->getId() : (string)$categoryInfoEntry); $stmt->addParameter(++$args, $categoryInfoEntry instanceof ForumCategoryInfo ? $categoryInfoEntry->id : (string)$categoryInfoEntry);
} else } else
$stmt->addParameter(++$args, $categoryInfo); $stmt->addParameter(++$args, $categoryInfo);
} }
@ -216,7 +216,7 @@ class ForumTopics {
if($hasPostInfo) { if($hasPostInfo) {
if($postInfo instanceof ForumPostInfo) { if($postInfo instanceof ForumPostInfo) {
$query .= ' WHERE topic_id = ?'; $query .= ' WHERE topic_id = ?';
$value = $postInfo->getTopicId(); $value = $postInfo->topicId;
} else { } else {
$query .= ' WHERE topic_id = (SELECT topic_id FROM msz_forum_posts WHERE post_id = ?)'; $query .= ' WHERE topic_id = (SELECT topic_id FROM msz_forum_posts WHERE post_id = ?)';
$value = $postInfo; $value = $postInfo;
@ -246,7 +246,7 @@ class ForumTopics {
$type = ForumTopicInfo::TYPE_ALIASES[$type]; $type = ForumTopicInfo::TYPE_ALIASES[$type];
} }
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
if($userInfo instanceof UserInfo) if($userInfo instanceof UserInfo)
$userInfo = $userInfo->getId(); $userInfo = $userInfo->getId();
@ -266,7 +266,7 @@ class ForumTopics {
string|int|null $type = null string|int|null $type = null
): void { ): void {
if($topicInfo instanceof ForumTopicInfo) if($topicInfo instanceof ForumTopicInfo)
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
$fields = []; $fields = [];
$values = []; $values = [];
@ -301,7 +301,7 @@ class ForumTopics {
public function incrementTopicViews(ForumTopicInfo|string $topicInfo): void { public function incrementTopicViews(ForumTopicInfo|string $topicInfo): void {
if($topicInfo instanceof ForumTopicInfo) if($topicInfo instanceof ForumTopicInfo)
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
$stmt = $this->cache->get('UPDATE msz_forum_topics SET topic_count_views = topic_count_views + 1 WHERE topic_id = ?'); $stmt = $this->cache->get('UPDATE msz_forum_topics SET topic_count_views = topic_count_views + 1 WHERE topic_id = ?');
$stmt->addParameter(1, $topicInfo); $stmt->addParameter(1, $topicInfo);
@ -310,7 +310,7 @@ class ForumTopics {
public function bumpTopic(ForumTopicInfo|string $topicInfo): void { public function bumpTopic(ForumTopicInfo|string $topicInfo): void {
if($topicInfo instanceof ForumTopicInfo) if($topicInfo instanceof ForumTopicInfo)
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
$stmt = $this->cache->get('UPDATE msz_forum_topics SET topic_bumped = NOW() WHERE topic_id = ?'); $stmt = $this->cache->get('UPDATE msz_forum_topics SET topic_bumped = NOW() WHERE topic_id = ?');
$stmt->addParameter(1, $topicInfo); $stmt->addParameter(1, $topicInfo);
@ -319,7 +319,7 @@ class ForumTopics {
public function lockTopic(ForumTopicInfo|string $topicInfo): void { public function lockTopic(ForumTopicInfo|string $topicInfo): void {
if($topicInfo instanceof ForumTopicInfo) if($topicInfo instanceof ForumTopicInfo)
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
$stmt = $this->cache->get('UPDATE msz_forum_topics SET topic_locked = NOW() WHERE topic_id = ?'); $stmt = $this->cache->get('UPDATE msz_forum_topics SET topic_locked = NOW() WHERE topic_id = ?');
$stmt->addParameter(1, $topicInfo); $stmt->addParameter(1, $topicInfo);
@ -328,7 +328,7 @@ class ForumTopics {
public function unlockTopic(ForumTopicInfo|string $topicInfo): void { public function unlockTopic(ForumTopicInfo|string $topicInfo): void {
if($topicInfo instanceof ForumTopicInfo) if($topicInfo instanceof ForumTopicInfo)
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
$stmt = $this->cache->get('UPDATE msz_forum_topics SET topic_locked = NULL WHERE topic_id = ?'); $stmt = $this->cache->get('UPDATE msz_forum_topics SET topic_locked = NULL WHERE topic_id = ?');
$stmt->addParameter(1, $topicInfo); $stmt->addParameter(1, $topicInfo);
@ -337,7 +337,7 @@ class ForumTopics {
public function deleteTopic(ForumTopicInfo|string $topicInfo): void { public function deleteTopic(ForumTopicInfo|string $topicInfo): void {
if($topicInfo instanceof ForumTopicInfo) if($topicInfo instanceof ForumTopicInfo)
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
$stmt = $this->cache->get('UPDATE msz_forum_topics SET topic_deleted = COALESCE(topic_deleted, NOW()) WHERE topic_id = ? AND topic_deleted IS NULL'); $stmt = $this->cache->get('UPDATE msz_forum_topics SET topic_deleted = COALESCE(topic_deleted, NOW()) WHERE topic_id = ? AND topic_deleted IS NULL');
$stmt->addParameter(1, $topicInfo); $stmt->addParameter(1, $topicInfo);
@ -350,7 +350,7 @@ class ForumTopics {
public function restoreTopic(ForumTopicInfo|string $topicInfo): void { public function restoreTopic(ForumTopicInfo|string $topicInfo): void {
if($topicInfo instanceof ForumTopicInfo) if($topicInfo instanceof ForumTopicInfo)
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
$stmt = $this->cache->get('UPDATE msz_forum_posts AS fp SET post_deleted = NULL WHERE topic_id = ? AND post_deleted = (SELECT topic_deleted FROM msz_forum_topics WHERE topic_id = fp.topic_id)'); $stmt = $this->cache->get('UPDATE msz_forum_posts AS fp SET post_deleted = NULL WHERE topic_id = ? AND post_deleted = (SELECT topic_deleted FROM msz_forum_topics WHERE topic_id = fp.topic_id)');
$stmt->addParameter(1, $topicInfo); $stmt->addParameter(1, $topicInfo);
@ -363,7 +363,7 @@ class ForumTopics {
public function nukeTopic(ForumTopicInfo|string $topicInfo): void { public function nukeTopic(ForumTopicInfo|string $topicInfo): void {
if($topicInfo instanceof ForumTopicInfo) if($topicInfo instanceof ForumTopicInfo)
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
$stmt = $this->cache->get('DELETE FROM msz_forum_topics WHERE topic_id = ?'); $stmt = $this->cache->get('DELETE FROM msz_forum_topics WHERE topic_id = ?');
$stmt->addParameter(1, $topicInfo); $stmt->addParameter(1, $topicInfo);
@ -377,7 +377,7 @@ class ForumTopics {
if($userInfo === null) if($userInfo === null)
return false; return false;
if($topicInfo instanceof ForumTopicInfo) if($topicInfo instanceof ForumTopicInfo)
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
if($userInfo instanceof UserInfo) if($userInfo instanceof UserInfo)
$userInfo = $userInfo->getId(); $userInfo = $userInfo->getId();
@ -398,7 +398,7 @@ class ForumTopics {
return false; return false;
$topicInfoIsInstance = $topicInfo instanceof ForumTopicInfo; $topicInfoIsInstance = $topicInfo instanceof ForumTopicInfo;
if($topicInfoIsInstance && !$topicInfo->isActive()) if($topicInfoIsInstance && !$topicInfo->active)
return false; return false;
$query = 'SELECT UNIX_TIMESTAMP(track_last_read) FROM msz_forum_topics_track AS ftt WHERE user_id = ? AND topic_id = ?'; $query = 'SELECT UNIX_TIMESTAMP(track_last_read) FROM msz_forum_topics_track AS ftt WHERE user_id = ? AND topic_id = ?';
@ -407,7 +407,7 @@ class ForumTopics {
$stmt = $this->cache->get($query); $stmt = $this->cache->get($query);
$stmt->addParameter(1, $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo); $stmt->addParameter(1, $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo);
$stmt->addParameter(2, $topicInfoIsInstance ? $topicInfo->getId() : $topicInfo); $stmt->addParameter(2, $topicInfoIsInstance ? $topicInfo->id : $topicInfo);
$stmt->execute(); $stmt->execute();
$result = $stmt->getResult(); $result = $stmt->getResult();
@ -415,7 +415,7 @@ class ForumTopics {
if(!$result->next()) if(!$result->next())
return true; return true;
return $result->getInteger(0) < $topicInfo->getBumpedTime(); return $result->getInteger(0) < $topicInfo->bumpedTime;
} }
public function getMostActiveTopicInfo( public function getMostActiveTopicInfo(
@ -445,7 +445,7 @@ class ForumTopics {
$stmt->addParameter(++$args, $userInfo); $stmt->addParameter(++$args, $userInfo);
foreach($exceptCategoryInfos as $categoryInfo) { foreach($exceptCategoryInfos as $categoryInfo) {
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$stmt->addParameter(++$args, $categoryInfo->getId()); $stmt->addParameter(++$args, $categoryInfo->id);
elseif(is_string($categoryInfo) || is_int($categoryInfo)) elseif(is_string($categoryInfo) || is_int($categoryInfo))
$stmt->addParameter(++$args, (string)$categoryInfo); $stmt->addParameter(++$args, (string)$categoryInfo);
else else
@ -453,7 +453,7 @@ class ForumTopics {
} }
foreach($exceptTopicInfos as $topicInfo) { foreach($exceptTopicInfos as $topicInfo) {
if($topicInfo instanceof ForumTopicInfo) if($topicInfo instanceof ForumTopicInfo)
$stmt->addParameter(++$args, $topicInfo->getId()); $stmt->addParameter(++$args, $topicInfo->id);
elseif(is_string($topicInfo) || is_int($topicInfo)) elseif(is_string($topicInfo) || is_int($topicInfo))
$stmt->addParameter(++$args, (string)$topicInfo); $stmt->addParameter(++$args, (string)$topicInfo);
else else
@ -483,7 +483,7 @@ class ForumTopics {
return true; return true;
$stmt = $this->cache->get('SELECT COUNT(*) FROM msz_forum_topics_track WHERE topic_id = ? AND user_id = ?'); $stmt = $this->cache->get('SELECT COUNT(*) FROM msz_forum_topics_track WHERE topic_id = ? AND user_id = ?');
$stmt->addParameter(1, $topicInfo instanceof ForumTopicInfo ? $topicInfo->getId() : $topicInfo); $stmt->addParameter(1, $topicInfo instanceof ForumTopicInfo ? $topicInfo->id : $topicInfo);
$stmt->addParameter(2, $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo); $stmt->addParameter(2, $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo);
$stmt->execute(); $stmt->execute();
$result = $stmt->getResult(); $result = $stmt->getResult();
@ -503,13 +503,13 @@ class ForumTopics {
$userInfo = $userInfo->getId(); $userInfo = $userInfo->getId();
if($topicInfo instanceof ForumTopicInfo) { if($topicInfo instanceof ForumTopicInfo) {
$categoryInfo = $topicInfo->getCategoryId(); $categoryInfo = $topicInfo->categoryId;
$topicInfo = $topicInfo->getId(); $topicInfo = $topicInfo->id;
} else { } else {
if($categoryInfo === null) if($categoryInfo === null)
throw new InvalidArgumentException('$categoryInfo must be specified if $topicInfo is not an instance of ForumTopicInfo.'); throw new InvalidArgumentException('$categoryInfo must be specified if $topicInfo is not an instance of ForumTopicInfo.');
if($categoryInfo instanceof ForumCategoryInfo) if($categoryInfo instanceof ForumCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
} }
$stmt = $this->cache->get('REPLACE INTO msz_forum_topics_track (user_id, topic_id, forum_id, track_last_read) VALUES (?, ?, ?, NOW())'); $stmt = $this->cache->get('REPLACE INTO msz_forum_topics_track (user_id, topic_id, forum_id, track_last_read) VALUES (?, ?, ?, NOW())');

View file

@ -41,7 +41,7 @@ final class HanyuuRpcHandler implements RpcHandler {
} }
private function canImpersonateUserId(UserInfo $impersonator, string $targetId): bool { private function canImpersonateUserId(UserInfo $impersonator, string $targetId): bool {
if($impersonator->isSuperUser()) if($impersonator->super)
return true; return true;
return in_array( return in_array(
@ -78,28 +78,26 @@ final class HanyuuRpcHandler implements RpcHandler {
$tokenPacker = $this->authCtx->createAuthTokenPacker(); $tokenPacker = $this->authCtx->createAuthTokenPacker();
$tokenInfo = $tokenPacker->unpack(trim($token)); $tokenInfo = $tokenPacker->unpack(trim($token));
if($tokenInfo->isEmpty()) if($tokenInfo->isEmpty)
return self::createPayload('auth:check:fail', ['reason' => 'empty', 'login_url' => $loginUrl, 'register_url' => $registerUrl]); return self::createPayload('auth:check:fail', ['reason' => 'empty', 'login_url' => $loginUrl, 'register_url' => $registerUrl]);
$sessions = $this->authCtx->getSessions();
try { try {
$sessionInfo = $sessions->getSession(sessionToken: $tokenInfo->getSessionToken()); $sessionInfo = $this->authCtx->sessions->getSession(sessionToken: $tokenInfo->sessionToken);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
return self::createPayload('auth:check:fail', ['reason' => 'session', 'login_url' => $loginUrl, 'register_url' => $registerUrl]); return self::createPayload('auth:check:fail', ['reason' => 'session', 'login_url' => $loginUrl, 'register_url' => $registerUrl]);
} }
if($sessionInfo->hasExpired()) { if($sessionInfo->expired) {
$sessions->deleteSessions(sessionInfos: $sessionInfo); $this->authCtx->sessions->deleteSessions(sessionInfos: $sessionInfo);
return self::createPayload('auth:check:fail', ['reason' => 'expired', 'login_url' => $loginUrl, 'register_url' => $registerUrl]); return self::createPayload('auth:check:fail', ['reason' => 'expired', 'login_url' => $loginUrl, 'register_url' => $registerUrl]);
} }
$sessions->recordSessionActivity(sessionInfo: $sessionInfo, remoteAddr: $remoteAddr); $this->authCtx->sessions->recordSessionActivity(sessionInfo: $sessionInfo, remoteAddr: $remoteAddr);
$users = $this->usersCtx->getUsers(); $userInfo = $userInfoReal = $this->usersCtx->users->getUser($sessionInfo->userId, 'id');
$userInfo = $userInfoReal = $users->getUser($sessionInfo->getUserId(), 'id'); if($tokenInfo->hasImpersonatedUserId && $this->canImpersonateUserId($userInfo, $tokenInfo->impersonatedUserId)) {
if($tokenInfo->hasImpersonatedUserId() && $this->canImpersonateUserId($userInfo, $tokenInfo->getImpersonatedUserId())) {
try { try {
$userInfo = $users->getUser($tokenInfo->getImpersonatedUserId(), 'id'); $userInfo = $this->usersCtx->users->getUser($tokenInfo->impersonatedUserId, 'id');
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
$userInfo = $userInfoReal; $userInfo = $userInfoReal;
} }
@ -107,22 +105,22 @@ final class HanyuuRpcHandler implements RpcHandler {
$response = []; $response = [];
$response['session'] = [ $response['session'] = [
'token' => $sessionInfo->getToken(), 'token' => $sessionInfo->token,
'created_at' => $sessionInfo->getCreatedTime(), 'created_at' => $sessionInfo->createdTime,
'expires_at' => $sessionInfo->getExpiresTime(), 'expires_at' => $sessionInfo->expiresTime,
'lifetime_extends' => $sessionInfo->shouldBumpExpires(), 'lifetime_extends' => $sessionInfo->shouldBumpExpires,
]; ];
$banInfo = $this->usersCtx->tryGetActiveBan($userInfo); $banInfo = $this->usersCtx->tryGetActiveBan($userInfo);
if($banInfo !== null) if($banInfo !== null)
$response['ban'] = [ $response['ban'] = [
'severity' => $banInfo->getSeverity(), 'severity' => $banInfo->severity,
'reason' => $banInfo->getPublicReason(), 'reason' => $banInfo->publicReason,
'created_at' => $banInfo->getCreatedTime(), 'created_at' => $banInfo->createdTime,
'is_permanent' => $banInfo->isPermanent(), 'is_permanent' => $banInfo->permanent,
'expires_at' => $banInfo->getExpiresTime(), 'expires_at' => $banInfo->expiresTime,
'duration_str' => $banInfo->getDurationString(), 'duration_str' => $banInfo->durationString,
'remaining_str' => $banInfo->getRemainingString(), 'remaining_str' => $banInfo->remainingString,
]; ];
$gatherRequestedAvatars = function($userInfo) use ($avatarResolutions, $baseUrl) { $gatherRequestedAvatars = function($userInfo) use ($avatarResolutions, $baseUrl) {
@ -140,13 +138,13 @@ final class HanyuuRpcHandler implements RpcHandler {
$extractUserInfo = fn($userInfo) => [ $extractUserInfo = fn($userInfo) => [
'id' => $userInfo->getId(), 'id' => $userInfo->getId(),
'name' => $userInfo->getName(), 'name' => $userInfo->name,
'colour' => (string)$users->getUserColour($userInfo), 'colour' => (string)$users->getUserColour($userInfo),
'rank' => $users->getUserRank($userInfo), 'rank' => $users->getUserRank($userInfo),
'is_super' => $userInfo->isSuperUser(), 'is_super' => $userInfo->super,
'country_code' => $userInfo->getCountryCode(), 'country_code' => $userInfo->countryCode,
'is_deleted' => $userInfo->isDeleted(), 'is_deleted' => $userInfo->deleted,
'has_totp' => $userInfo->hasTOTPKey(), 'has_totp' => $userInfo->hasTOTP,
'profile_url' => $baseUrl . $this->urls->format('user-profile', ['user' => $userInfo->getId()]), 'profile_url' => $baseUrl . $this->urls->format('user-profile', ['user' => $userInfo->getId()]),
'avatars' => $gatherRequestedAvatars($userInfo), 'avatars' => $gatherRequestedAvatars($userInfo),
]; ];
@ -155,7 +153,7 @@ final class HanyuuRpcHandler implements RpcHandler {
if($userInfo !== $userInfoReal) { if($userInfo !== $userInfoReal) {
$response['guise'] = $extractUserInfo($userInfoReal); $response['guise'] = $extractUserInfo($userInfoReal);
$csrfp = CSRF::create($sessionInfo->getToken()); $csrfp = CSRF::create($sessionInfo->token);
$response['guise']['revert_url'] = $baseUrl . $this->urls->format('auth-revert', ['csrf' => $csrfp->createToken()]); $response['guise']['revert_url'] = $baseUrl . $this->urls->format('auth-revert', ['csrf' => $csrfp->createToken()]);
} }

View file

@ -42,7 +42,7 @@ class HomeRoutes implements RouteHandler, UrlSource {
} }
private function getOnlineUsers(): iterable { private function getOnlineUsers(): iterable {
return $this->usersCtx->getUsers()->getUsers( return $this->usersCtx->users->getUsers(
lastActiveInMinutes: 5, lastActiveInMinutes: 5,
deleted: false, deleted: false,
orderBy: 'random', orderBy: 'random',
@ -63,16 +63,16 @@ class HomeRoutes implements RouteHandler, UrlSource {
$posts = []; $posts = [];
foreach($postInfos as $postInfo) { foreach($postInfos as $postInfo) {
$categoryId = $postInfo->getCategoryId(); $categoryId = $postInfo->categoryId;
$userInfo = $postInfo->hasUserId() ? $this->usersCtx->getUserInfo($postInfo->getUserId()) : null; $userInfo = $postInfo->userId !== null ? $this->usersCtx->getUserInfo($postInfo->userId) : null;
if(array_key_exists($categoryId, $this->newsCategoryInfos)) if(array_key_exists($categoryId, $this->newsCategoryInfos))
$categoryInfo = $this->newsCategoryInfos[$categoryId]; $categoryInfo = $this->newsCategoryInfos[$categoryId];
else else
$this->newsCategoryInfos[$categoryId] = $categoryInfo = $this->news->getCategory(postInfo: $postInfo); $this->newsCategoryInfos[$categoryId] = $categoryInfo = $this->news->getCategory(postInfo: $postInfo);
$commentsCount = $postInfo->hasCommentsCategoryId() $commentsCount = $postInfo->commentsSectionId
? $this->comments->countPosts(categoryInfo: $postInfo->getCommentsCategoryId(), deleted: false) : 0; ? $this->comments->countPosts(categoryInfo: $postInfo->commentsSectionId, deleted: false) : 0;
$posts[] = [ $posts[] = [
'post' => $postInfo, 'post' => $postInfo,
@ -155,7 +155,7 @@ class HomeRoutes implements RouteHandler, UrlSource {
#[HttpGet('/')] #[HttpGet('/')]
#[UrlFormat('index', '/')] #[UrlFormat('index', '/')]
public function getIndex(...$args) { public function getIndex(...$args) {
return $this->authInfo->isLoggedIn() return $this->authInfo->isLoggedIn
? $this->getHome(...$args) ? $this->getHome(...$args)
: $this->getLanding(...$args); : $this->getLanding(...$args);
} }
@ -169,7 +169,7 @@ class HomeRoutes implements RouteHandler, UrlSource {
$stats['users:online:recent'] = count($onlineUserInfos); $stats['users:online:recent'] = count($onlineUserInfos);
$birthdays = []; $birthdays = [];
$birthdayInfos = $this->usersCtx->getUsers()->getUsers(deleted: false, birthdate: XDateTime::now(), orderBy: 'random'); $birthdayInfos = $this->usersCtx->users->getUsers(deleted: false, birthdate: XDateTime::now(), orderBy: 'random');
foreach($birthdayInfos as $birthdayInfo) foreach($birthdayInfos as $birthdayInfo)
$birthdays[] = [ $birthdays[] = [
'info' => $birthdayInfo, 'info' => $birthdayInfo,
@ -211,9 +211,9 @@ class HomeRoutes implements RouteHandler, UrlSource {
$linkedData = [ $linkedData = [
'@context' => 'http://schema.org', '@context' => 'http://schema.org',
'@type' => 'Organization', '@type' => 'Organization',
'name' => $this->siteInfo->getName(), 'name' => $this->siteInfo->name,
'url' => $this->siteInfo->getURL(), 'url' => $this->siteInfo->url,
'logo' => $this->siteInfo->getExternalLogo(), 'logo' => $this->siteInfo->externalLogo,
'same_as' => $config['social.linked'], 'same_as' => $config['social.linked'],
]; ];
} else $linkedData = null; } else $linkedData = null;

View file

@ -42,9 +42,8 @@ final class GdImage extends Image {
} }
} }
if(!isset($this->gd)) { if(!isset($this->gd))
throw new InvalidArgumentException('Unsupported image format.'); throw new InvalidArgumentException('Unsupported image format.');
}
} }
public function __destruct() { public function __destruct() {

View file

@ -7,146 +7,82 @@ use Index\Db\DbResult;
class MessageInfo { class MessageInfo {
public function __construct( public function __construct(
private string $messageId, public private(set) string $id,
private string $ownerId, public private(set) string $ownerId,
private ?string $authorId, public private(set) ?string $authorId,
private ?string $recipientId, public private(set) ?string $recipientId,
private ?string $replyTo, public private(set) ?string $replyToId,
private string $title, public private(set) string $title,
private string $body, public private(set) string $body,
private int $parser, public private(set) int $parser,
private int $created, public private(set) int $createdTime,
private ?int $sent, public private(set) ?int $sentTime,
private ?int $read, public private(set) ?int $readTime,
private ?int $deleted, public private(set) ?int $deletedTime,
) {} ) {}
public static function fromResult(DbResult $result): MessageInfo { public static function fromResult(DbResult $result): MessageInfo {
return new MessageInfo( return new MessageInfo(
messageId: $result->getString(0), id: $result->getString(0),
ownerId: $result->getString(1), ownerId: $result->getString(1),
authorId: $result->getStringOrNull(2), authorId: $result->getStringOrNull(2),
recipientId: $result->getStringOrNull(3), recipientId: $result->getStringOrNull(3),
replyTo: $result->getStringOrNull(4), replyToId: $result->getStringOrNull(4),
title: $result->getString(5), title: $result->getString(5),
body: $result->getString(6), body: $result->getString(6),
parser: $result->getInteger(7), parser: $result->getInteger(7),
created: $result->getInteger(8), createdTime: $result->getInteger(8),
sent: $result->getIntegerOrNull(9), sentTime: $result->getIntegerOrNull(9),
read: $result->getIntegerOrNull(10), readTime: $result->getIntegerOrNull(10),
deleted: $result->getIntegerOrNull(11), deletedTime: $result->getIntegerOrNull(11),
); );
} }
public function getId(): string { public bool $isBodyPlain {
return $this->messageId; get => $this->parser === Parser::PLAIN;
} }
public function getOwnerId(): string { public bool $isBodyBBCode {
return $this->ownerId; get => $this->parser === Parser::BBCODE;
} }
public function hasAuthorId(): bool { public bool $isBodyMarkdown {
return $this->authorId !== null; get => $this->parser === Parser::MARKDOWN;
} }
public function getAuthorId(): ?string { public CarbonImmutable $createdAt {
return $this->authorId; get => CarbonImmutable::createFromTimestampUTC($this->createdTime);
} }
public function hasRecipientId(): bool { public bool $sent {
return $this->recipientId !== null; get => $this->sentTime !== null;
} }
public function getRecipientId(): ?string { public ?CarbonImmutable $sentAt {
return $this->recipientId; get => $this->sentTime === null ? null : CarbonImmutable::createFromTimestampUTC($this->sentTime);
} }
public function hasReplyToId(): bool { public bool $read {
return $this->replyTo !== null; get => $this->readTime !== null;
} }
public function getReplyToId(): ?string { public ?CarbonImmutable $readAt {
return $this->replyTo; get => $this->readTime === null ? null : CarbonImmutable::createFromTimestampUTC($this->readTime);
} }
public function getTitle(): string { public bool $deleted {
return $this->title; get => $this->deletedTime !== null;
} }
public function getBody(): string { public ?CarbonImmutable $deletedAt {
return $this->body; get => $this->deletedTime === null ? null : CarbonImmutable::createFromTimestampUTC($this->deletedTime);
} }
public function getParser(): int { public int $displayTime {
return $this->parser; get => $this->sent ? $this->sentTime : $this->createdTime;
} }
public function isBodyPlain(): bool { public CarbonImmutable $displayAt {
return $this->parser === Parser::PLAIN; get => $this->sent ? $this->sentAt : $this->createdAt;
}
public function isBodyBBCode(): bool {
return $this->parser === Parser::BBCODE;
}
public function isBodyMarkdown(): bool {
return $this->parser === Parser::MARKDOWN;
}
public function getCreatedTime(): int {
return $this->created;
}
public function getCreatedAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->created);
}
public function isSent(): bool {
return $this->sent !== null;
}
public function getSentTime(): ?int {
return $this->sent;
}
public function getSentAt(): ?CarbonImmutable {
return $this->sent === null ? null : CarbonImmutable::createFromTimestampUTC($this->sent);
}
public function isRead(): bool {
return $this->read !== null;
}
public function getReadTime(): ?int {
return $this->read;
}
public function getReadAt(): ?CarbonImmutable {
return $this->read === null ? null : CarbonImmutable::createFromTimestampUTC($this->read);
}
public function isDeleted(): bool {
return $this->deleted !== null;
}
public function getDeletedTime(): ?int {
return $this->deleted;
}
public function getDeletedAt(): ?CarbonImmutable {
return $this->deleted === null ? null : CarbonImmutable::createFromTimestampUTC($this->deleted);
}
public function getDisplayTime(): int {
if($this->isSent())
return $this->getSentTime();
return $this->getCreatedTime();
}
public function getDisplayAt(): CarbonImmutable {
if($this->isSent())
return $this->getSentAt();
return $this->getCreatedAt();
} }
} }

View file

@ -4,13 +4,9 @@ namespace Misuzu\Messages;
use Index\Db\DbConnection; use Index\Db\DbConnection;
class MessagesContext { class MessagesContext {
private MessagesDatabase $database; public private(set) MessagesDatabase $database;
public function __construct(DbConnection $dbConn) { public function __construct(DbConnection $dbConn) {
$this->database = new MessagesDatabase($dbConn); $this->database = new MessagesDatabase($dbConn);
} }
public function getDatabase(): MessagesDatabase {
return $this->database;
}
} }

View file

@ -72,9 +72,9 @@ class MessagesDatabase {
if($hasRecipientInfo) if($hasRecipientInfo)
$stmt->addParameter(++$args, $recipientInfo instanceof UserInfo ? $recipientInfo->getId() : $recipientInfo); $stmt->addParameter(++$args, $recipientInfo instanceof UserInfo ? $recipientInfo->getId() : $recipientInfo);
if($hasRepliesFor) if($hasRepliesFor)
$stmt->addParameter(++$args, $repliesFor instanceof MessageInfo ? $repliesFor->getId() : $repliesFor); $stmt->addParameter(++$args, $repliesFor instanceof MessageInfo ? $repliesFor->id : $repliesFor);
if($hasReplyTo) if($hasReplyTo)
$stmt->addParameter(++$args, $replyTo instanceof MessageInfo ? $replyTo->getReplyToId() : $replyTo); $stmt->addParameter(++$args, $replyTo instanceof MessageInfo ? $replyTo->replyToId : $replyTo);
$stmt->execute(); $stmt->execute();
$result = $stmt->getResult(); $result = $stmt->getResult();
@ -143,9 +143,9 @@ class MessagesDatabase {
if($hasRecipientInfo) if($hasRecipientInfo)
$stmt->addParameter(++$args, $recipientInfo instanceof UserInfo ? $recipientInfo->getId() : $recipientInfo); $stmt->addParameter(++$args, $recipientInfo instanceof UserInfo ? $recipientInfo->getId() : $recipientInfo);
if($hasRepliesFor) if($hasRepliesFor)
$stmt->addParameter(++$args, $repliesFor instanceof MessageInfo ? $repliesFor->getId() : $repliesFor); $stmt->addParameter(++$args, $repliesFor instanceof MessageInfo ? $repliesFor->id : $repliesFor);
if($hasReplyTo) if($hasReplyTo)
$stmt->addParameter(++$args, $replyTo instanceof MessageInfo ? $replyTo->getReplyToId() : $replyTo); $stmt->addParameter(++$args, $replyTo instanceof MessageInfo ? $replyTo->replyToId : $replyTo);
if($hasPagination) { if($hasPagination) {
$stmt->addParameter(++$args, $pagination->getRange()); $stmt->addParameter(++$args, $pagination->getRange());
$stmt->addParameter(++$args, $pagination->getOffset()); $stmt->addParameter(++$args, $pagination->getOffset());
@ -167,7 +167,7 @@ class MessagesDatabase {
)); ));
if($messageInfoOrId instanceof MessageInfo) if($messageInfoOrId instanceof MessageInfo)
$stmt->addParameter(1, $useReplyTo ? $messageInfoOrId->getReplyToId() : $messageInfoOrId->getId()); $stmt->addParameter(1, $useReplyTo ? $messageInfoOrId->replyToId : $messageInfoOrId->id);
else else
$stmt->addParameter(1, $messageInfoOrId); $stmt->addParameter(1, $messageInfoOrId);
$stmt->addParameter(2, $ownerInfo instanceof UserInfo ? $ownerInfo->getId() : $ownerInfo); $stmt->addParameter(2, $ownerInfo instanceof UserInfo ? $ownerInfo->getId() : $ownerInfo);
@ -197,7 +197,7 @@ class MessagesDatabase {
$stmt->addParameter(2, $ownerInfo instanceof UserInfo ? $ownerInfo->getId() : $ownerInfo); $stmt->addParameter(2, $ownerInfo instanceof UserInfo ? $ownerInfo->getId() : $ownerInfo);
$stmt->addParameter(3, $authorInfo instanceof UserInfo ? $authorInfo->getId() : $authorInfo); $stmt->addParameter(3, $authorInfo instanceof UserInfo ? $authorInfo->getId() : $authorInfo);
$stmt->addParameter(4, $recipientInfo instanceof UserInfo ? $recipientInfo->getId() : $recipientInfo); $stmt->addParameter(4, $recipientInfo instanceof UserInfo ? $recipientInfo->getId() : $recipientInfo);
$stmt->addParameter(5, $replyTo instanceof MessageInfo ? $replyTo->getId() : $replyTo); $stmt->addParameter(5, $replyTo instanceof MessageInfo ? $replyTo->id : $replyTo);
$stmt->addParameter(6, $title); $stmt->addParameter(6, $title);
$stmt->addParameter(7, $body); $stmt->addParameter(7, $body);
$stmt->addParameter(8, $parser); $stmt->addParameter(8, $parser);
@ -229,7 +229,7 @@ class MessagesDatabase {
if($messageInfo !== null) { if($messageInfo !== null) {
$whereQuery[] = 'msg_id = ?'; $whereQuery[] = 'msg_id = ?';
$whereValues[] = $messageInfo instanceof MessageInfo ? $messageInfo->getId() : $messageInfo; $whereValues[] = $messageInfo instanceof MessageInfo ? $messageInfo->id : $messageInfo;
} }
if($title !== null) { if($title !== null) {
@ -307,7 +307,7 @@ class MessagesDatabase {
if(is_string($messageInfo)) if(is_string($messageInfo))
$stmt->addParameter(++$args, $messageInfo); $stmt->addParameter(++$args, $messageInfo);
elseif($messageInfo instanceof MessageInfo) elseif($messageInfo instanceof MessageInfo)
$stmt->addParameter(++$args, $messageInfo->getId()); $stmt->addParameter(++$args, $messageInfo->id);
else else
throw new InvalidArgumentException('$messageInfos must be an array of strings or MessageInfo instances.'); throw new InvalidArgumentException('$messageInfos must be an array of strings or MessageInfo instances.');
} }
@ -345,7 +345,7 @@ class MessagesDatabase {
if(is_string($messageInfo)) if(is_string($messageInfo))
$stmt->addParameter(++$args, $messageInfo); $stmt->addParameter(++$args, $messageInfo);
elseif($messageInfo instanceof MessageInfo) elseif($messageInfo instanceof MessageInfo)
$stmt->addParameter(++$args, $messageInfo->getId()); $stmt->addParameter(++$args, $messageInfo->id);
else else
throw new InvalidArgumentException('$messageInfos must be an array of strings or MessageInfo instances.'); throw new InvalidArgumentException('$messageInfos must be an array of strings or MessageInfo instances.');
} }
@ -383,7 +383,7 @@ class MessagesDatabase {
if(is_string($messageInfo)) if(is_string($messageInfo))
$stmt->addParameter(++$args, $messageInfo); $stmt->addParameter(++$args, $messageInfo);
elseif($messageInfo instanceof MessageInfo) elseif($messageInfo instanceof MessageInfo)
$stmt->addParameter(++$args, $messageInfo->getId()); $stmt->addParameter(++$args, $messageInfo->id);
else else
throw new InvalidArgumentException('$messageInfos must be an array of strings or MessageInfo instances.'); throw new InvalidArgumentException('$messageInfos must be an array of strings or MessageInfo instances.');
} }

View file

@ -38,11 +38,11 @@ class MessagesRoutes implements RouteHandler, UrlSource {
#[HttpMiddleware('/messages')] #[HttpMiddleware('/messages')]
public function checkAccess($response, $request) { public function checkAccess($response, $request) {
// should probably be a permission or something too // should probably be a permission or something too
if(!$this->authInfo->isLoggedIn()) if(!$this->authInfo->isLoggedIn)
return 401; return 401;
// do not allow access to PMs when impersonating in production mode // do not allow access to PMs when impersonating in production mode
if(!MSZ_DEBUG && $this->authInfo->isImpersonating()) if(!MSZ_DEBUG && $this->authInfo->isImpersonating)
return 403; return 403;
$globalPerms = $this->authInfo->getPerms('global'); $globalPerms = $this->authInfo->getPerms('global');
@ -50,7 +50,7 @@ class MessagesRoutes implements RouteHandler, UrlSource {
return 403; return 403;
$this->canSendMessages = $globalPerms->check(Perm::G_MESSAGES_SEND) $this->canSendMessages = $globalPerms->check(Perm::G_MESSAGES_SEND)
&& !$this->usersCtx->hasActiveBan($this->authInfo->getUserInfo()); && !$this->usersCtx->hasActiveBan($this->authInfo->userInfo);
if($request->getMethod() === 'POST' && $request->isFormContent()) { if($request->getMethod() === 'POST' && $request->isFormContent()) {
$content = $request->getContent(); $content = $request->getContent();
@ -70,9 +70,9 @@ class MessagesRoutes implements RouteHandler, UrlSource {
$message = new stdClass; $message = new stdClass;
$message->info = $messageInfo; $message->info = $messageInfo;
$message->author_info = $messageInfo->hasAuthorId() ? $this->usersCtx->getUserInfo($messageInfo->getAuthorId(), 'id') : null; $message->author_info = $messageInfo->authorId !== null ? $this->usersCtx->getUserInfo($messageInfo->authorId, 'id') : null;
$message->author_colour = $this->usersCtx->getUserColour($message->author_info); $message->author_colour = $this->usersCtx->getUserColour($message->author_info);
$message->recipient_info = $messageInfo->hasRecipientId() ? $this->usersCtx->getUserInfo($messageInfo->getRecipientId(), 'id') : null; $message->recipient_info = $messageInfo->recipientId !== null ? $this->usersCtx->getUserInfo($messageInfo->recipientId, 'id') : null;
$message->recipient_colour = $this->usersCtx->getUserColour($message->recipient_info); $message->recipient_colour = $this->usersCtx->getUserColour($message->recipient_info);
return $message; return $message;
@ -93,8 +93,8 @@ class MessagesRoutes implements RouteHandler, UrlSource {
$folderSent = $folderName === 'sent'; $folderSent = $folderName === 'sent';
$folderTrash = $folderName === 'trash'; $folderTrash = $folderName === 'trash';
$selfInfo = $this->authInfo->getUserInfo(); $selfInfo = $this->authInfo->userInfo;
$msgsDb = $this->msgsCtx->getDatabase(); $msgsDb = $this->msgsCtx->database;
$authorInfo = !$folderTrash && $folderSent ? $selfInfo : null; $authorInfo = !$folderTrash && $folderSent ? $selfInfo : null;
$recipientInfo = !$folderTrash && $folderInbox ? $selfInfo : null; $recipientInfo = !$folderTrash && $folderInbox ? $selfInfo : null;
@ -134,8 +134,8 @@ class MessagesRoutes implements RouteHandler, UrlSource {
#[HttpGet('/messages/stats')] #[HttpGet('/messages/stats')]
#[UrlFormat('messages-stats', '/messages/stats')] #[UrlFormat('messages-stats', '/messages/stats')]
public function getStats() { public function getStats() {
$selfInfo = $this->authInfo->getUserInfo(); $selfInfo = $this->authInfo->userInfo;
$msgsDb = $this->msgsCtx->getDatabase(); $msgsDb = $this->msgsCtx->database;
return [ return [
'unread' => $msgsDb->countMessages( 'unread' => $msgsDb->countMessages(
@ -179,7 +179,7 @@ class MessagesRoutes implements RouteHandler, UrlSource {
return [ return [
'id' => $userInfo->getId(), 'id' => $userInfo->getId(),
'name' => $userInfo->getName(), 'name' => $userInfo->name,
'ban' => $this->usersCtx->hasActiveBan($userInfo), 'ban' => $this->usersCtx->hasActiveBan($userInfo),
'avatar' => $this->urls->format('user-avatar', [ 'avatar' => $this->urls->format('user-avatar', [
'user' => $userInfo->getId(), 'user' => $userInfo->getId(),
@ -205,8 +205,8 @@ class MessagesRoutes implements RouteHandler, UrlSource {
if(strlen($messageId) !== 8) if(strlen($messageId) !== 8)
return 404; return 404;
$selfInfo = $this->authInfo->getUserInfo(); $selfInfo = $this->authInfo->userInfo;
$msgsDb = $this->msgsCtx->getDatabase(); $msgsDb = $this->msgsCtx->database;
try { try {
$messageInfo = $msgsDb->getMessageInfo($selfInfo, $messageId); $messageInfo = $msgsDb->getMessageInfo($selfInfo, $messageId);
@ -214,7 +214,7 @@ class MessagesRoutes implements RouteHandler, UrlSource {
return 404; return 404;
} }
if(!$messageInfo->isRead()) if(!$messageInfo->read)
$msgsDb->updateMessage( $msgsDb->updateMessage(
ownerInfo: $selfInfo, ownerInfo: $selfInfo,
messageInfo: $messageInfo, messageInfo: $messageInfo,
@ -224,7 +224,7 @@ class MessagesRoutes implements RouteHandler, UrlSource {
$message = $this->populateMessage($messageInfo); $message = $this->populateMessage($messageInfo);
$replyTo = null; $replyTo = null;
if($messageInfo->hasReplyToId()) { if($messageInfo->replyToId !== null) {
try { try {
$replyTo = $this->populateMessage( $replyTo = $this->populateMessage(
$msgsDb->getMessageInfo($selfInfo, $messageInfo, true) $msgsDb->getMessageInfo($selfInfo, $messageInfo, true)
@ -243,7 +243,7 @@ class MessagesRoutes implements RouteHandler, UrlSource {
foreach($repliesForInfos as $repliesForInfo) { foreach($repliesForInfos as $repliesForInfo) {
$repliesFor[] = $this->populateMessage($repliesForInfo); $repliesFor[] = $this->populateMessage($repliesForInfo);
if(!$repliesForInfo->isSent() && $draftInfo === null) if(!$repliesForInfo->sent && $draftInfo === null)
$draftInfo = $repliesForInfo; $draftInfo = $repliesForInfo;
} }
@ -349,8 +349,8 @@ class MessagesRoutes implements RouteHandler, UrlSource {
if($error !== null) if($error !== null)
return $error; return $error;
$selfInfo = $this->authInfo->getUserInfo(); $selfInfo = $this->authInfo->userInfo;
$msgsDb = $this->msgsCtx->getDatabase(); $msgsDb = $this->msgsCtx->database;
try { try {
$recipientInfo = $this->usersCtx->getUserInfo($recipient, 'messaging'); $recipientInfo = $this->usersCtx->getUserInfo($recipient, 'messaging');
@ -387,7 +387,7 @@ class MessagesRoutes implements RouteHandler, UrlSource {
]; ];
} }
if(!$replyToInfo->isSent()) if(!$replyToInfo->sent)
return [ return [
'error' => [ 'error' => [
'name' => 'msgs:draft_reply', 'name' => 'msgs:draft_reply',
@ -450,8 +450,8 @@ class MessagesRoutes implements RouteHandler, UrlSource {
if($error !== null) if($error !== null)
return $error; return $error;
$selfInfo = $this->authInfo->getUserInfo(); $selfInfo = $this->authInfo->userInfo;
$msgsDb = $this->msgsCtx->getDatabase(); $msgsDb = $this->msgsCtx->database;
try { try {
$messageInfo = $msgsDb->getMessageInfo($selfInfo, $messageId); $messageInfo = $msgsDb->getMessageInfo($selfInfo, $messageId);
@ -464,7 +464,7 @@ class MessagesRoutes implements RouteHandler, UrlSource {
]; ];
} }
if(!$messageInfo->hasAuthorId() || $messageInfo->getAuthorId() !== $selfInfo->getId()) if($messageInfo->authorId === null || $messageInfo->authorId !== $selfInfo->getId())
return [ return [
'error' => [ 'error' => [
'name' => 'msgs:not_author', 'name' => 'msgs:not_author',
@ -472,7 +472,7 @@ class MessagesRoutes implements RouteHandler, UrlSource {
], ],
]; ];
if(!$messageInfo->hasRecipientId()) if($messageInfo->recipientId === null)
return [ return [
'error' => [ 'error' => [
'name' => 'msgs:recipient_gone', 'name' => 'msgs:recipient_gone',
@ -480,7 +480,7 @@ class MessagesRoutes implements RouteHandler, UrlSource {
], ],
]; ];
if($messageInfo->isSent()) if($messageInfo->sent)
return [ return [
'error' => [ 'error' => [
'name' => 'msgs:not_draft', 'name' => 'msgs:not_draft',
@ -488,7 +488,7 @@ class MessagesRoutes implements RouteHandler, UrlSource {
], ],
]; ];
$error = $this->checkCanReceiveMessages($messageInfo->getRecipientId()); $error = $this->checkCanReceiveMessages($messageInfo->recipientId);
if($error !== null) if($error !== null)
return $error; return $error;
@ -504,16 +504,16 @@ class MessagesRoutes implements RouteHandler, UrlSource {
); );
// recipient copy // recipient copy
if($sentAt !== null && $messageInfo->getRecipientId() !== $selfInfo->getId()) if($sentAt !== null && $messageInfo->recipientId !== $selfInfo->getId())
$msgsDb->createMessage( $msgsDb->createMessage(
messageId: $messageId, messageId: $messageId,
ownerInfo: $messageInfo->getRecipientId(), ownerInfo: $messageInfo->recipientId,
authorInfo: $selfInfo, authorInfo: $selfInfo,
recipientInfo: $messageInfo->getRecipientId(), recipientInfo: $messageInfo->recipientId,
title: $title, title: $title,
body: $body, body: $body,
parser: $parser, parser: $parser,
replyTo: $messageInfo->getReplyToId(), replyTo: $messageInfo->replyToId,
sentAt: $sentAt sentAt: $sentAt
); );
@ -541,8 +541,8 @@ class MessagesRoutes implements RouteHandler, UrlSource {
], ],
]; ];
$selfInfo = $this->authInfo->getUserInfo(); $selfInfo = $this->authInfo->userInfo;
$msgsDb = $this->msgsCtx->getDatabase(); $msgsDb = $this->msgsCtx->database;
foreach($messages as $messageId) foreach($messages as $messageId)
$msgsDb->updateMessage( $msgsDb->updateMessage(
@ -573,8 +573,8 @@ class MessagesRoutes implements RouteHandler, UrlSource {
$messages = explode(',', $messages); $messages = explode(',', $messages);
$this->msgsCtx->getDatabase()->deleteMessages( $this->msgsCtx->database->deleteMessages(
$this->authInfo->getUserInfo(), $this->authInfo->userInfo,
$messages $messages
); );
@ -600,8 +600,8 @@ class MessagesRoutes implements RouteHandler, UrlSource {
$messages = explode(',', $messages); $messages = explode(',', $messages);
$this->msgsCtx->getDatabase()->restoreMessages( $this->msgsCtx->database->restoreMessages(
$this->authInfo->getUserInfo(), $this->authInfo->userInfo,
$messages $messages
); );
@ -627,8 +627,8 @@ class MessagesRoutes implements RouteHandler, UrlSource {
$messages = explode(',', $messages); $messages = explode(',', $messages);
$this->msgsCtx->getDatabase()->nukeMessages( $this->msgsCtx->database->nukeMessages(
$this->authInfo->getUserInfo(), $this->authInfo->userInfo,
$messages $messages
); );

View file

@ -29,36 +29,34 @@ use Index\Urls\UrlRegistry;
// dunno if i want null checks some maybe some kind of init func should be called first like is the case // dunno if i want null checks some maybe some kind of init func should be called first like is the case
// with the http shit // with the http shit
class MisuzuContext { class MisuzuContext {
private DbConnection $dbConn;
private Config $config;
private TplEnvironment $templating; private TplEnvironment $templating;
private AuditLog $auditLog; public private(set) AuditLog $auditLog;
private Counters $counters; public private(set) Counters $counters;
private Emotes $emotes; public private(set) Emotes $emotes;
private Changelog $changelog; public private(set) Changelog $changelog;
private News $news; public private(set) News $news;
private Comments $comments; public private(set) Comments $comments;
private AuthContext $authCtx; public private(set) AuthContext $authCtx;
private ForumContext $forumCtx; public private(set) ForumContext $forumCtx;
private MessagesContext $messagesCtx; private MessagesContext $messagesCtx;
private UsersContext $usersCtx; public private(set) UsersContext $usersCtx;
private ProfileFields $profileFields; public private(set) ProfileFields $profileFields;
private Permissions $perms; public private(set) Permissions $perms;
private AuthInfo $authInfo; public private(set) AuthInfo $authInfo;
private SiteInfo $siteInfo; public private(set) SiteInfo $siteInfo;
// this probably shouldn't be available // this probably shouldn't be available
private UrlRegistry $urls; public private(set) UrlRegistry $urls;
public function __construct(DbConnection $dbConn, Config $config) {
$this->dbConn = $dbConn;
$this->config = $config;
public function __construct(
public private(set) DbConnection $dbConn,
public private(set) Config $config
) {
$this->perms = new Permissions($dbConn); $this->perms = new Permissions($dbConn);
$this->authInfo = new AuthInfo($this->perms); $this->authInfo = new AuthInfo($this->perms);
$this->siteInfo = new SiteInfo($config->scopeTo('site')); $this->siteInfo = new SiteInfo($config->scopeTo('site'));
@ -77,10 +75,6 @@ class MisuzuContext {
$this->profileFields = new ProfileFields($dbConn); $this->profileFields = new ProfileFields($dbConn);
} }
public function getDbConn(): DbConnection {
return $this->dbConn;
}
public function getDbQueryCount(): int { public function getDbQueryCount(): int {
$result = $this->dbConn->query('SHOW SESSION STATUS LIKE "Questions"'); $result = $this->dbConn->query('SHOW SESSION STATUS LIKE "Questions"');
return $result->next() ? $result->getInteger(1) : 0; return $result->next() ? $result->getInteger(1) : 0;
@ -94,69 +88,9 @@ class MisuzuContext {
return new FsDbMigrationRepo(MSZ_MIGRATIONS); return new FsDbMigrationRepo(MSZ_MIGRATIONS);
} }
public function getUrls(): UrlRegistry {
return $this->urls;
}
public function getConfig(): Config {
return $this->config;
}
public function getEmotes(): Emotes {
return $this->emotes;
}
public function getChangelog(): Changelog {
return $this->changelog;
}
public function getNews(): News {
return $this->news;
}
public function getComments(): Comments {
return $this->comments;
}
public function getAuditLog(): AuditLog {
return $this->auditLog;
}
public function getCounters(): Counters {
return $this->counters;
}
public function getProfileFields(): ProfileFields {
return $this->profileFields;
}
public function getPerms(): Permissions {
return $this->perms;
}
public function getAuthContext(): AuthContext {
return $this->authCtx;
}
public function getUsersContext(): UsersContext {
return $this->usersCtx;
}
public function getForumContext(): ForumContext {
return $this->forumCtx;
}
public function getAuthInfo(): AuthInfo {
return $this->authInfo;
}
public function getSiteInfo(): SiteInfo {
return $this->siteInfo;
}
public function createAuditLog(string $action, array $params = [], UserInfo|string|null $userInfo = null): void { public function createAuditLog(string $action, array $params = [], UserInfo|string|null $userInfo = null): void {
if($userInfo === null && $this->authInfo->isLoggedIn()) if($userInfo === null && $this->authInfo->isLoggedIn)
$userInfo = $this->authInfo->getUserInfo(); $userInfo = $this->authInfo->userInfo;
$this->auditLog->createLog( $this->auditLog->createLog(
$userInfo, $userInfo,
@ -169,8 +103,8 @@ class MisuzuContext {
private ?bool $hasManageAccess = null; private ?bool $hasManageAccess = null;
public function hasManageAccess(): bool { public function hasManageAccess(): bool {
$this->hasManageAccess ??= $this->authInfo->isLoggedIn() $this->hasManageAccess ??= $this->authInfo->isLoggedIn
&& !$this->usersCtx->hasActiveBan($this->authInfo->getUserInfo()) && !$this->usersCtx->hasActiveBan($this->authInfo->userInfo)
&& $this->authInfo->getPerms('global')->check(Perm::G_IS_JANITOR); && $this->authInfo->getPerms('global')->check(Perm::G_IS_JANITOR);
return $this->hasManageAccess; return $this->hasManageAccess;
} }
@ -195,7 +129,7 @@ class MisuzuContext {
$isDebug = MSZ_DEBUG; $isDebug = MSZ_DEBUG;
$globals['site_info'] = $this->siteInfo; $globals['site_info'] = $this->siteInfo;
$globals['auth_info'] = $this->authInfo; $globals['auth_info'] = $this->authInfo;
$globals['active_ban_info'] = $this->usersCtx->tryGetActiveBan($this->authInfo->getUserInfo()); $globals['active_ban_info'] = $this->usersCtx->tryGetActiveBan($this->authInfo->userInfo);
$globals['display_timings_info'] = $isDebug || $this->authInfo->getPerms('global')->check(Perm::G_TIMINGS_VIEW); $globals['display_timings_info'] = $isDebug || $this->authInfo->getPerms('global')->check(Perm::G_TIMINGS_VIEW);
$this->templating = new TplEnvironment( $this->templating = new TplEnvironment(
@ -210,9 +144,8 @@ class MisuzuContext {
} }
public function createRouting(): RoutingContext { public function createRouting(): RoutingContext {
$routingCtx = new RoutingContext(); $routingCtx = new RoutingContext;
$this->urls = $routingCtx->urls;
$this->urls = $routingCtx->getUrls();
$routingCtx->register(new \Misuzu\Home\HomeRoutes( $routingCtx->register(new \Misuzu\Home\HomeRoutes(
$this->config, $this->config,
@ -283,7 +216,7 @@ class MisuzuContext {
$routingCtx->register(new LegacyRoutes($this->urls)); $routingCtx->register(new LegacyRoutes($this->urls));
$rpcServer = new HttpRpcServer; $rpcServer = new HttpRpcServer;
$routingCtx->getRouter()->register($rpcServer->createRouteHandler( $routingCtx->router->register($rpcServer->createRouteHandler(
new HmacVerificationProvider(fn() => $this->config->getString('aleister.secret')) new HmacVerificationProvider(fn() => $this->config->getString('aleister.secret'))
)); ));
@ -305,7 +238,7 @@ class MisuzuContext {
// This RPC server will eventually despawn when Hanyuu fully owns auth // This RPC server will eventually despawn when Hanyuu fully owns auth
$hanyuuRpcServer = new HttpRpcServer; $hanyuuRpcServer = new HttpRpcServer;
$routingCtx->getRouter()->scopeTo('/_hanyuu')->register($hanyuuRpcServer->createRouteHandler( $routingCtx->router->scopeTo('/_hanyuu')->register($hanyuuRpcServer->createRouteHandler(
new HmacVerificationProvider(fn() => $this->config->getString('hanyuu.secret')) new HmacVerificationProvider(fn() => $this->config->getString('hanyuu.secret'))
)); ));

View file

@ -10,11 +10,11 @@ use Misuzu\Comments\CommentsCategoryInfo;
use Misuzu\Users\UserInfo; use Misuzu\Users\UserInfo;
class News { class News {
private DbConnection $dbConn;
private DbStatementCache $cache; private DbStatementCache $cache;
public function __construct(DbConnection $dbConn) { public function __construct(
$this->dbConn = $dbConn; private DbConnection $dbConn
) {
$this->cache = new DbStatementCache($dbConn); $this->cache = new DbStatementCache($dbConn);
} }
@ -84,7 +84,7 @@ class News {
if($hasPostInfo) { if($hasPostInfo) {
if($postInfo instanceof NewsPostInfo) { if($postInfo instanceof NewsPostInfo) {
$query .= ' WHERE category_id = ?'; $query .= ' WHERE category_id = ?';
$value = $postInfo->getCategoryId(); $value = $postInfo->categoryId;
} else { } else {
$query .= ' WHERE category_id = (SELECT category_id FROM msz_news_posts WHERE post_id = ?)'; $query .= ' WHERE category_id = (SELECT category_id FROM msz_news_posts WHERE post_id = ?)';
$value = $postInfo; $value = $postInfo;
@ -126,7 +126,7 @@ class News {
public function deleteCategory(NewsCategoryInfo|string $infoOrId): void { public function deleteCategory(NewsCategoryInfo|string $infoOrId): void {
if($infoOrId instanceof NewsCategoryInfo) if($infoOrId instanceof NewsCategoryInfo)
$infoOrId = $infoOrId->getId(); $infoOrId = $infoOrId->id;
$stmt = $this->cache->get('DELETE FROM msz_news_categories WHERE category_id = ?'); $stmt = $this->cache->get('DELETE FROM msz_news_categories WHERE category_id = ?');
$stmt->addParameter(1, $infoOrId); $stmt->addParameter(1, $infoOrId);
@ -140,7 +140,7 @@ class News {
?bool $hidden = null ?bool $hidden = null
): void { ): void {
if($infoOrId instanceof NewsCategoryInfo) if($infoOrId instanceof NewsCategoryInfo)
$infoOrId = $infoOrId->getId(); $infoOrId = $infoOrId->id;
if($name !== null) { if($name !== null) {
$name = trim($name); $name = trim($name);
@ -172,7 +172,7 @@ class News {
bool $includeDeleted = false bool $includeDeleted = false
): int { ): int {
if($categoryInfo instanceof NewsCategoryInfo) if($categoryInfo instanceof NewsCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
$hasCategoryInfo = $categoryInfo !== null; $hasCategoryInfo = $categoryInfo !== null;
@ -218,7 +218,7 @@ class News {
?Pagination $pagination = null ?Pagination $pagination = null
): iterable { ): iterable {
if($categoryInfo instanceof NewsCategoryInfo) if($categoryInfo instanceof NewsCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
$hasCategoryInfo = $categoryInfo !== null; $hasCategoryInfo = $categoryInfo !== null;
$hasSearchQuery = $searchQuery !== null; $hasSearchQuery = $searchQuery !== null;
@ -287,7 +287,7 @@ class News {
DateTimeInterface|int|null $schedule = null DateTimeInterface|int|null $schedule = null
): NewsPostInfo { ): NewsPostInfo {
if($categoryInfo instanceof NewsCategoryInfo) if($categoryInfo instanceof NewsCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
if($userInfo instanceof UserInfo) if($userInfo instanceof UserInfo)
$userInfo = $userInfo->getId(); $userInfo = $userInfo->getId();
if($schedule instanceof DateTimeInterface) if($schedule instanceof DateTimeInterface)
@ -315,7 +315,7 @@ class News {
public function deletePost(NewsPostInfo|string $postInfo): void { public function deletePost(NewsPostInfo|string $postInfo): void {
if($postInfo instanceof NewsPostInfo) if($postInfo instanceof NewsPostInfo)
$postInfo = $postInfo->getId(); $postInfo = $postInfo->id;
$stmt = $this->cache->get('UPDATE msz_news_posts SET post_deleted = COALESCE(post_deleted, NOW()) WHERE post_id = ?'); $stmt = $this->cache->get('UPDATE msz_news_posts SET post_deleted = COALESCE(post_deleted, NOW()) WHERE post_id = ?');
$stmt->addParameter(1, $postInfo); $stmt->addParameter(1, $postInfo);
@ -324,7 +324,7 @@ class News {
public function restorePost(NewsPostInfo|string $postInfo): void { public function restorePost(NewsPostInfo|string $postInfo): void {
if($postInfo instanceof NewsPostInfo) if($postInfo instanceof NewsPostInfo)
$postInfo = $postInfo->getId(); $postInfo = $postInfo->id;
$stmt = $this->cache->get('UPDATE msz_news_posts SET post_deleted = NULL WHERE post_id = ?'); $stmt = $this->cache->get('UPDATE msz_news_posts SET post_deleted = NULL WHERE post_id = ?');
$stmt->addParameter(1, $postInfo); $stmt->addParameter(1, $postInfo);
@ -333,7 +333,7 @@ class News {
public function nukePost(NewsPostInfo|string $postInfo): void { public function nukePost(NewsPostInfo|string $postInfo): void {
if($postInfo instanceof NewsPostInfo) if($postInfo instanceof NewsPostInfo)
$postInfo = $postInfo->getId(); $postInfo = $postInfo->id;
// should this enforce a soft delete first? (AND post_deleted IS NOT NULL) // should this enforce a soft delete first? (AND post_deleted IS NOT NULL)
$stmt = $this->cache->get('DELETE FROM msz_news_posts WHERE post_id = ?'); $stmt = $this->cache->get('DELETE FROM msz_news_posts WHERE post_id = ?');
@ -352,9 +352,9 @@ class News {
DateTimeInterface|int|null $schedule = null DateTimeInterface|int|null $schedule = null
): void { ): void {
if($postInfo instanceof NewsPostInfo) if($postInfo instanceof NewsPostInfo)
$postInfo = $postInfo->getId(); $postInfo = $postInfo->id;
if($categoryInfo instanceof NewsCategoryInfo) if($categoryInfo instanceof NewsCategoryInfo)
$categoryInfo = $categoryInfo->getId(); $categoryInfo = $categoryInfo->id;
if($userInfo instanceof UserInfo) if($userInfo instanceof UserInfo)
$userInfo = $userInfo->getId(); $userInfo = $userInfo->getId();
if($schedule instanceof DateTimeInterface) if($schedule instanceof DateTimeInterface)
@ -392,9 +392,9 @@ class News {
CommentsCategoryInfo|string $commentsCategory CommentsCategoryInfo|string $commentsCategory
): void { ): void {
if($postInfo instanceof NewsPostInfo) if($postInfo instanceof NewsPostInfo)
$postInfo = $postInfo->getId(); $postInfo = $postInfo->id;
if($commentsCategory instanceof CommentsCategoryInfo) if($commentsCategory instanceof CommentsCategoryInfo)
$commentsCategory = $commentsCategory->getId(); $commentsCategory = $commentsCategory->id;
$stmt = $this->cache->get('UPDATE msz_news_posts SET comment_section_id = ? WHERE post_id = ?'); $stmt = $this->cache->get('UPDATE msz_news_posts SET comment_section_id = ? WHERE post_id = ?');
$stmt->addParameter(1, $commentsCategory); $stmt->addParameter(1, $commentsCategory);

View file

@ -6,12 +6,12 @@ use Index\Db\DbResult;
class NewsCategoryInfo { class NewsCategoryInfo {
public function __construct( public function __construct(
private string $id, public private(set) string $id,
private string $name, public private(set) string $name,
private string $description, public private(set) string $description,
private bool $hidden, public private(set) bool $hidden,
private int $created, public private(set) int $createdTime,
private int $posts, public private(set) int $postsCount,
) {} ) {}
public static function fromResult(DbResult $result): NewsCategoryInfo { public static function fromResult(DbResult $result): NewsCategoryInfo {
@ -20,36 +20,12 @@ class NewsCategoryInfo {
name: $result->getString(1), name: $result->getString(1),
description: $result->getString(2), description: $result->getString(2),
hidden: $result->getBoolean(3), hidden: $result->getBoolean(3),
created: $result->getInteger(4), createdTime: $result->getInteger(4),
posts: $result->getInteger(5), postsCount: $result->getInteger(5),
); );
} }
public function getId(): string { public CarbonImmutable $createdAt {
return $this->id; get => CarbonImmutable::createFromTimestampUTC($this->createdTime);
}
public function getName(): string {
return $this->name;
}
public function getDescription(): string {
return $this->description;
}
public function isHidden(): bool {
return $this->hidden;
}
public function getCreatedTime(): int {
return $this->created;
}
public function getCreatedAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->created);
}
public function getPostsCount(): int {
return $this->posts;
} }
} }

View file

@ -6,17 +6,17 @@ use Index\Db\DbResult;
class NewsPostInfo { class NewsPostInfo {
public function __construct( public function __construct(
private string $id, public private(set) string $id,
private string $categoryId, public private(set) string $categoryId,
private ?string $userId, public private(set) ?string $userId,
private ?string $commentsSectionId, public private(set) ?string $commentsSectionId,
private bool $featured, public private(set) bool $featured,
private string $title, public private(set) string $title,
private string $body, public private(set) string $body,
private int $scheduled, public private(set) int $scheduledTime,
private int $created, public private(set) int $createdTime,
private int $updated, public private(set) int $updatedTime,
private ?int $deleted, public private(set) ?int $deletedTime,
) {} ) {}
public static function fromResult(DbResult $result): NewsPostInfo { public static function fromResult(DbResult $result): NewsPostInfo {
@ -28,99 +28,49 @@ class NewsPostInfo {
featured: $result->getBoolean(4), featured: $result->getBoolean(4),
title: $result->getString(5), title: $result->getString(5),
body: $result->getString(6), body: $result->getString(6),
scheduled: $result->getInteger(7), scheduledTime: $result->getInteger(7),
created: $result->getInteger(8), createdTime: $result->getInteger(8),
updated: $result->getInteger(9), updatedTime: $result->getInteger(9),
deleted: $result->getIntegerOrNull(10), deletedTime: $result->getIntegerOrNull(10),
); );
} }
public function getId(): string { public string $commentsCategoryName {
return $this->id; get => sprintf('news-%s', $this->id);
} }
public function getCategoryId(): string { public string $firstParagraph {
return $this->categoryId; get {
$index = mb_strpos($this->body, "\n");
return $index === false ? $this->body : mb_substr($this->body, 0, $index);
}
} }
public function hasUserId(): bool { public CarbonImmutable $scheduledAt {
return $this->userId !== null; get => CarbonImmutable::createFromTimestampUTC($this->scheduledTime);
} }
public function getUserId(): ?string { public bool $published {
return $this->userId; get => $this->scheduledTime <= time();
} }
public function hasCommentsCategoryId(): bool { public CarbonImmutable $createdAt {
return $this->commentsSectionId !== null; get => CarbonImmutable::createFromTimestampUTC($this->createdTime);
} }
public function getCommentsCategoryId(): string { public CarbonImmutable $updatedAt {
return $this->commentsSectionId; get => CarbonImmutable::createFromTimestampUTC($this->updatedTime);
} }
public function getCommentsCategoryName(): string { public bool $edited {
return sprintf('news-%s', $this->id); get => $this->updatedTime > $this->createdTime;
} }
public function isFeatured(): bool { public bool $deleted {
return $this->featured; get => $this->deletedTime !== null;
} }
public function getTitle(): string { public ?CarbonImmutable $deletedAt {
return $this->title; get => $this->deletedTime === null ? null : CarbonImmutable::createFromTimestampUTC($this->deletedTime);
}
public function getBody(): string {
return $this->body;
}
public function getFirstParagraph(): string {
$index = mb_strpos($this->body, "\n");
return $index === false ? $this->body : mb_substr($this->body, 0, $index);
}
public function getScheduledTime(): int {
return $this->scheduled;
}
public function getScheduledAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->scheduled);
}
public function isPublished(): bool {
return $this->scheduled <= time();
}
public function getCreatedTime(): int {
return $this->created;
}
public function getCreatedAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->created);
}
public function getUpdatedTime(): int {
return $this->updated;
}
public function getUpdatedAt(): CarbonImmutable {
return CarbonImmutable::createFromTimestampUTC($this->updated);
}
public function isEdited(): bool {
return $this->updated > $this->created;
}
public function isDeleted(): bool {
return $this->deleted !== null;
}
public function getDeletedTime(): ?int {
return $this->deleted;
}
public function getDeletedAt(): ?CarbonImmutable {
return $this->deleted === null ? null : CarbonImmutable::createFromTimestampUTC($this->deleted);
} }
} }

View file

@ -34,16 +34,16 @@ class NewsRoutes implements RouteHandler, UrlSource {
); );
foreach($postInfos as $postInfo) { foreach($postInfos as $postInfo) {
$categoryId = $postInfo->getCategoryId(); $categoryId = $postInfo->categoryId;
$userInfo = $postInfo->hasUserId() ? $this->usersCtx->getUserInfo($postInfo->getUserId()) : null; $userInfo = $postInfo->userId !== null ? $this->usersCtx->getUserInfo($postInfo->userId) : null;
if(array_key_exists($categoryId, $this->categoryInfos)) if(array_key_exists($categoryId, $this->categoryInfos))
$categoryInfo = $this->categoryInfos[$categoryId]; $categoryInfo = $this->categoryInfos[$categoryId];
else else
$this->categoryInfos[$categoryId] = $categoryInfo = $this->news->getCategory(postInfo: $postInfo); $this->categoryInfos[$categoryId] = $categoryInfo = $this->news->getCategory(postInfo: $postInfo);
$commentsCount = $postInfo->hasCommentsCategoryId() $commentsCount = $postInfo->commentsSectionId
? $this->comments->countPosts(categoryInfo: $postInfo->getCommentsCategoryId(), deleted: false) ? $this->comments->countPosts(categoryInfo: $postInfo->commentsSectionId, deleted: false)
: 0; : 0;
$posts[] = [ $posts[] = [
@ -67,9 +67,8 @@ class NewsRoutes implements RouteHandler, UrlSource {
); );
foreach($postInfos as $postInfo) { foreach($postInfos as $postInfo) {
$userId = $postInfo->getUserId(); $categoryId = $postInfo->categoryId;
$categoryId = $postInfo->getCategoryId(); $userInfo = $postInfo->userId !== null ? $this->usersCtx->getUserInfo($postInfo->userId) : null;
$userInfo = $postInfo->hasUserId() ? $this->usersCtx->getUserInfo($postInfo->getUserId()) : null;
$posts[] = [ $posts[] = [
'post' => $postInfo, 'post' => $postInfo,
@ -134,22 +133,22 @@ class NewsRoutes implements RouteHandler, UrlSource {
return 404; return 404;
} }
if(!$postInfo->isPublished() || $postInfo->isDeleted()) if(!$postInfo->published || $postInfo->deleted)
return 404; return 404;
$categoryInfo = $this->news->getCategory(postInfo: $postInfo); $categoryInfo = $this->news->getCategory(postInfo: $postInfo);
if($postInfo->hasCommentsCategoryId()) if($postInfo->commentsSectionId !== null)
try { try {
$commentsCategory = $this->comments->getCategory(categoryId: $postInfo->getCommentsCategoryId()); $commentsCategory = $this->comments->getCategory(categoryId: $postInfo->commentsSectionId);
} catch(RuntimeException $ex) {} } catch(RuntimeException $ex) {}
if(!isset($commentsCategory)) { if(!isset($commentsCategory)) {
$commentsCategory = $this->comments->ensureCategory($postInfo->getCommentsCategoryName()); $commentsCategory = $this->comments->ensureCategory($postInfo->commentsCategoryName);
$this->news->updatePostCommentCategory($postInfo, $commentsCategory); $this->news->updatePostCommentCategory($postInfo, $commentsCategory);
} }
$userInfo = $postInfo->hasUserId() ? $this->usersCtx->getUserInfo($postInfo->getUserId()) : null; $userInfo = $postInfo->userId !== null ? $this->usersCtx->getUserInfo($postInfo->userId) : null;
$comments = new CommentsEx($this->authInfo, $this->comments, $this->usersCtx); $comments = new CommentsEx($this->authInfo, $this->comments, $this->usersCtx);
return Template::renderRaw('news.post', [ return Template::renderRaw('news.post', [
@ -168,16 +167,16 @@ class NewsRoutes implements RouteHandler, UrlSource {
$response->setContentType('application/rss+xml; charset=utf-8'); $response->setContentType('application/rss+xml; charset=utf-8');
$hasCategory = $categoryInfo !== null; $hasCategory = $categoryInfo !== null;
$siteName = $this->siteInfo->getName(); $siteName = $this->siteInfo->name;
$siteUrl = $this->siteInfo->getURL(); $siteUrl = $this->siteInfo->url;
$posts = $this->getNewsPostsForFeed($categoryInfo); $posts = $this->getNewsPostsForFeed($categoryInfo);
$feed = new FeedBuilder; $feed = new FeedBuilder;
if($hasCategory) { if($hasCategory) {
$feed->setTitle(sprintf('%s » %s', $siteName, $categoryInfo->getName())); $feed->setTitle(sprintf('%s » %s', $siteName, $categoryInfo->name));
$feed->setDescription($categoryInfo->getDescription()); $feed->setDescription($categoryInfo->description);
$feed->setContentUrl($siteUrl . $this->urls->format('news-category', ['category' => $categoryInfo->getId()])); $feed->setContentUrl($siteUrl . $this->urls->format('news-category', ['category' => $categoryInfo->id]));
$feed->setFeedUrl($siteUrl . $this->urls->format('news-category-feed', ['category' => $categoryInfo->getId()])); $feed->setFeedUrl($siteUrl . $this->urls->format('news-category-feed', ['category' => $categoryInfo->id]));
} else { } else {
$feed->setTitle(sprintf('%s » Featured News', $siteName)); $feed->setTitle(sprintf('%s » Featured News', $siteName));
$feed->setDescription('A live featured news feed.'); $feed->setDescription('A live featured news feed.');
@ -191,22 +190,22 @@ class NewsRoutes implements RouteHandler, UrlSource {
$postInfo = $post['post']; $postInfo = $post['post'];
$userInfo = $post['user']; $userInfo = $post['user'];
$item->setTitle($postInfo->getTitle()); $item->setTitle($postInfo->title);
$item->setDescription(Parser::instance(Parser::MARKDOWN)->parseText($postInfo->getBody())); $item->setDescription(Parser::instance(Parser::MARKDOWN)->parseText($postInfo->body));
$item->setCreatedAt($postInfo->getCreatedTime()); $item->setCreatedAt($postInfo->createdTime);
$item->setContentUrl($siteUrl . $this->urls->format('news-post', ['post' => $postInfo->getId()])); $item->setContentUrl($siteUrl . $this->urls->format('news-post', ['post' => $postInfo->id]));
$item->setCommentsUrl($siteUrl . $this->urls->format('news-post-comments', ['post' => $postInfo->getId()])); $item->setCommentsUrl($siteUrl . $this->urls->format('news-post-comments', ['post' => $postInfo->id]));
if($userInfo !== null) { if($userInfo !== null) {
$item->setAuthorName($userInfo->getName()); $item->setAuthorName($userInfo->name);
$item->setAuthorUrl($siteUrl . $this->urls->format('user-profile', ['user' => $userInfo->getId()])); $item->setAuthorUrl($siteUrl . $this->urls->format('user-profile', ['user' => $userInfo->getId()]));
} }
$itemUpdatedAt = $postInfo->getUpdatedTime(); $itemUpdatedAt = $postInfo->updatedTime;
if($feedUpdatedAt < $itemUpdatedAt) { if($feedUpdatedAt < $itemUpdatedAt) {
$feed->setUpdatedAt($feedUpdatedAt = $itemUpdatedAt); $feed->setUpdatedAt($feedUpdatedAt = $itemUpdatedAt);
if($postInfo->getCreatedTime() < $itemUpdatedAt) if($postInfo->createdTime < $itemUpdatedAt)
$item->setUpdatedAt($itemUpdatedAt); $item->setUpdatedAt($itemUpdatedAt);
} }
}); });

View file

@ -9,12 +9,12 @@ class PermissionInfo implements IPermissionResult {
private int $calculated; private int $calculated;
public function __construct( public function __construct(
private ?string $userId, public private(set) ?string $userId,
private ?string $roleId, public private(set) ?string $roleId,
private ?string $forumCategoryId, public private(set) ?string $forumCategoryId,
private string $category, public private(set) string $category,
private int $allow, public private(set) int $allow,
private int $deny, public private(set) int $deny,
) { ) {
$this->calculated = $this->allow & ~$this->deny; $this->calculated = $this->allow & ~$this->deny;
} }
@ -30,42 +30,6 @@ class PermissionInfo implements IPermissionResult {
); );
} }
public function hasUserId(): bool {
return $this->userId !== null;
}
public function getUserId(): ?string {
return $this->userId;
}
public function hasRoleId(): bool {
return $this->roleId !== null;
}
public function getRoleId(): ?string {
return $this->roleId;
}
public function hasForumCategoryId(): bool {
return $this->forumCategoryId !== null;
}
public function getForumCategoryId(): ?string {
return $this->forumCategoryId;
}
public function getCategory(): string {
return $this->category;
}
public function getAllow(): int {
return $this->allow;
}
public function getDeny(): int {
return $this->deny;
}
public function getCalculated(): int { public function getCalculated(): int {
return $this->calculated; return $this->calculated;
} }

Some files were not shown because too many files have changed in this diff Show more