Compare commits

...

31 Commits
cleanup ... dev

Author SHA1 Message Date
Pig Fang
52f6fefed0
chore: remove unavailable sponsor info 2026-03-30 21:42:23 +08:00
Steven Qiu
840555df42
make tests happy 2026-03-09 23:13:47 +08:00
Steven Qiu
9aa867e8aa
refactor: do not catch exceptions when cannot read options 2026-03-09 20:21:43 +08:00
Steven Qiu
d70b39f445
feat: add Auto-Submitted header to emails
@see https://datatracker.ietf.org/doc/html/rfc3834
Hopefully this could prevent sender being spammed by auto replies...
2025-10-09 01:54:37 +08:00
SANYE-YA
33055ecbf9
fix avatar (refactor needed) (#666) 2025-08-07 05:08:13 +08:00
Steven Qiu
2e39fbce77
fix: avatar (refactor needed) 2025-07-31 19:59:29 +08:00
Steven Qiu
1b3b020d52
fix: make imagick sanitize result stable 2025-07-27 03:34:35 +08:00
Steven Qiu
33d805ee82
fix: skinlib 2d preview (refactor needed) 2025-07-26 21:38:33 +08:00
Steven Qiu
57c02dd51c
fix: check if imagick installed 2025-07-25 17:43:53 +08:00
Steven Qiu
5b6bb98860
chore: update README 2025-07-25 17:39:02 +08:00
Steven Qiu
c01112a6c1
chore: use imagick for Intervention\Image 2025-07-25 17:13:54 +08:00
Steven Qiu
064b0967fc
chore: complete Facade namespaces in use statements 2025-07-02 19:29:19 +08:00
Steven Qiu
9f4c59abec
chore: no .DS_Store [skip ci] 2025-07-02 19:29:19 +08:00
Steven Qiu
d8547a0a3d
refactor: use Intervention/Image to sanitize textures 2025-07-02 19:12:46 +08:00
Steven Qiu
9c51bd602b chore: remove redundant VSCode launch profile 2025-06-29 16:50:14 +08:00
Steven Qiu
bc3f504ca3 chore: ci 2025-06-29 16:50:14 +08:00
Steven Qiu
761cbb7828
feat: max texture width & texture sanitize (#662)
* feat: sanitize uploaded file when user upload texture

* feat: limit max texture width to avoid png bomb

* style: apply php-cs-fixer fixes

* chore: set default value for max_texture_width option

* Update skinlib.yml

Co-authored-by: Pig Fang <g-plane@hotmail.com>

---------

Co-authored-by: Pig Fang <g-plane@hotmail.com>
2025-06-29 16:09:55 +08:00
Steven Qiu
01fe3eb4cb
chore: set filename for ci snapshot build artifact 2025-06-28 17:48:16 +08:00
Steven Qiu
f03dd8122b
chore: remove redundant command in ci 2025-06-28 17:47:28 +08:00
Steven Qiu
cfda2a6bf8
style: apply php-cs-fixer fixes 2025-06-28 06:17:40 +08:00
Steven Qiu
74ce668221
fix: phpunit test 2025-06-28 06:16:49 +08:00
Steven Qiu
5a18d24464
fix: db exception in tests 2025-06-28 03:46:17 +08:00
Steven Qiu
5125862f80
fix: unexpected db query during composer install 2025-06-27 19:23:20 +08:00
Steven Qiu
fa791857ec
fix: ci snapshot build 2025-06-26 21:47:35 +08:00
Steven Qiu
24ad29ea99
style: apply php-cs-fixer fixes 2025-06-26 21:16:56 +08:00
Steven Qiu
cdfb972bd0 fix: scopes missing after cache clear 2025-06-26 21:15:53 +08:00
Zephyr Lykos
1985ce6ff8
config: switch default registry 2025-06-25 22:57:43 +08:00
Steven Qiu
d84eb65d55
Handle null route when request is handled by middleware 2025-06-22 21:50:57 +08:00
Steven Qiu
16474fb5d0
Remove locale cookie for API requests (#660)
* Do not set locale cookie for API requests

https://t.me/blessing_skin/184899

* Remove redundant code
2025-06-22 17:48:48 +08:00
Jerry
186138b884
Fix Netlify links (#656)
* Update README-zh.md

fix: wrong link

* Fix Netlify link

---------

Co-authored-by: Steven Qiu <tnqzh123@littlesk.in>
2025-06-22 17:40:50 +08:00
Zephyr Lykos
9ca6e37e39
chore: update deps 2025-01-18 16:44:33 +08:00
64 changed files with 2198 additions and 1589 deletions

View File

@ -26,14 +26,13 @@ jobs:
with: with:
php-version: 8.3 php-version: 8.3
coverage: none coverage: none
extensions: mbstring, dom, fileinfo, gd extensions: mbstring, dom, fileinfo, gd, imagick
- name: Install dependencies - name: Install dependencies
run: | run: |
composer install --prefer-dist --no-progress composer install --prefer-dist --no-progress
- name: Prepare - name: Prepare
run: | run: |
cp .env.example .env cp .env.example .env
php artisan key:generate
mkdir -p resources/views/overrides mkdir -p resources/views/overrides
- name: Validate Twig templates - name: Validate Twig templates
run: php artisan twig:lint -v run: php artisan twig:lint -v
@ -55,14 +54,14 @@ jobs:
with: with:
php-version: ${{ matrix.php }} php-version: ${{ matrix.php }}
coverage: none coverage: none
extensions: mbstring, dom, fileinfo, sqlite, gd, zip extensions: mbstring, dom, fileinfo, sqlite, gd, zip, imagick
- name: Setup PHP with Xdebug - name: Setup PHP with Xdebug
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
if: matrix.php == '8.3' if: matrix.php == '8.3'
with: with:
php-version: ${{ matrix.php }} php-version: ${{ matrix.php }}
coverage: xdebug coverage: xdebug
extensions: mbstring, dom, fileinfo, sqlite, gd, zip extensions: mbstring, dom, fileinfo, sqlite, gd, zip, imagick
- name: Cache Composer dependencies - name: Cache Composer dependencies
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
@ -120,7 +119,7 @@ jobs:
with: with:
php-version: 8.2 php-version: 8.2
coverage: none coverage: none
extensions: mbstring, dom, fileinfo, sqlite, gd, zip extensions: mbstring, dom, fileinfo, sqlite, gd, zip, imagick
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Cache Node dependencies - name: Cache Node dependencies
@ -144,11 +143,13 @@ jobs:
yarn build yarn build
cp resources/assets/src/images/bg.webp public/app/ cp resources/assets/src/images/bg.webp public/app/
cp resources/assets/src/images/favicon.ico public/app/ cp resources/assets/src/images/favicon.ico public/app/
- uses: benjlevesque/short-sha@v1.2 - uses: benjlevesque/short-sha@v3.0
id: short-sha id: short-sha
- name: Archive release - name: Archive release
run: zip -9 -r blessing-skin-server-${{ steps.short-sha.outputs.sha }}.zip app bootstrap config database plugins public resources/lang resources/views resources/misc/textures routes storage vendor .env.example artisan LICENSE README.md README-zh.md index.html run: zip -9 -r blessing-skin-server-${{ steps.short-sha.outputs.sha }}.zip app bootstrap config database plugins public resources/lang resources/views resources/misc/textures routes storage vendor .env.example artisan LICENSE README.md README-zh.md index.html
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
if-no-files-found: error
name: blessing-skin-server-${{ steps.short-sha.outputs.sha }}.zip
path: blessing-skin-server-${{ steps.short-sha.outputs.sha }}.zip path: blessing-skin-server-${{ steps.short-sha.outputs.sha }}.zip

2
.gitignore vendored
View File

@ -25,3 +25,5 @@ storage/options.php
.phpunit.result.cache .phpunit.result.cache
.php-cs-fixer.cache .php-cs-fixer.cache
resources/views/overrides resources/views/overrides
.DS_Store
*/.DS_Store

68
.vscode/launch.json vendored
View File

@ -1,38 +1,34 @@
{ {
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"type": "node", "type": "node",
"request": "launch", "request": "launch",
"name": "Launch Jest Tests", "name": "Launch Jest Tests",
"program": "${workspaceFolder}/node_modules/.bin/jest", "program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["${file}"], "args": ["${file}"],
"internalConsoleOptions": "openOnSessionStart", "internalConsoleOptions": "openOnSessionStart",
"skipFiles": [ "skipFiles": ["<node_internals>/**"]
"<node_internals>/**" },
] {
}, "type": "php",
{ "request": "launch",
"type": "php", "name": "Launch with XDebug",
"request": "launch", "ignore": ["**/vendor/**/*.php"]
"name": "Launch with XDebug", },
"ignore": [ {
"**/vendor/**/*.php" "type": "firefox",
] "request": "launch",
}, "reAttach": true,
{ "name": "Launch with Firefox Debugger",
"type": "firefox", "url": "http://localhost/",
"request": "launch", "webRoot": "${workspaceFolder}",
"reAttach": true, "pathMappings": [
"name": "Launch with Firefox Debugger", {
"url": "http://localhost/", "url": "webpack:///",
"webRoot": "${workspaceFolder}", "path": "${workspaceFolder}/"
"pathMappings": [ }
{ ]
"url": "webpack:///", }
"path": "${workspaceFolder}/" ]
}
]
}
]
} }

View File

@ -52,6 +52,7 @@ Blessing Skin 对您的服务器有一定的要求。在大多数情况下,下
- JSON - JSON
- fileinfo - fileinfo
- zip - zip
- Imagick
## 快速使用 ## 快速使用
@ -61,105 +62,9 @@ Blessing Skin 对您的服务器有一定的要求。在大多数情况下,下
Blessing Skin 提供了强大的插件系统,您可以通过添加多种多样的插件来为您的皮肤站添加功能。 Blessing Skin 提供了强大的插件系统,您可以通过添加多种多样的插件来为您的皮肤站添加功能。
## 支持并赞助 Blessing Skin
如果您觉得这个软件对您很有帮助,欢迎通过赞助来支持开发!
目前可在 [爱发电](https://afdian.net/@blessing-skin) 上赞助。
### Sponsors
<table>
<tbody>
<tr>
<td align=center>
<a href="https://afdian.net/@gao_cai_sheng">
<img src="https://pic1.afdiancdn.com/user/2aac23481b1b11ea9f6e52540025c377/avatar/96a8b23d98cbac5aa36601db15a27e5e_w512_h512_s234.jpg" width="120" height="120">
<br>
gao_cai_sheng
</a>
</td>
<td align=center>
<a href="https://afdian.net/@LD_fantasy">
<img src="https://pic1.afdiancdn.com/user/9bed7bb454f011eb821652540025c377/avatar/cb679e3eac693e0eea2eac527c7954e0_w700_h1307_s137.jpg" width="120" height="120">
<br>
K_LazyCat
</a>
</td>
<td align=center>
<a href="https://afdian.net/@nmzy2018">
<img src="https://pic1.afdiancdn.com/user/a66f79d2f5a311e9af4e52540025c377/avatar/98682fb3c5914a39c8986bb1e97b5501_w512_h512_s248.jpg" width="120" height="120">
<br>
伊南
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/default/avatar/avatar-blue.png" width="120" height="120">
<br>
家乐
</a>
</td>
<td align=center>
<a href="https://afdian.net/@oar-01">
<img src="https://pic1.afdiancdn.com/user/e391f6ccdfa911ebb0e352540025c377/avatar/74da4afa92fa2666c306d43ab7a8804b_w1920_h1080_s338.jpg" width="120" height="120">
<br>
黄金鞘翅的郡主
</a>
</td>
</tr>
<tr>
<td align=center>
<a href="https://www.bilibili.plus/caucmc1.orz">
<img src="https://pic1.afdiancdn.com/user/edde2efc879611e889f552540025c377/avatar/d6a712efd6560b28989ac33f99c8915d_w473_h454_s24.jpg" width="120" height="120">
<br>
睡觉塞牙
</a>
</td>
</tr>
</tbody>
</table>
### Backers
<table>
<tbody>
<tr>
<td align=center>
<a href="https://afdian.net/@ValiantShishu976400">
<img src="https://pic1.afdiancdn.com/user/178a08963a5e11e9addd52540025c377/avatar/ece9f089aaf2c2f83204a8de11697caf_w350_h350_s16.jpg" width="75" height="75">
<br>
飒爽师叔
</a>
</td>
<td align=center>
<a href="https://afdian.net/@PAKingdom">
<img src="https://pic1.afdiancdn.com/user/18ad3338e58a11e9b29352540025c377/avatar/1e8b6476b589ddac545ac1ce13166e59_w584_h797_s59.jpg" width="75" height="75">
<br>
皮皮帕
</a>
</td>
<td align=center>
<a href="https://afdian.net/@oar-01">
<img src="https://pic1.afdiancdn.com/user/e391f6ccdfa911ebb0e352540025c377/avatar/74da4afa92fa2666c306d43ab7a8804b_w1920_h1080_s338.jpg" width="75" height="75">
<br>
黄金鞘翅的郡主
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/user/fc143860efa111ebb3e552540025c377/avatar/6e1d0f3f6ffb80b89b44269f59aa775f_w1080_h1080_s107.jpg" width="75" height="75">
<br>
♂sudo rm -rf /*[幼稚鬼]
</a>
</td>
</tr>
</tbody>
</table>
## 自行构建 ## 自行构建
详情可阅读 [这里](https://blessing.netlify.com/build.html)。 详情可阅读 [这里](https://blessing.netlify.app/build.html)。
> 您可以订阅我们的 Telegram 频道 [Blessing Skin News](https://t.me/blessing_skin_news) 来获取最新开发动态。当有新的 Commit 被推送时,我们的机器人将会在频道内发送一条消息来提示您能否拉取最新代码,以及拉取后应该做什么。 > 您可以订阅我们的 Telegram 频道 [Blessing Skin News](https://t.me/blessing_skin_news) 来获取最新开发动态。当有新的 Commit 被推送时,我们的机器人将会在频道内发送一条消息来提示您能否拉取最新代码,以及拉取后应该做什么。
@ -171,7 +76,7 @@ Blessing Skin 可支持多种语言,当前支持英语、简体中文和西班
## 问题报告 ## 问题报告
请参阅 [报告问题的正确姿势](https://blessing.netlify.com/report.html)。 请参阅 [报告问题的正确姿势](https://blessing.netlify.app/report.html)。
## 相关链接 ## 相关链接

View File

@ -52,6 +52,7 @@ Blessing Skin has only a few system requirements. In most cases, these PHP exten
- JSON - JSON
- fileinfo - fileinfo
- zip - zip
- Imagick
## Quick Install ## Quick Install
@ -61,102 +62,6 @@ Please read [Installation Guide](https://blessing.netlify.app/en/setup.html).
Blessing Skin provides an elegant and powerful plugin system, and you can attach plenty of functions and customization to your site via installing plugins. Blessing Skin provides an elegant and powerful plugin system, and you can attach plenty of functions and customization to your site via installing plugins.
## Supporting Blessing Skin
Welcome to sponsoring Blessing Skin if this software is useful for you!
Currently you can sponsor us via [爱发电](https://afdian.net/@blessing-skin).
### Sponsors
<table>
<tbody>
<tr>
<td align=center>
<a href="https://afdian.net/@gao_cai_sheng">
<img src="https://pic1.afdiancdn.com/user/2aac23481b1b11ea9f6e52540025c377/avatar/96a8b23d98cbac5aa36601db15a27e5e_w512_h512_s234.jpg" width="120" height="120">
<br>
gao_cai_sheng
</a>
</td>
<td align=center>
<a href="https://afdian.net/@LD_fantasy">
<img src="https://pic1.afdiancdn.com/user/9bed7bb454f011eb821652540025c377/avatar/cb679e3eac693e0eea2eac527c7954e0_w700_h1307_s137.jpg" width="120" height="120">
<br>
K_LazyCat
</a>
</td>
<td align=center>
<a href="https://afdian.net/@nmzy2018">
<img src="https://pic1.afdiancdn.com/user/a66f79d2f5a311e9af4e52540025c377/avatar/98682fb3c5914a39c8986bb1e97b5501_w512_h512_s248.jpg" width="120" height="120">
<br>
伊南
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/default/avatar/avatar-blue.png" width="120" height="120">
<br>
家乐
</a>
</td>
<td align=center>
<a href="https://afdian.net/@oar-01">
<img src="https://pic1.afdiancdn.com/user/e391f6ccdfa911ebb0e352540025c377/avatar/74da4afa92fa2666c306d43ab7a8804b_w1920_h1080_s338.jpg" width="120" height="120">
<br>
黄金鞘翅的郡主
</a>
</td>
</tr>
<tr>
<td align=center>
<a href="https://www.bilibili.plus/caucmc1.orz">
<img src="https://pic1.afdiancdn.com/user/edde2efc879611e889f552540025c377/avatar/d6a712efd6560b28989ac33f99c8915d_w473_h454_s24.jpg" width="120" height="120">
<br>
睡觉塞牙
</a>
</td>
</tr>
</tbody>
</table>
### Backers
<table>
<tbody>
<tr>
<td align=center>
<a href="https://afdian.net/@ValiantShishu976400">
<img src="https://pic1.afdiancdn.com/user/178a08963a5e11e9addd52540025c377/avatar/ece9f089aaf2c2f83204a8de11697caf_w350_h350_s16.jpg" width="75" height="75">
<br>
飒爽师叔
</a>
</td>
<td align=center>
<a href="https://afdian.net/@PAKingdom">
<img src="https://pic1.afdiancdn.com/user/18ad3338e58a11e9b29352540025c377/avatar/1e8b6476b589ddac545ac1ce13166e59_w584_h797_s59.jpg" width="75" height="75">
<br>
皮皮帕
</a>
</td>
<td align=center>
<a href="https://afdian.net/@oar-01">
<img src="https://pic1.afdiancdn.com/user/e391f6ccdfa911ebb0e352540025c377/avatar/74da4afa92fa2666c306d43ab7a8804b_w1920_h1080_s338.jpg" width="75" height="75">
<br>
黄金鞘翅的郡主
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/user/fc143860efa111ebb3e552540025c377/avatar/6e1d0f3f6ffb80b89b44269f59aa775f_w1080_h1080_s107.jpg" width="75" height="75">
<br>
♂sudo rm -rf /*[幼稚鬼]
</a>
</td>
</tr>
</tbody>
</table>
## Build From Source ## Build From Source
Please refer to [Manual Build](https://blessing.netlify.app/build.html). Please refer to [Manual Build](https://blessing.netlify.app/build.html).

View File

@ -86,7 +86,7 @@ class AdminController extends Controller
Request $request, Request $request,
PluginManager $plugins, PluginManager $plugins,
Filesystem $filesystem, Filesystem $filesystem,
Filter $filter Filter $filter,
) { ) {
$db = config('database.connections.'.config('database.default')); $db = config('database.connections.'.config('database.default'));
$dbType = Arr::get([ $dbType = Arr::get([

View File

@ -8,16 +8,16 @@ use App\Mail\ForgotPassword;
use App\Models\Player; use App\Models\Player;
use App\Models\User; use App\Models\User;
use App\Rules; use App\Rules;
use Auth;
use Blessing\Filter; use Blessing\Filter;
use Blessing\Rejection; use Blessing\Rejection;
use Cache;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Mail; use Illuminate\Support\Facades\Auth;
use Session; use Illuminate\Support\Facades\Cache;
use URL; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\URL;
use Vectorface\Whip\Whip; use Vectorface\Whip\Whip;
class AuthController extends Controller class AuthController extends Controller
@ -50,7 +50,7 @@ class AuthController extends Controller
Request $request, Request $request,
Rules\Captcha $captcha, Rules\Captcha $captcha,
Dispatcher $dispatcher, Dispatcher $dispatcher,
Filter $filter Filter $filter,
) { ) {
$data = $request->validate([ $data = $request->validate([
'identification' => 'required', 'identification' => 'required',
@ -151,7 +151,7 @@ class AuthController extends Controller
Request $request, Request $request,
Rules\Captcha $captcha, Rules\Captcha $captcha,
Dispatcher $dispatcher, Dispatcher $dispatcher,
Filter $filter Filter $filter,
) { ) {
$can = $filter->apply('can_register', null); $can = $filter->apply('can_register', null);
if ($can instanceof Rejection) { if ($can instanceof Rejection) {
@ -248,7 +248,7 @@ class AuthController extends Controller
Request $request, Request $request,
Rules\Captcha $captcha, Rules\Captcha $captcha,
Dispatcher $dispatcher, Dispatcher $dispatcher,
Filter $filter Filter $filter,
) { ) {
$data = $request->validate([ $data = $request->validate([
'email' => 'required|email', 'email' => 'required|email',

View File

@ -4,12 +4,12 @@ namespace App\Http\Controllers;
use App\Models\Texture; use App\Models\Texture;
use App\Models\User; use App\Models\User;
use Auth;
use Blessing\Filter; use Blessing\Filter;
use Blessing\Rejection; use Blessing\Rejection;
use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ClosetController extends Controller class ClosetController extends Controller
{ {
@ -75,7 +75,7 @@ class ClosetController extends Controller
public function add( public function add(
Request $request, Request $request,
Dispatcher $dispatcher, Dispatcher $dispatcher,
Filter $filter Filter $filter,
) { ) {
['tid' => $tid, 'name' => $name] = $request->validate([ ['tid' => $tid, 'name' => $name] = $request->validate([
'tid' => 'required|integer', 'tid' => 'required|integer',
@ -132,7 +132,7 @@ class ClosetController extends Controller
Request $request, Request $request,
Dispatcher $dispatcher, Dispatcher $dispatcher,
Filter $filter, Filter $filter,
$tid $tid,
) { ) {
['name' => $name] = $request->validate(['name' => 'required']); ['name' => $name] = $request->validate(['name' => 'required']);
/** @var User */ /** @var User */

View File

@ -163,6 +163,10 @@ class OptionsController extends Controller
->text('max_upload_file_size')->addon('KB') ->text('max_upload_file_size')->addon('KB')
->hint(trans('options.general.max_upload_file_size.hint', ['size' => ini_get('upload_max_filesize')])); ->hint(trans('options.general.max_upload_file_size.hint', ['size' => ini_get('upload_max_filesize')]));
$form->group('max_texture_width')
->text('max_texture_width')->addon('px')
->hint(trans('options.general.max_texture_width.hint'));
$form->select('player_name_rule') $form->select('player_name_rule')
->option('official', trans('options.general.player_name_rule.official')) ->option('official', trans('options.general.player_name_rule.official'))
->option('cjk', trans('options.general.player_name_rule.cjk')) ->option('cjk', trans('options.general.player_name_rule.cjk'))

View File

@ -10,11 +10,11 @@ use App\Models\Player;
use App\Models\Texture; use App\Models\Texture;
use App\Models\User; use App\Models\User;
use App\Rules; use App\Rules;
use Auth;
use Blessing\Filter; use Blessing\Filter;
use Blessing\Rejection; use Blessing\Rejection;
use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
class PlayerController extends Controller class PlayerController extends Controller
@ -124,7 +124,7 @@ class PlayerController extends Controller
public function delete( public function delete(
Dispatcher $dispatcher, Dispatcher $dispatcher,
Filter $filter, Filter $filter,
Player $player Player $player,
) { ) {
/** @var User */ /** @var User */
$user = auth()->user(); $user = auth()->user();
@ -157,7 +157,7 @@ class PlayerController extends Controller
Request $request, Request $request,
Dispatcher $dispatcher, Dispatcher $dispatcher,
Filter $filter, Filter $filter,
Player $player Player $player,
) { ) {
$name = $request->validate([ $name = $request->validate([
'name' => [ 'name' => [
@ -194,7 +194,7 @@ class PlayerController extends Controller
Request $request, Request $request,
Dispatcher $dispatcher, Dispatcher $dispatcher,
Filter $filter, Filter $filter,
Player $player Player $player,
) { ) {
/** @var User */ /** @var User */
$user = auth()->user(); $user = auth()->user();
@ -234,7 +234,7 @@ class PlayerController extends Controller
Request $request, Request $request,
Dispatcher $dispatcher, Dispatcher $dispatcher,
Filter $filter, Filter $filter,
Player $player Player $player,
) { ) {
$types = $request->input('type', []); $types = $request->input('type', []);

View File

@ -44,7 +44,7 @@ class PlayersManagementController extends Controller
public function name( public function name(
Player $player, Player $player,
Request $request, Request $request,
Dispatcher $dispatcher Dispatcher $dispatcher,
) { ) {
$name = $request->validate([ $name = $request->validate([
'player_name' => [ 'player_name' => [
@ -70,7 +70,7 @@ class PlayersManagementController extends Controller
public function owner( public function owner(
Player $player, Player $player,
Request $request, Request $request,
Dispatcher $dispatcher Dispatcher $dispatcher,
) { ) {
$uid = $request->validate(['uid' => 'required|integer'])['uid']; $uid = $request->validate(['uid' => 'required|integer'])['uid'];
@ -96,7 +96,7 @@ class PlayersManagementController extends Controller
public function texture( public function texture(
Player $player, Player $player,
Request $request, Request $request,
Dispatcher $dispatcher Dispatcher $dispatcher,
) { ) {
$data = $request->validate([ $data = $request->validate([
'tid' => 'required|integer', 'tid' => 'required|integer',
@ -123,7 +123,7 @@ class PlayersManagementController extends Controller
public function delete( public function delete(
Player $player, Player $player,
Dispatcher $dispatcher Dispatcher $dispatcher,
) { ) {
$dispatcher->dispatch('player.deleting', [$player]); $dispatcher->dispatch('player.deleting', [$player]);

View File

@ -77,7 +77,7 @@ class ReportController extends Controller
public function review( public function review(
Report $report, Report $report,
Request $request, Request $request,
Dispatcher $dispatcher Dispatcher $dispatcher,
) { ) {
$data = $request->validate([ $data = $request->validate([
'action' => ['required', Rule::in(['delete', 'ban', 'reject'])], 'action' => ['required', Rule::in(['delete', 'ban', 'reject'])],

View File

@ -20,7 +20,7 @@ class SetupController extends Controller
Request $request, Request $request,
Filesystem $filesystem, Filesystem $filesystem,
Connection $connection, Connection $connection,
DatabaseManager $manager DatabaseManager $manager,
) { ) {
if ($request->isMethod('get')) { if ($request->isMethod('get')) {
try { try {
@ -121,7 +121,7 @@ class SetupController extends Controller
'database/migrations', 'database/migrations',
'vendor/laravel/passport/database/migrations', 'vendor/laravel/passport/database/migrations',
], ],
]); ]);
$siteUrl = url('/'); $siteUrl = url('/');
if (Str::endsWith($siteUrl, '/index.php')) { if (Str::endsWith($siteUrl, '/index.php')) {

View File

@ -4,7 +4,6 @@ namespace App\Http\Controllers;
use App\Models\Texture; use App\Models\Texture;
use App\Models\User; use App\Models\User;
use Auth;
use Blessing\Filter; use Blessing\Filter;
use Blessing\Rejection; use Blessing\Rejection;
use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Events\Dispatcher;
@ -12,10 +11,12 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Intervention\Image\Facades\Image;
use League\CommonMark\GithubFlavoredMarkdownConverter; use League\CommonMark\GithubFlavoredMarkdownConverter;
use Storage;
class SkinlibController extends Controller class SkinlibController extends Controller
{ {
@ -189,7 +190,7 @@ class SkinlibController extends Controller
public function handleUpload( public function handleUpload(
Request $request, Request $request,
Filter $filter, Filter $filter,
Dispatcher $dispatcher Dispatcher $dispatcher,
) { ) {
$file = $request->file('file'); $file = $request->file('file');
if ($file && !$file->isValid()) { if ($file && !$file->isValid()) {
@ -220,6 +221,16 @@ class SkinlibController extends Controller
$type = $data['type']; $type = $data['type'];
$size = getimagesize($file); $size = getimagesize($file);
$maxWidth = option('max_texture_width', 8192);
if ($size[0] > $maxWidth) {
$message = trans('skinlib.upload.too-wide', [
'width' => $size[0],
'maxWidth' => $maxWidth,
]);
return json($message, 1);
}
if ($size[0] % 64 != 0 || $size[1] % 32 != 0) { if ($size[0] % 64 != 0 || $size[1] % 32 != 0) {
$message = trans('skinlib.upload.invalid-size', [ $message = trans('skinlib.upload.invalid-size', [
'type' => $type === 'cape' ? trans('general.cape') : trans('general.skin'), 'type' => $type === 'cape' ? trans('general.cape') : trans('general.skin'),
@ -253,8 +264,17 @@ class SkinlibController extends Controller
} }
} }
$hash = hash_file('sha256', $file); $image = Image::make($file);
$hash = $filter->apply('uploaded_texture_hash', $hash, [$file]); $imagick = $image->getCore();
$imagick->setOption('png:compression-filter', '0');
$imagick->setOption('png:compression-level', '9');
$imagick->setOption('png:compression-strategy', '0');
$imagick->setOption('png:exclude-chunk', 'all');
$imagick->stripImage();
$sanitized = $image->encode('png')->getEncoded();
$hash = hash('sha256', $image->encoded);
$hash = $filter->apply('uploaded_texture_hash', $hash, [$image]);
/** @var User */ /** @var User */
$user = Auth::user(); $user = Auth::user();
@ -270,11 +290,11 @@ class SkinlibController extends Controller
return json(trans('skinlib.upload.repeated'), 2, ['tid' => $duplicated->tid]); return json(trans('skinlib.upload.repeated'), 2, ['tid' => $duplicated->tid]);
} }
$size = ceil($file->getSize() / 1024); $fileSize = ceil(strlen($sanitized) / 1024);
$isPublic = is_string($data['public']) $isPublic = is_string($data['public'])
? $data['public'] === '1' ? $data['public'] === '1'
: $data['public']; : $data['public'];
$cost = $size * ( $cost = $fileSize * (
$isPublic $isPublic
? option('score_per_storage') ? option('score_per_storage')
: option('private_score_per_storage') : option('private_score_per_storage')
@ -285,13 +305,13 @@ class SkinlibController extends Controller
return json(trans('skinlib.upload.lack-score'), 1); return json(trans('skinlib.upload.lack-score'), 1);
} }
$dispatcher->dispatch('texture.uploading', [$file, $name, $hash]); $dispatcher->dispatch('texture.uploading', [$image, $name, $hash]);
$texture = new Texture(); $texture = new Texture();
$texture->name = $name; $texture->name = $name;
$texture->type = $type; $texture->type = $type;
$texture->hash = $hash; $texture->hash = $hash;
$texture->size = $size; $texture->size = $fileSize;
$texture->public = $isPublic; $texture->public = $isPublic;
$texture->uploader = $user->uid; $texture->uploader = $user->uid;
$texture->likes = 1; $texture->likes = 1;
@ -300,14 +320,14 @@ class SkinlibController extends Controller
/** @var FilesystemAdapter */ /** @var FilesystemAdapter */
$disk = Storage::disk('textures'); $disk = Storage::disk('textures');
if ($disk->missing($hash)) { if ($disk->missing($hash)) {
$file->storePubliclyAs('', $hash, ['disk' => 'textures']); $disk->put($hash, $sanitized);
} }
$user->score -= $cost; $user->score -= $cost;
$user->closet()->attach($texture->tid, ['item_name' => $name]); $user->closet()->attach($texture->tid, ['item_name' => $name]);
$user->save(); $user->save();
$dispatcher->dispatch('texture.uploaded', [$texture, $file]); $dispatcher->dispatch('texture.uploaded', [$texture, $image]);
return json(trans('skinlib.upload.success', ['name' => $name]), 0, [ return json(trans('skinlib.upload.success', ['name' => $name]), 0, [
'tid' => $texture->tid, 'tid' => $texture->tid,
@ -386,7 +406,7 @@ class SkinlibController extends Controller
Request $request, Request $request,
Dispatcher $dispatcher, Dispatcher $dispatcher,
Filter $filter, Filter $filter,
Texture $texture Texture $texture,
) { ) {
$data = $request->validate(['name' => [ $data = $request->validate(['name' => [
'required', 'required',
@ -416,7 +436,7 @@ class SkinlibController extends Controller
Request $request, Request $request,
Dispatcher $dispatcher, Dispatcher $dispatcher,
Filter $filter, Filter $filter,
Texture $texture Texture $texture,
) { ) {
$data = $request->validate([ $data = $request->validate([
'type' => ['required', Rule::in(['steve', 'alex', 'cape'])], 'type' => ['required', Rule::in(['steve', 'alex', 'cape'])],

View File

@ -6,11 +6,11 @@ use App\Models\Player;
use App\Models\Texture; use App\Models\Texture;
use App\Models\User; use App\Models\User;
use Blessing\Minecraft; use Blessing\Minecraft;
use Cache;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Image; use Illuminate\Support\Facades\Cache;
use Storage; use Illuminate\Support\Facades\Storage;
use Intervention\Image\Facades\Image;
class TextureController extends Controller class TextureController extends Controller
{ {
@ -71,7 +71,8 @@ class TextureController extends Controller
$lastModified = $disk->lastModified($hash); $lastModified = $disk->lastModified($hash);
return Image::make($image) // TODO: refactor
return \Intervention\Image\ImageManagerStatic::configure(['driver' => 'gd'])->make($image)
->response($usePNG ? 'png' : 'webp', 100) ->response($usePNG ? 'png' : 'webp', 100)
->setLastModified(Carbon::createFromTimestamp($lastModified)); ->setLastModified(Carbon::createFromTimestamp($lastModified));
} }
@ -145,7 +146,8 @@ class TextureController extends Controller
$disk = Storage::disk('textures'); $disk = Storage::disk('textures');
if (is_null($texture) || $disk->missing($texture->hash)) { if (is_null($texture) || $disk->missing($texture->hash)) {
return Image::make(resource_path("misc/textures/avatar$mode.png")) // TODO: refactor
return \Intervention\Image\ImageManagerStatic::configure(['driver' => 'gd'])->make(resource_path("misc/textures/avatar$mode.png"))
->resize($size, $size) ->resize($size, $size)
->response($usePNG ? 'png' : 'webp', 100); ->response($usePNG ? 'png' : 'webp', 100);
} }
@ -165,7 +167,8 @@ class TextureController extends Controller
$lastModified = Carbon::createFromTimestamp($disk->lastModified($hash)); $lastModified = Carbon::createFromTimestamp($disk->lastModified($hash));
return Image::make($image) // TODO: refactor
return \Intervention\Image\ImageManagerStatic::configure(['driver' => 'gd'])->make($image)
->resize($size, $size) ->resize($size, $size)
->response($usePNG ? 'png' : 'webp', 100) ->response($usePNG ? 'png' : 'webp', 100)
->setLastModified($lastModified); ->setLastModified($lastModified);

View File

@ -40,7 +40,7 @@ class TranslationsController extends Controller
Request $request, Request $request,
Application $app, Application $app,
JavaScript $js, JavaScript $js,
LanguageLine $line LanguageLine $line,
) { ) {
$data = $request->validate(['text' => 'required|string']); $data = $request->validate(['text' => 'required|string']);
@ -57,7 +57,7 @@ class TranslationsController extends Controller
public function delete( public function delete(
Application $app, Application $app,
JavaScript $js, JavaScript $js,
LanguageLine $line LanguageLine $line,
) { ) {
$line->delete(); $line->delete();

View File

@ -6,16 +6,16 @@ use App\Events\UserProfileUpdated;
use App\Mail\EmailVerification; use App\Mail\EmailVerification;
use App\Models\Texture; use App\Models\Texture;
use App\Models\User; use App\Models\User;
use Auth;
use Blessing\Filter; use Blessing\Filter;
use Blessing\Rejection; use Blessing\Rejection;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\URL;
use League\CommonMark\GithubFlavoredMarkdownConverter; use League\CommonMark\GithubFlavoredMarkdownConverter;
use Mail;
use Session;
use URL;
class UserController extends Controller class UserController extends Controller
{ {

View File

@ -9,10 +9,10 @@ use Illuminate\Http\Request;
class CheckRole class CheckRole
{ {
protected $roles = [ protected $roles = [
'banned' => USER::BANNED, 'banned' => User::BANNED,
'normal' => USER::NORMAL, 'normal' => User::NORMAL,
'admin' => USER::ADMIN, 'admin' => User::ADMIN,
'super-admin' => USER::SUPER_ADMIN, 'super-admin' => User::SUPER_ADMIN,
]; ];
public function handle(Request $request, Closure $next, $role) public function handle(Request $request, Closure $next, $role)

View File

@ -28,7 +28,9 @@ class DetectLanguagePrefer
/** @var Response */ /** @var Response */
$response = $next($request); $response = $next($request);
$response->cookie('locale', $locale, 120); if (!in_array('api', optional($request->route())->middleware() ?? [])) {
$response->cookie('locale', $locale, 120);
}
return $response; return $response;
} }

View File

@ -22,7 +22,7 @@ class FootComposer
Request $request, Request $request,
JavaScript $javascript, JavaScript $javascript,
Dispatcher $dispatcher, Dispatcher $dispatcher,
Filter $filter Filter $filter,
) { ) {
$this->request = $request; $this->request = $request;
$this->javascript = $javascript; $this->javascript = $javascript;

View File

@ -20,7 +20,7 @@ class HeadComposer
public function __construct( public function __construct(
Dispatcher $dispatcher, Dispatcher $dispatcher,
Request $request, Request $request,
Filter $filter Filter $filter,
) { ) {
$this->dispatcher = $dispatcher; $this->dispatcher = $dispatcher;
$this->request = $request; $this->request = $request;

View File

@ -3,7 +3,7 @@
namespace App\Listeners; namespace App\Listeners;
use App\Models\User; use App\Models\User;
use Event; use Illuminate\Support\Facades\Event;
class NotifyFailedPlugin class NotifyFailedPlugin
{ {

View File

@ -4,6 +4,7 @@ namespace App\Mail;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Headers;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class EmailVerification extends Mailable class EmailVerification extends Mailable
@ -26,4 +27,13 @@ class EmailVerification extends Mailable
->subject(trans('user.verification.mail.title', ['sitename' => $site_name])) ->subject(trans('user.verification.mail.title', ['sitename' => $site_name]))
->view('mails.email-verification'); ->view('mails.email-verification');
} }
public function headers(): Headers
{
return new Headers(
text: [
'Auto-Submitted' => 'auto-generated',
]
);
}
} }

View File

@ -4,6 +4,7 @@ namespace App\Mail;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Headers;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class ForgotPassword extends Mailable class ForgotPassword extends Mailable
@ -26,4 +27,13 @@ class ForgotPassword extends Mailable
->subject(trans('auth.forgot.mail.title', ['sitename' => $site_name])) ->subject(trans('auth.forgot.mail.title', ['sitename' => $site_name]))
->view('mails.password-reset'); ->view('mails.password-reset');
} }
public function headers(): Headers
{
return new Headers(
text: [
'Auto-Submitted' => 'auto-generated',
]
);
}
} }

View File

@ -3,7 +3,6 @@
namespace App\Models; namespace App\Models;
use App\Events\PlayerProfileUpdated; use App\Events\PlayerProfileUpdated;
use App\Models;
use DateTimeInterface; use DateTimeInterface;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@ -57,17 +56,17 @@ class Player extends Model
public function user() public function user()
{ {
return $this->belongsTo(Models\User::class, 'uid'); return $this->belongsTo(User::class, 'uid');
} }
public function skin() public function skin()
{ {
return $this->belongsTo(Models\Texture::class, 'tid_skin'); return $this->belongsTo(Texture::class, 'tid_skin');
} }
public function cape() public function cape()
{ {
return $this->belongsTo(Models\Texture::class, 'tid_cape'); return $this->belongsTo(Texture::class, 'tid_cape');
} }
public function getModelAttribute() public function getModelAttribute()

View File

@ -2,8 +2,10 @@
namespace App\Providers; namespace App\Providers;
use App\Models\Scope;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Laravel\Passport\Passport; use Laravel\Passport\Passport;
class AuthServiceProvider extends ServiceProvider class AuthServiceProvider extends ServiceProvider
@ -39,7 +41,19 @@ class AuthServiceProvider extends ServiceProvider
'ReportsManagement.ReadWrite' => 'auth.oauth.scope.reports-management.readwrite', 'ReportsManagement.ReadWrite' => 'auth.oauth.scope.reports-management.readwrite',
]; ];
$scopes = Cache::get('scopes', []); /*
* Return empty scopes if running unit tests or before installation.
* In these cases, migrations arent run yet, so DB queries will fail.
* OAuth isnt tested in unit tests, so returning empty scopes should be fine...?
* Maybe the best approach is to run migrations before bootstrap in tests,
* but this seems impossible for DB_DATABASE=:memory:;
* Or change how scopes are registered so they don't depend on the database,
* but that may introduce BREAKING CHANGES and plugin incompatibility.
* PRs welcome for better solutions!
*/
$scopes = (app()->runningUnitTests() || !Storage::disk('root')->exists('storage/install.lock')) ? [] : Cache::rememberForever('scopes', function () {
return Scope::pluck('description', 'name')->toArray();
});
Passport::tokensCan(array_merge($defaultScopes, $scopes)); Passport::tokensCan(array_merge($defaultScopes, $scopes));

View File

@ -8,10 +8,10 @@ use App\Events;
use App\Notifications; use App\Notifications;
use Blessing\Filter; use Blessing\Filter;
use Closure; use Closure;
use Event;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Notification;
class Hook class Hook
{ {

View File

@ -2,10 +2,10 @@
namespace App\Services; namespace App\Services;
use DB;
use Illuminate\Database\QueryException; use Illuminate\Database\QueryException;
use Illuminate\Filesystem\Filesystem; use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
class Option class Option
{ {
@ -20,13 +20,14 @@ class Option
return; return;
} }
try { if (!file_exists(storage_path('install.lock')) || app()->runningUnitTests()) {
$this->items = DB::table('options')
->get()
->mapWithKeys(fn ($item) => [$item->option_name => $item->option_value]);
} catch (QueryException $e) {
$this->items = collect(); $this->items = collect();
return;
} }
$this->items = DB::table('options')
->get()
->mapWithKeys(fn ($item) => [$item->option_name => $item->option_value]);
} }
public function get($key, $default = null, $raw = false) public function get($key, $default = null, $raw = false)

View File

@ -2,10 +2,10 @@
namespace App\Services; namespace App\Services;
use App\Services\Facades\Option;
use BadMethodCallException; use BadMethodCallException;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Option;
use ReflectionClass; use ReflectionClass;
/** /**
@ -203,7 +203,7 @@ class OptionForm
/** /**
* Handle the HTTP post request and update modified options. * Handle the HTTP post request and update modified options.
*/ */
public function handle(callable $callback = null): self public function handle(?callable $callback = null): self
{ {
$request = request(); $request = request();
$allPostData = $request->all(); $allPostData = $request->all();

View File

@ -36,7 +36,7 @@ class PluginManager
Application $app, Application $app,
Option $option, Option $option,
Dispatcher $dispatcher, Dispatcher $dispatcher,
Filesystem $filesystem Filesystem $filesystem,
) { ) {
$this->app = $app; $this->app = $app;
$this->option = $option; $this->option = $option;
@ -366,7 +366,7 @@ class PluginManager
*/ */
public function formatUnresolved( public function formatUnresolved(
Collection $unsatisfied, Collection $unsatisfied,
Collection $conflicts Collection $conflicts,
): array { ): array {
$unsatisfied = $unsatisfied->map(function ($detail, $name) { $unsatisfied = $unsatisfied->map(function ($detail, $name) {
if ($name === 'blessing-skin-server') { if ($name === 'blessing-skin-server') {

View File

@ -20,7 +20,7 @@ class JavaScript
public function __construct( public function __construct(
Filesystem $filesystem, Filesystem $filesystem,
Repository $cache, Repository $cache,
PluginManager $plugins PluginManager $plugins,
) { ) {
$this->filesystem = $filesystem; $this->filesystem = $filesystem;
$this->cache = $cache; $this->cache = $cache;

View File

@ -51,6 +51,7 @@ ini_set('display_errors', true);
'json', 'json',
'fileinfo', 'fileinfo',
'zip', 'zip',
'imagick',
], ],
'write_permission' => [ 'write_permission' => [
'bootstrap/cache', 'bootstrap/cache',

View File

@ -6,6 +6,7 @@
"php": "^8.1", "php": "^8.1",
"ext-ctype": "*", "ext-ctype": "*",
"ext-gd": "*", "ext-gd": "*",
"ext-imagick": "*",
"ext-json": "*", "ext-json": "*",
"ext-mbstring": "*", "ext-mbstring": "*",
"ext-openssl": "*", "ext-openssl": "*",

3020
composer.lock generated

File diff suppressed because it is too large Load Diff

20
config/image.php Normal file
View File

@ -0,0 +1,20 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Image Driver
|--------------------------------------------------------------------------
|
| Intervention Image supports "GD Library" and "Imagick" to process images
| internally. You may choose one of them according to your PHP
| configuration. By default PHP's "GD Library" implementation is used.
|
| Supported: "gd", "imagick"
|
*/
'driver' => 'imagick'
];

View File

@ -33,7 +33,7 @@ return [
*/ */
'registry' => env( 'registry' => env(
'PLUGINS_REGISTRY', 'PLUGINS_REGISTRY',
'https://d2jw1l0ullrzt6.cloudfront.net/registry_{lang}.json' 'https://bs-plugins.littleservice.cn/registry_{lang}.json'
), ),
/* /*

View File

@ -2,6 +2,7 @@
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateAllTables extends Migration class CreateAllTables extends Migration
{ {

View File

@ -1,6 +1,8 @@
<?php <?php
use App\Services\Facades\Option;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
class ImportOptions extends Migration class ImportOptions extends Migration
{ {

View File

@ -2,6 +2,7 @@
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddVerificationToUsersTable extends Migration class AddVerificationToUsersTable extends Migration
{ {

View File

@ -0,0 +1,21 @@
<?php
use App\Services\Facades\Option;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Option::set('max_texture_width', 8192);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
}
};

View File

@ -158,5 +158,6 @@
"isolatedModules": true "isolatedModules": true
} }
} }
} },
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
} }

View File

@ -110,7 +110,10 @@ general:
regs_per_ip: Max accounts of one IP regs_per_ip: Max accounts of one IP
max_upload_file_size: max_upload_file_size:
title: Max Upload Size title: Max Upload Size
hint: "Limit specified in php.ini: :size" hint: 'Limit specified in php.ini: :size'
max_texture_width:
title: Max Texture Width
hint: Maximum width of uploaded textures, must be an integer multiple of 64
player_name_rule: player_name_rule:
title: Player Name Rule title: Player Name Rule
official: Letters, numbers and underscores (Mojang's official rule) official: Letters, numbers and underscores (Mojang's official rule)
@ -129,8 +132,8 @@ general:
label: Delete invalid textures automatically. label: Delete invalid textures automatically.
hint: Delete textures records whose file no longer exists from skinlib. hint: Delete textures records whose file no longer exists from skinlib.
allow_downloading_texture: allow_downloading_texture:
title: Downloading Textures title: Downloading Textures
label: Allow users to directly download the source file of a skinlib item. label: Allow users to directly download the source file of a skinlib item.
status_code_for_private: status_code_for_private:
title: HTTP Code for Rejecting Accessing Private Textures title: HTTP Code for Rejecting Accessing Private Textures
texture_name_regexp: texture_name_regexp:

View File

@ -13,6 +13,7 @@ upload:
private-score-notice: It will spend you more scores for setting it as private. You will be charged :score scores for per KB storage. private-score-notice: It will spend you more scores for setting it as private. You will be charged :score scores for per KB storage.
invalid-size: Invalid :type file (width :width, height :height) invalid-size: Invalid :type file (width :width, height :height)
invalid-hd-skin: Invalid HD skin (width and height should be divisible by 32) invalid-hd-skin: Invalid HD skin (width and height should be divisible by 32)
too-wide: The texture is too wide (:widthpx). Maximum width allowed is :maxWidthpx
lack-score: You don't have enough score to upload this texture. lack-score: You don't have enough score to upload this texture.
repeated: The texture is already uploaded by someone else. You can add it to your closet directly. repeated: The texture is already uploaded by someone else. You can add it to your closet directly.
success: Texture :name was uploaded successfully. success: Texture :name was uploaded successfully.

View File

@ -20,7 +20,7 @@ homepage:
label: 开启后背景不会随页面滚动而滚动 label: 开启后背景不会随页面滚动而滚动
copyright_prefer: copyright_prefer:
title: 程序版权信息 title: 程序版权信息
description: "每种支持的语言都可以对应不同的程序版权信息,如果想要编辑某种特定语言下的版权信息,请在右上角切换至该语言后再提交修改。<b>对于任何恶意修改页面右下角的版权信息(包括不限于删除、修改作者信息、修改链接指向)的用户,作者保留对其追究责任的权利。</b>" description: '每种支持的语言都可以对应不同的程序版权信息,如果想要编辑某种特定语言下的版权信息,请在右上角切换至该语言后再提交修改。<b>对于任何恶意修改页面右下角的版权信息(包括不限于删除、修改作者信息、修改链接指向)的用户,作者保留对其追究责任的权利。</b>'
copyright_text: copyright_text:
title: 自定义版权文字 title: 自定义版权文字
description: 自定义版权文字内可使用占位符,<code>{site_name}</code> 将会被自动替换为站点名称,<code>{site_url}</code> 会被替换为站点地址。每种支持的语言都可以对应不同的自定义版权文字,如果想要编辑某种特定语言下的版权文字,请在右上角切换至该语言后再提交修改。 description: 自定义版权文字内可使用占位符,<code>{site_name}</code> 将会被自动替换为站点名称,<code>{site_url}</code> 会被替换为站点地址。每种支持的语言都可以对应不同的自定义版权文字,如果想要编辑某种特定语言下的版权文字,请在右上角切换至该语言后再提交修改。
@ -97,7 +97,10 @@ general:
regs_per_ip: 每个 IP 限制注册数 regs_per_ip: 每个 IP 限制注册数
max_upload_file_size: max_upload_file_size:
title: 最大允许上传大小 title: 最大允许上传大小
hint: "PHP 限制::size定义在 php.ini 中。" hint: 'PHP 限制::size定义在 php.ini 中。'
max_texture_width:
title: 最大允许材质宽度
hint: 允许上传的材质的最大的宽度,必须是 64 的整数倍
player_name_rule: player_name_rule:
title: 角色名规则 title: 角色名规则
official: 大小写字母数字下划线Mojang 官方的用户名规则) official: 大小写字母数字下划线Mojang 官方的用户名规则)

View File

@ -12,6 +12,7 @@ upload:
private-score-notice: 私密材质将会消耗更多的积分:每 KB 存储空间 :score 积分 private-score-notice: 私密材质将会消耗更多的积分:每 KB 存储空间 :score 积分
invalid-size: 不是有效的 :type 文件(宽 :width高 :height invalid-size: 不是有效的 :type 文件(宽 :width高 :height
invalid-hd-skin: 不是有效的高清皮肤(宽和高不是 32 的整数倍) invalid-hd-skin: 不是有效的高清皮肤(宽和高不是 32 的整数倍)
too-wide: 材质过宽(:widthpx本站允许的最大宽度为 :maxWidthpx
lack-score: 积分不足 lack-score: 积分不足
repeated: 已经有人上传过这个材质了,直接添加到衣柜使用吧~ repeated: 已经有人上传过这个材质了,直接添加到衣柜使用吧~
success: 材质 :name 上传成功 success: 材质 :name 上传成功

View File

@ -1,5 +1,7 @@
<?php <?php
use Illuminate\Support\Facades\Route;
Route::any('', 'HomeController@apiRoot'); Route::any('', 'HomeController@apiRoot');
Route::prefix('user')->middleware('auth:oauth')->group(function () { Route::prefix('user')->middleware('auth:oauth')->group(function () {

View File

@ -1,5 +1,7 @@
<?php <?php
use Illuminate\Support\Facades\Route;
Route::get('{player}.json', 'TextureController@json'); Route::get('{player}.json', 'TextureController@json');
Route::get('csl/{player}.json', 'TextureController@json'); Route::get('csl/{player}.json', 'TextureController@json');

View File

@ -1,6 +1,7 @@
<?php <?php
use App\Http\Middleware; use App\Http\Middleware;
use Illuminate\Support\Facades\Route;
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -20,7 +20,18 @@ class UpdateCommandTest extends TestCase
->once() ->once()
->andReturn(true); ->andReturn(true);
}); });
/*
* Yeah I know it's FUCKING UGLY
* But it's the only FUCKING way that WORKS
* SOMEONE REFACTOR THIS SHIT PLEASE, I BEG
*/
Cache::partialMock()->shouldReceive('flush')->once(); Cache::partialMock()->shouldReceive('flush')->once();
$mock = \Mockery::mock(\Illuminate\Contracts\Cache\Repository::class);
$mock->shouldReceive('put');
$mock->shouldReceive('get');
$this->app->instance('cache.store', $mock);
option(['version' => '0.0.0']); option(['version' => '0.0.0']);
config([ config([
'app.version' => '0.0.1', 'app.version' => '0.0.1',

View File

@ -8,10 +8,10 @@ use App\Models\Player;
use App\Models\User; use App\Models\User;
use App\Rules\Captcha; use App\Rules\Captcha;
use Blessing\Rejection; use Blessing\Rejection;
use Cache;
use Carbon\Carbon; use Carbon\Carbon;
use Event;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\URL;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -25,7 +25,7 @@ class AuthControllerTest extends TestCase
protected function setUp(): void protected function setUp(): void
{ {
parent::setUp(); parent::setUp();
app()->instance(Captcha::class, new class() extends Captcha { app()->instance(Captcha::class, new class extends Captcha {
public function passes($attribute, $value) public function passes($attribute, $value)
{ {
return true; return true;
@ -35,7 +35,7 @@ class AuthControllerTest extends TestCase
public function testLogin() public function testLogin()
{ {
$filter = Fakes\Filter::fake(); $filter = Filter::fake();
$this->get('/auth/login')->assertSee('Log in'); $this->get('/auth/login')->assertSee('Log in');
$filter->assertApplied('auth_page_rows:login'); $filter->assertApplied('auth_page_rows:login');
@ -60,28 +60,28 @@ class AuthControllerTest extends TestCase
// Should return a warning if length of `password` is lower than 6 // Should return a warning if length of `password` is lower than 6
$this->postJson( $this->postJson(
'/auth/login', [ '/auth/login', [
'identification' => $user->email, 'identification' => $user->email,
'password' => '123', 'password' => '123',
])->assertJsonValidationErrors('password'); ])->assertJsonValidationErrors('password');
// Should return a warning if length of `password` is greater than 32 // Should return a warning if length of `password` is greater than 32
$this->postJson( $this->postJson(
'/auth/login', [ '/auth/login', [
'identification' => $user->email, 'identification' => $user->email,
'password' => Str::random(80), 'password' => Str::random(80),
])->assertJsonValidationErrors('password'); ])->assertJsonValidationErrors('password');
$this->flushSession(); $this->flushSession();
// Should return a warning if user isn't existed // Should return a warning if user isn't existed
$this->postJson( $this->postJson(
'/auth/login', [ '/auth/login', [
'identification' => 'nope@nope.net', 'identification' => 'nope@nope.net',
'password' => '12345678', 'password' => '12345678',
])->assertJson([ ])->assertJson([
'code' => 2, 'code' => 2,
'message' => trans('auth.validation.user'), 'message' => trans('auth.validation.user'),
]); ]);
Event::assertDispatched('auth.login.attempt', function ($event, $payload) { Event::assertDispatched('auth.login.attempt', function ($event, $payload) {
$this->assertEquals('nope@nope.net', $payload[0]); $this->assertEquals('nope@nope.net', $payload[0]);
$this->assertEquals('12345678', $payload[1]); $this->assertEquals('12345678', $payload[1]);
@ -103,15 +103,15 @@ class AuthControllerTest extends TestCase
// Logging in should be failed if password is wrong // Logging in should be failed if password is wrong
$this->postJson( $this->postJson(
'/auth/login', [ '/auth/login', [
'identification' => $user->email, 'identification' => $user->email,
'password' => 'wrong-password', 'password' => 'wrong-password',
])->assertJson( ])->assertJson(
[ [
'code' => 1, 'code' => 1,
'message' => trans('auth.validation.password'), 'message' => trans('auth.validation.password'),
'data' => ['login_fails' => 1], 'data' => ['login_fails' => 1],
] ]
); );
$filter->assertApplied('client_ip', function ($value) use ($ip) { $filter->assertApplied('client_ip', function ($value) use ($ip) {
$this->assertEquals($ip, $value); $this->assertEquals($ip, $value);
@ -143,8 +143,8 @@ class AuthControllerTest extends TestCase
Cache::put($loginFailsCacheKey, 4); Cache::put($loginFailsCacheKey, 4);
$this->postJson( $this->postJson(
'/auth/login', [ '/auth/login', [
'identification' => $user->email, 'identification' => $user->email,
'password' => '12345678', 'password' => '12345678',
])->assertJsonValidationErrors('captcha'); ])->assertJsonValidationErrors('captcha');
Cache::flush(); Cache::flush();
@ -183,9 +183,9 @@ class AuthControllerTest extends TestCase
auth()->logout(); auth()->logout();
$this->postJson( $this->postJson(
'/auth/login', [ '/auth/login', [
'identification' => $player->name, 'identification' => $player->name,
'password' => '12345678', 'password' => '12345678',
] ]
)->assertJson( )->assertJson(
[ [
'code' => 0, 'code' => 0,
@ -240,7 +240,7 @@ class AuthControllerTest extends TestCase
public function testRegister() public function testRegister()
{ {
$filter = Fakes\Filter::fake(); $filter = Filter::fake();
$this->get('/auth/register')->assertSee('Register'); $this->get('/auth/register')->assertSee('Register');
$filter->assertApplied('auth_page_rows:register'); $filter->assertApplied('auth_page_rows:register');

View File

@ -41,20 +41,20 @@ class ClosetControllerTest extends TestCase
$user->closet()->attach($cape->tid, ['item_name' => 'custom_name']); $user->closet()->attach($cape->tid, ['item_name' => 'custom_name']);
$this->getJson('/user/closet/list?category=cape') $this->getJson('/user/closet/list?category=cape')
->assertJson(['data' => [[ ->assertJson(['data' => [[
'tid' => $cape->tid, 'tid' => $cape->tid,
'type' => 'cape', 'type' => 'cape',
'pivot' => ['item_name' => 'custom_name'], 'pivot' => ['item_name' => 'custom_name'],
], ],
]]); ]]);
// Search by keyword // Search by keyword
$random = $textures->random(); $random = $textures->random();
$this->getJson('/user/closet/list?q='.$random->name) $this->getJson('/user/closet/list?q='.$random->name)
->assertJson(['data' => [[ ->assertJson(['data' => [[
'tid' => $random->tid, 'tid' => $random->tid,
'name' => $random->name, 'name' => $random->name,
'type' => $random->type, 'type' => $random->type,
], ],
]]); ]]);
} }

View File

@ -3,7 +3,7 @@
namespace Tests; namespace Tests;
use App\Models\User; use App\Models\User;
use Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Mockery; use Mockery;

View File

@ -7,8 +7,8 @@ use App\Models\Player;
use App\Models\Texture; use App\Models\Texture;
use App\Models\User; use App\Models\User;
use Blessing\Rejection; use Blessing\Rejection;
use Event;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Event;
class PlayerControllerTest extends TestCase class PlayerControllerTest extends TestCase
{ {
@ -430,13 +430,13 @@ class PlayerControllerTest extends TestCase
// success // success
$this->deleteJson(route('user.player.clear', ['player' => $player]), [ $this->deleteJson(route('user.player.clear', ['player' => $player]), [
'skin' => true, 'skin' => true,
'cape' => true, 'cape' => true,
'nope' => true, // invalid texture type is acceptable 'nope' => true, // invalid texture type is acceptable
])->assertJson([ ])->assertJson([
'code' => 0, 'code' => 0,
'message' => trans('user.player.clear.success', ['name' => $player->name]), 'message' => trans('user.player.clear.success', ['name' => $player->name]),
]); ]);
$this->assertEquals(0, Player::find($player->pid)->tid_skin); $this->assertEquals(0, Player::find($player->pid)->tid_skin);
$this->assertEquals(0, Player::find($player->pid)->tid_cape); $this->assertEquals(0, Player::find($player->pid)->tid_cape);
Event::assertDispatched(Events\PlayerProfileUpdated::class); Event::assertDispatched(Events\PlayerProfileUpdated::class);

View File

@ -5,8 +5,8 @@ namespace Tests;
use App\Models\Player; use App\Models\Player;
use App\Models\Texture; use App\Models\Texture;
use App\Models\User; use App\Models\User;
use Event;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Event;
class PlayersManagementControllerTest extends TestCase class PlayersManagementControllerTest extends TestCase
{ {

View File

@ -7,8 +7,8 @@ use App\Models\Texture;
use App\Models\User; use App\Models\User;
use Blessing\Filter; use Blessing\Filter;
use Blessing\Rejection; use Blessing\Rejection;
use Event;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
class ReportControllerTest extends TestCase class ReportControllerTest extends TestCase

View File

@ -11,6 +11,7 @@ use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Intervention\Image\Image;
class SkinlibControllerTest extends TestCase class SkinlibControllerTest extends TestCase
{ {
@ -303,6 +304,22 @@ class SkinlibControllerTest extends TestCase
'type' => 'steve', 'type' => 'steve',
])->assertJsonValidationErrors('public'); ])->assertJsonValidationErrors('public');
// too wide texture
option(['max_texture_width' => 128]);
$this->postJson(route('texture.upload'), [
'name' => 'texture',
'file' => UploadedFile::fake()->image('wide.png', 256, 256),
'type' => 'steve',
'public' => true,
])->assertJson([
'code' => 1,
'message' => trans('skinlib.upload.too-wide', [
'width' => 256,
'maxWidth' => 128,
]),
]);
option(['max_texture_width' => 8192]);
// invalid skin size // invalid skin size
$this->postJson(route('texture.upload'), [ $this->postJson(route('texture.upload'), [
'name' => 'texture', 'name' => 'texture',
@ -437,7 +454,7 @@ class SkinlibControllerTest extends TestCase
'uploaded_texture_hash', 'uploaded_texture_hash',
function ($hash, $file) use ($texture) { function ($hash, $file) use ($texture) {
$this->assertEquals($texture->hash, $hash); $this->assertEquals($texture->hash, $hash);
$this->assertInstanceOf(UploadedFile::class, $file); $this->assertInstanceOf(Image::class, $file);
return true; return true;
} }
@ -445,7 +462,7 @@ class SkinlibControllerTest extends TestCase
Event::assertDispatched( Event::assertDispatched(
'texture.uploading', 'texture.uploading',
function ($eventName, $payload) use ($texture) { function ($eventName, $payload) use ($texture) {
$this->assertInstanceOf(UploadedFile::class, $payload[0]); $this->assertInstanceOf(Image::class, $payload[0]);
$this->assertEquals($texture->name, $payload[1]); $this->assertEquals($texture->name, $payload[1]);
$this->assertEquals($texture->hash, $payload[2]); $this->assertEquals($texture->hash, $payload[2]);
@ -456,7 +473,7 @@ class SkinlibControllerTest extends TestCase
'texture.uploaded', 'texture.uploaded',
function ($eventName, $payload) use ($texture) { function ($eventName, $payload) use ($texture) {
$this->assertTrue($texture->is($payload[0])); $this->assertTrue($texture->is($payload[0]));
$this->assertInstanceOf(UploadedFile::class, $payload[1]); $this->assertInstanceOf(Image::class, $payload[1]);
return true; return true;
} }
@ -480,15 +497,15 @@ class SkinlibControllerTest extends TestCase
$texture->uploader = $user->uid; $texture->uploader = $user->uid;
$texture->save(); $texture->save();
$this->postJson(route('texture.upload'), [ $this->postJson(route('texture.upload'), [
'name' => 'texture', 'name' => 'texture',
'public' => true, 'public' => true,
'type' => 'steve', 'type' => 'steve',
'file' => $upload, 'file' => $upload,
])->assertJson([ ])->assertJson([
'code' => 2, 'code' => 2,
'message' => trans('skinlib.upload.repeated'), 'message' => trans('skinlib.upload.repeated'),
'data' => ['tid' => $texture->tid], 'data' => ['tid' => $texture->tid],
]); ]);
// rejected // rejected
$filter->add('can_upload_texture', function ($can, $file, $name) { $filter->add('can_upload_texture', function ($can, $file, $name) {
@ -498,11 +515,11 @@ class SkinlibControllerTest extends TestCase
return new Rejection('rejected'); return new Rejection('rejected');
}); });
$this->postJson(route('texture.upload'), [ $this->postJson(route('texture.upload'), [
'name' => 'texture', 'name' => 'texture',
'public' => true, 'public' => true,
'type' => 'steve', 'type' => 'steve',
'file' => $upload, 'file' => $upload,
])->assertJson(['code' => 1, 'message' => 'rejected']); ])->assertJson(['code' => 1, 'message' => 'rejected']);
$disk->delete($texture->hash); $disk->delete($texture->hash);
} }

View File

@ -6,10 +6,10 @@ use App\Models\Player;
use App\Models\Texture; use App\Models\Texture;
use App\Models\User; use App\Models\User;
use Blessing\Minecraft; use Blessing\Minecraft;
use Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Image; use Intervention\Image\Facades\Image;
class TextureControllerTest extends TestCase class TextureControllerTest extends TestCase
{ {

View File

@ -9,8 +9,8 @@ use App\Models\User;
use Blessing\Filter; use Blessing\Filter;
use Blessing\Rejection; use Blessing\Rejection;
use Carbon\Carbon; use Carbon\Carbon;
use Event;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use League\CommonMark\GithubFlavoredMarkdownConverter; use League\CommonMark\GithubFlavoredMarkdownConverter;
@ -489,12 +489,12 @@ class UserControllerTest extends TestCase
// Administrator cannot be deleted // Administrator cannot be deleted
$this->actingAs(User::factory()->admin()->create()) $this->actingAs(User::factory()->admin()->create())
->postJson('/user/profile', [ ->postJson('/user/profile', [
'action' => 'delete', 'action' => 'delete',
'password' => '87654321', 'password' => '87654321',
])->assertJson([ ])->assertJson([
'code' => 1, 'code' => 1,
'message' => trans('user.profile.delete.admin'), 'message' => trans('user.profile.delete.admin'),
]); ]);
} }
public function testSetAvatar() public function testSetAvatar()

View File

@ -3,7 +3,7 @@
namespace Tests; namespace Tests;
use App\Models\User; use App\Models\User;
use Event; use Illuminate\Support\Facades\Event;
class FireUserAuthenticatedTest extends TestCase class FireUserAuthenticatedTest extends TestCase
{ {

View File

@ -4,8 +4,8 @@ namespace Tests;
use App\Models\User; use App\Models\User;
use App\Services\Translations\JavaScript; use App\Services\Translations\JavaScript;
use Event;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Event;
use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\DomCrawler\Crawler;
class FootComposerTest extends TestCase class FootComposerTest extends TestCase

View File

@ -2,8 +2,8 @@
namespace Tests; namespace Tests;
use Event;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Event;
use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\DomCrawler\Crawler;
class HeadComposerTest extends TestCase class HeadComposerTest extends TestCase

View File

@ -6,7 +6,7 @@ use App\Events;
use App\Models\User; use App\Models\User;
use App\Services\Plugin; use App\Services\Plugin;
use App\Services\PluginManager; use App\Services\PluginManager;
use Event; use Illuminate\Support\Facades\Event;
use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\DomCrawler\Crawler;
class SideMenuComposerTest extends TestCase class SideMenuComposerTest extends TestCase

View File

@ -3,7 +3,7 @@
namespace Tests; namespace Tests;
use App\Models\User; use App\Models\User;
use Event; use Illuminate\Support\Facades\Event;
class UserPanelComposerTest extends TestCase class UserPanelComposerTest extends TestCase
{ {

View File

@ -6,8 +6,8 @@ use App\Events;
use App\Services\Option; use App\Services\Option;
use App\Services\Plugin; use App\Services\Plugin;
use App\Services\PluginManager; use App\Services\PluginManager;
use Event;
use Illuminate\Filesystem\Filesystem; use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Facades\Event;
use ReflectionClass; use ReflectionClass;
class PluginManagerTest extends TestCase class PluginManagerTest extends TestCase
@ -425,7 +425,7 @@ class PluginManagerTest extends TestCase
->with('/mayaka/callbacks.php') ->with('/mayaka/callbacks.php')
->once() ->once()
->andReturn([ ->andReturn([
\App\Events\PluginWasDeleted::class => function ($plugin) { Events\PluginWasDeleted::class => function ($plugin) {
$this->assertInstanceOf(Plugin::class, $plugin); $this->assertInstanceOf(Plugin::class, $plugin);
$this->assertEquals('mayaka', $plugin->name); $this->assertEquals('mayaka', $plugin->name);
}, },
@ -434,7 +434,7 @@ class PluginManagerTest extends TestCase
app()->forgetInstance(PluginManager::class); app()->forgetInstance(PluginManager::class);
resolve(PluginManager::class)->boot(); resolve(PluginManager::class)->boot();
event(new \App\Events\PluginWasDeleted(new Plugin('/mayaka', ['name' => 'mayaka']))); event(new Events\PluginWasDeleted(new Plugin('/mayaka', ['name' => 'mayaka'])));
} }
public function testRegisterAutoload() public function testRegisterAutoload()