Compare commits

..

99 Commits
master ... 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
Steven Qiu
866e182c31
Fix #583 (#584) 2024-01-12 20:44:58 +08:00
Zephyr Lykos
739b9c97d7
tests: fix CheckRoleTest warning 2024-01-12 20:41:37 +08:00
Zephyr Lykos
0a43c5aa67
style: format code 2024-01-12 20:37:11 +08:00
Zephyr Lykos
35bd4524e6
ci: bump snapshot builds to PHP 8.2 2024-01-12 20:35:16 +08:00
Zephyr Lykos
eba91d5f1d
chore: update frontend deps 2024-01-12 20:31:53 +08:00
Zephyr Lykos
d764836244
ci: update deps 2024-01-12 20:31:37 +08:00
Zephyr Lykos
b1b4a71c3e
chore: update deps 2024-01-12 20:24:47 +08:00
Zephyr Lykos
fb74b43d64
upgrade dependencies 2023-08-26 14:29:53 +08:00
Zephyr Lykos
2a24506c15
config: switch default registry to CloudFront 2023-07-22 15:10:29 +08:00
Asnxthaony
f436c056e0
fix: load PluginServiceProvider first 2023-05-31 16:47:56 +08:00
Asnxthaony
77b58de7ef
update to checkout@v3 2023-05-30 17:57:08 +08:00
Asnxthaony
f5a5367e3e
fix ci 2023-05-30 17:42:45 +08:00
Asnxthaony
b811a7ae41
BREAKING: increase PHP version requirement to 8.1.0 2023-05-30 16:43:57 +08:00
Asnxthaony
d96dff1144
revert auto load Console commands 2023-05-30 15:09:39 +08:00
Asnxthaony
169ca11030
BREAKING: get ready for Laravel 10 2023-05-30 14:56:27 +08:00
Asnxthaony
b25b7588a9
use twig 3 & upgrade dependencies 2023-05-30 13:19:17 +08:00
Asnxthaony
46cccdb708
feat: generate source maps for production 2023-05-27 00:39:09 +08:00
Asnxthaony
7d9b34ecc9
fix: allow open CLI from anywhere 2023-05-27 00:37:47 +08:00
Asnxthaony
a13879f2df
fix: use unique routes name 2023-05-27 00:36:01 +08:00
Asnxthaony
1e8c0f85d5
upgrade dependencies 2023-05-26 23:46:10 +08:00
Tianle Xu
2a181c9a77
fix(i18n): typo in en/auth.yml (#527) 2023-05-06 00:36:17 +08:00
Haowei Wen
1698f1162e fix ci 2023-02-10 21:28:31 +00:00
Haowei Wen
cba50ecc59 Make skin viewer resizable 2023-02-10 20:35:30 +00:00
Haowei Wen
908f191ac8 Fix sending request to /textures/undefined 2023-02-10 19:30:04 +00:00
Haowei Wen
5a5dbea05b Upgrade skinview3d 2023-02-10 06:48:55 +08:00
Haowei Wen
950f2e6c7a Update fontawesome to 6.3.0 2023-02-09 20:13:05 +00:00
Haowei Wen
ec7be1bf63 Fix dev container 2023-02-09 19:48:30 +00:00
Pig Fang
5c4afe8f80
update middlewares 2023-01-29 11:59:30 +08:00
Pig Fang
ececb3486b
upgrade PHP dependencies 2023-01-29 11:51:06 +08:00
Pig Fang
bebdea9c20
user menu can be controlled by plugins 2023-01-26 09:37:23 +08:00
Pig Fang
b8188c91be
remove meta.js file 2023-01-25 12:01:29 +08:00
Pig Fang
0b516d02c5
clean up 2023-01-25 11:48:46 +08:00
Pig Fang
82dd87703f
first argument of option() function is required 2023-01-25 11:45:33 +08:00
Pig Fang
63b828d46b
remove Workbox 2023-01-24 18:22:16 +08:00
Pig Fang
7d005f7e4a
update test 2023-01-17 10:09:44 +08:00
Pig Fang
5750c1ae94
add test 2023-01-17 10:08:04 +08:00
Pig Fang
7eff0cb66d
update Composer lock file 2023-01-17 10:05:39 +08:00
Pig Fang
eff859a864
update php-cs-fixer config & apply fixes 2023-01-16 23:15:41 +08:00
Pig Fang
64dea61ec9
refactor 2023-01-16 23:06:31 +08:00
Pig Fang
0b169df679
fix running php-cs-fixer 2023-01-16 23:02:44 +08:00
Pig Fang
e72b68f8d6
disable PHP 8.0 in CI temporarily 2023-01-16 22:58:37 +08:00
Pig Fang
f1414c1dcb
add PHP 8.2 in CI 2023-01-16 22:56:27 +08:00
Pig Fang
7086098536
fix #463 2023-01-16 22:54:49 +08:00
Pig Fang
fb35d58a65
update Composer lock file 2023-01-16 22:53:08 +08:00
薄荷牛奶萌
9120efe1c6
The interface has been modified. (#514)
Co-authored-by: Pig Fang <g-plane@hotmail.com>
Co-authored-by: Zephyr Lykos <git@mochaa.ws>
2023-01-11 12:58:37 +08:00
Asnxthaony
564672b436
fix: allow logout when user are banned 2022-12-29 12:06:58 +08:00
Pig Fang
cc8873ec31
New translations setup.yml (Chinese Simplified) (#510) 2022-12-10 10:08:44 +08:00
Pig Fang
fa90b96682
bundle Font Awesome 2022-10-08 15:58:14 +08:00
Pig Fang
f1c7845b8d
New translations admin.yml (Japanese) (#479) 2022-09-29 22:58:56 +08:00
Pig Fang
be244cde60
New translations user.yml (Chinese Simplified) (#469) 2022-08-21 15:02:10 +08:00
bingling_sama
2dc2409d3f
fix: email verification resend link visibility on dark mode (#461) 2022-08-05 14:26:11 +08:00
Pig Fang
32fa6a8a52
New Crowdin updates (#455)
* New translations auth.yml (Spanish)

* New translations options.yml (Spanish)

* New translations user.yml (Spanish)
2022-08-05 14:24:03 +08:00
mochaaP
ec790b41f1
chore(bootstrap): remove environment check
This is not necessary as we should leave troubleshooting to users themselves. It's not our responsibility to warn about misconfigurations.
2022-07-14 03:31:52 +08:00
Pig Fang
b8337bed19
New translations user.yml (Chinese Traditional) (#439) 2022-07-13 01:14:24 +08:00
mochaaP
73e308f15a fix(admin): chart title color on dark mode 2022-07-11 21:16:47 +00:00
mochaaP
3e50237846 Revert "chore(gitpod): private port by default"
This reverts commit b7c3e06590.
2022-07-11 21:05:33 +00:00
Pig Fang
33b64625d9
New translations auth.yml (Spanish) (#436) 2022-07-12 04:15:52 +08:00
Pig Fang
c2c6ef8e51
New Crowdin updates (#432)
* New translations user.yml (French)

* New translations user.yml (Spanish)

* New translations user.yml (German)

* New translations user.yml (Greek)

* New translations user.yml (Italian)

* New translations user.yml (Japanese)

* New translations user.yml (Korean)

* New translations user.yml (Dutch)

* New translations user.yml (Portuguese)

* New translations user.yml (Russian)

* New translations user.yml (Chinese Simplified)

* New translations user.yml (Chinese Traditional)
2022-07-06 14:00:13 +08:00
Haowei Wen
a153753dc5 fix typo 2022-07-06 13:43:14 +08:00
Asnxthaony
e965a53c18
feat(closet): add sanity check on closet management 2022-07-05 17:09:06 +08:00
Asnxthaony
1a7f76ea69
fix(skinlib): cast allow_downloading_texture to boolean 2022-07-04 19:11:05 +08:00
mochaaP
fe7307d655
chore(docs): add requirement for OpenSSL 1.1.1 [skip ci] 2022-06-28 14:56:24 +08:00
mochaaP
411f85b43b
chore(docs): add requirement for OpenSSL 1.1.1 2022-06-28 14:55:18 +08:00
mochaaP
4303d14e64
chore: remove stale bot 2022-06-27 11:39:46 +08:00
mochaaP
b7c3e06590 chore(gitpod): private port by default 2022-06-23 10:19:33 +00:00
mochaaP
8fd8b806b1 Revert "fix(test): disable concurrency"
This reverts commit b190ffb3d2.
2022-06-23 09:18:57 +00:00
mochaaP
2786626a3f fix: text overflow on home page 2022-06-23 08:39:43 +00:00
mochaaP
8279817d8c chore: switch default marketplace source 2022-06-23 08:19:46 +00:00
154 changed files with 18146 additions and 18839 deletions

View File

@ -0,0 +1,45 @@
APP_DEBUG=true
APP_ENV=development
APP_FALLBACK_LOCALE=en
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=blessingskin
DB_USERNAME=username
DB_PASSWORD=secret
DB_PREFIX=
# Hash Algorithm for Passwords
#
# Available values:
# - BCRYPT, ARGON2I, PHP_PASSWORD_HASH
# - MD5, SALTED2MD5
# - SHA256, SALTED2SHA256
# - SHA512, SALTED2SHA512
#
# New sites are *highly* recommended to use BCRYPT.
#
PWD_METHOD=BCRYPT
APP_KEY=base64:JaytOHG/JlLgulTVAhiS0tRqnAfCkQydbdP6VRmoAMY=
MAIL_MAILER=smtp
MAIL_HOST=
MAIL_PORT=465
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=
MAIL_FROM_ADDRESS=
MAIL_FROM_NAME=
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_CONNECTION=sync
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
PLUGINS_DIR=null
PLUGINS_URL=null

View File

@ -14,6 +14,9 @@ RUN docker-php-ext-install mysqli pdo pdo_mysql gd zip
ARG NODE_VERSION="none" ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
# Enable Apache rewrite module
RUN a2enmod rewrite
# [Optional] Uncomment this section to install additional OS packages. # [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here> # && apt-get -y install --no-install-recommends <your-package-list-here>

View File

@ -0,0 +1,13 @@
Listen 8080
<Directory /workspace/public/>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
<VirtualHost *:8080>
DocumentRoot /workspace/public
ErrorLog /workspace/storage/logs/apache-error.log
CustomLog /workspace/storage/logs/apache-access.log combined
</VirtualHost>

View File

@ -2,30 +2,33 @@
// https://github.com/microsoft/vscode-dev-containers/tree/v0.231.6/containers/php-mariadb // https://github.com/microsoft/vscode-dev-containers/tree/v0.231.6/containers/php-mariadb
// Update the VARIANT arg in docker-compose.yml to pick a PHP version // Update the VARIANT arg in docker-compose.yml to pick a PHP version
{ {
"name": "PHP & MariaDB (Community)", "name": "PHP & MariaDB (Community)",
"dockerComposeFile": "docker-compose.yml", "dockerComposeFile": "docker-compose.yml",
"service": "app", "service": "app",
"workspaceFolder": "/workspace", "workspaceFolder": "/workspace",
// Set *default* container specific settings.json values on container create. // Set *default* container specific settings.json values on container create.
"settings": { }, "settings": {},
// Add the IDs of extensions you want installed when the container is created. // Add the IDs of extensions you want installed when the container is created.
"extensions": [ "extensions": [
"xdebug.php-debug", "xdebug.php-debug",
"bmewburn.vscode-intelephense-client", "bmewburn.vscode-intelephense-client",
"mrmlnc.vscode-apache" "mrmlnc.vscode-apache"
], ],
// For use with PHP or Apache (e.g.php -S localhost:8080 or apache2ctl start) // For use with PHP or Apache (e.g.php -S localhost:8080 or apache2ctl start)
"forwardPorts": [8080, 3306], "forwardPorts": [8080, 3306],
// Use 'postCreateCommand' to run commands after the container is created. // Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "cp .env.example .env && sudo chmod a+x \"$(pwd)\" && sudo rm -rf /var/www/html && sudo ln -s \"$(pwd)/public\" /var/www/html && composer install && yarn install", "postCreateCommand": "sudo truncate -s 0 /etc/apache2/ports.conf && sudo rm -f /etc/apache2/sites-enabled/000-default.conf && sudo ln -sf /workspace/.devcontainer/blessing-skin.apache.conf /etc/apache2/sites-enabled/ && ln -sf .devcontainer/.env.devcontainer .env && composer install && yarn install",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. // Start apache2 after the container is started
"remoteUser": "vscode", "postStartCommand": "apache2ctl start && echo '\\n👉 \\e[0;32mPlease run '\\'yarn build\\'' to build the frontend. Application is available on port 8080.\\e[0m 👈\\n'",
"features": {
"powershell": "latest" // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
} "remoteUser": "vscode",
"features": {
"powershell": "latest"
}
} }

View File

@ -7,8 +7,6 @@
coverage/ coverage/
node_modules/ node_modules/
plugins/** plugins/**
public/sw.js
public/meta.js
public/app/* public/app/*
public/lang/* public/lang/*
public/plugins/** public/plugins/**

View File

@ -35,3 +35,71 @@ PLUGINS_URL=
TEXTURES_DIR= TEXTURES_DIR=
JWT_SECRET=1tdM3gXarxYI4KlAHMBo238iC2tEb4I3EtBlZTQQXvInXIt7V2ix7hJ1KTvxCKZW JWT_SECRET=1tdM3gXarxYI4KlAHMBo238iC2tEb4I3EtBlZTQQXvInXIt7V2ix7hJ1KTvxCKZW
PASSPORT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQC6q6SCprX3yfOE
DFBnfFk3R+33qvoe26nYQkavKfv7zA9KQxCBNHEsFKOQ6ui+ViebVHAIHBPm2518
REVMLN2JONvXbPETV6gJO/b6FFwo2Aow/GbTnesLhWEAPW11ei0/hBbjWF9hQZ/n
x3YsFk0xtml2iPDijfUohwp50iFyCQylw4S5Sy3vuVdM063dkxvECsU6wmHDev9C
PxFZGl3W2iLSwttYl7xmlwll8xuqxDQUJpJbOxrPeDKdDI1ikarSqA1c8bV1YLT+
CHxB7T5b1EPeaYRmeLl+wyd/ZxeBWWgDLBusi5wPFpSEIVxu1RzTYarOXEpD+XNV
Ohpb7LxpbJx8YRJru1B6CBouVO2pqoCEM21GtG7zSDNtaY+yVcj9Kf2SIbfz0e6a
SLAcyOT0q3aId4+z9q8TAfPVV7y0D7B3PaOz/3pMC+jHDjCRwQkN/DR7ODUKEjCd
UjsveGHtseBa3qBoKWcg39ZvwYeDF0P/cFa/yOqw5JxyWhbjk95YdPaSixdUyyMG
XkFTOrnBREVtBAfdTOG6WTFcIlyJK+ST0cJXVcrjFonbmJCCwTJqxz9t+935CfTt
CzLLPdONU16jZJ7j1cVoWb4L8o0OA+FBSawBxFOKyhFlh3+HKRnSCZSDqaSDsYUf
M3IupiGUcByhpZ9mNqhnLeivIXwapwIDAQABAoICAFu27F+S2DH0A9S7lh+aPV1H
VniKhVR2+aZ6va7fUmJ++n4yoB/TK82MIGcZu5uUyeXr4RVi8jZJbcF565BHNNtw
V7cq2/F0bmeHEkwBh9w7dRpnUIAlhS/GawfKpoaDLksYM4SkzUwECbQ/0GRN2sST
ipKGKtAtDihI3RFIeE1Ge/PPsdy2Ps4bAnUJRdHpLsmtvwSlL5JzUonyYawlI7jl
uRlTSqDnAFZpW+E+xjerKalC4EK5se0AceGuoqKszkCs98/UJCMVDigH9EER9sL4
chYLQtVz+DN7X+MdPDO9wThZygkHGPhi0DpxB7CevYhv4pN8TbLDE3Lq1ruWf2T3
7ts21ymjVIiOayBA9l86P0FSS/lP9KF53LyeNkOJKUy5On6xHoW5IKlrMJdmGwFH
B4yaR7bw5vxErhpMTzcYJVWqjCbo+PBJhdy7x+2XrrBLs9X0hfS/jeeAIuRlzju1
9xe3zO6U41sDjkCkrUavOn57DL6jh9LMgxT8cZkSdrP6rpawEyjPUi5kMbbQkv0j
eWiqz0vozJN5HcVpj36F5kqZyCnIojmeo4FCKdn7n/wvyGYQPSAekVpV80KzoJ2j
GQ440Q7Sgozj/Lw4cCPgG3/MA1Dwu+TUuaddFjBH2oqZ2X0bCqVCEVWhfmaD4z2R
I9C9nLvpxoMtcCHkG1VFAoIBAQDfawcBHMPxZQOy/qTqXZd7YR3bzGgd/SkLEWwg
PtDGKe36tDf3E/RGRij4HGl9v/fA7N/CufW0tJY7Ii/cn7yYdZg/dzaCoYIZbICl
CytWtsM0iH3XPuY3UpgsOwML0xK8hdD1U7qBKk5rnjF9Q9vKrB8ATR9hM0bPaGtr
Bqfx8A+kj6wqRpA8jbN0kZxJVv0/LgZSCrH3qUjHfXoYYtLhMBZd8UydyvyETo/1
Z44WS9oNqX8mBUvHjsuOQx3+eFPOr69QPIo06QMxytSEzMilgz40QUBWF68gKwGi
NYdUfR3IXVTmvJhYH2mQWqMKVk+KJFd2UanjKbBCOKrDSG4TAoIBAQDV5LMp/ztd
YQvMCWJzUrpazGqkoEGli/qxWb/pDpemgQT++lt6PBRQmmLKXZfNp9VJdZdP6+lF
ypGcA8tACY93m7Fk4wtewXG+0oTxmBkWqSiiO3ExoBQTxXLoZ9GwKt/exaM25QJ1
O2livxrYFFJbUe1YRqQENIURYk13RgeIaWS0gd9vp/yp0EhZAvGUPHFjKRsLjw7Y
gZDJ+lXj2pXg9THUzkhVDm+fM6blIsLcvf7qc8yKQI3nwZr00e/ba7xSNF8AhpdP
rxw59vm1RzsngZpZOK0Z1143gFRtGhhVWtvmhCvJo1EOssFu01ixE0bNq5nRIIJo
O/mY7NC9bCOdAoIBAEBfcyYz5pUwGM/DJTtN+i6XfeXt0HYLkn7Y50GnN7pRLHuW
36U2P6Tb5EQQ06hi3nzdA1/0+sG1Yq/pGsdD0zBOea6Xp8IdzQGMTMjBHhyfDkGd
rjyNqAF6r9PWsPsANx7Qo7N8C3nZ+bxyWSoRmkucKlaI4ii8gIOUP5cX1N4V4Dv3
FZEcwcRgw7srlU9gXBmPJk0PPdXxFcI8+if6mW4+z8MDmqLAcN+iT0JTMxJjipFz
K+qFjh8Smr4Dwqmme+dKoYXJ27yBAuWe3nrhElL2LL8bqfDkZBYtrgvRxotmfWVU
1vigkHibnGv2YZHB6qsP649w2jVUtq9t6m3X+bcCggEAEKDqCN7N17GewCsOm1aY
JEz2EXxf/iXGxJjsoYq/4XLwV35RNEyNa8LE4WSrU5KzszVQISd/CCz6av2khIL5
w1u4S9aW4LP7StGFAl9HvApEnXAvmaMPTIYyK70+gQqkQuZsjOz65vBKfiHLTXcu
++h/ojhDsgv/OF3DFf28wi8nZB0gqMaPjwghR8JB07trOUFN1/U0O0K/ZeRvXvp0
YnvNdvTejLZFmUPjuracHZsrwUBla24fWiAkEtprYkya5G0r4ZeVFd3QPPVlbmFu
SOD7heoxEuw6Z+gzKBQ6RhB9PguSd+eZeqINBbeqkoGkJIMtvyNe4AmhmvD2PXO1
xQKCAQAx12vD3q2+DHrpJ4fKGp1RbCxFIOYgXcBJ6zCozVZXpLUa54IBTLyQyDNF
D8+Wq5b1IbCnxBn55tsgq/CSLkVHigVfUDGnAt3lD+ggLdZmu7ayQY0BNe1quF5M
EgkOE0uYtq+2p+u4gxRB+gnQd1YIlPs0U0mrawbzV5ZX2vR5Ry1XpBno75JpnUbN
3D9DuIDVLuqsKJcx6KClWef2PzJB2uN7jDf4UnCtp5QCFB1GNt+5AzCNh9Bozas8
OflASrSn64AeyCZycCplmRY/F4BnH++7YI0Q/mjawByW7qYoHkFzmKuUcgakh200
y/ieeWy8Vunl0e4T4Bz/0zInBifn
-----END PRIVATE KEY-----"
PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuqukgqa198nzhAxQZ3xZ
N0ft96r6Htup2EJGryn7+8wPSkMQgTRxLBSjkOrovlYnm1RwCBwT5tudfERFTCzd
iTjb12zxE1eoCTv2+hRcKNgKMPxm053rC4VhAD1tdXotP4QW41hfYUGf58d2LBZN
MbZpdojw4o31KIcKedIhcgkMpcOEuUst77lXTNOt3ZMbxArFOsJhw3r/Qj8RWRpd
1toi0sLbWJe8ZpcJZfMbqsQ0FCaSWzsaz3gynQyNYpGq0qgNXPG1dWC0/gh8Qe0+
W9RD3mmEZni5fsMnf2cXgVloAywbrIucDxaUhCFcbtUc02GqzlxKQ/lzVToaW+y8
aWycfGESa7tQeggaLlTtqaqAhDNtRrRu80gzbWmPslXI/Sn9kiG389HumkiwHMjk
9Kt2iHePs/avEwHz1Ve8tA+wdz2js/96TAvoxw4wkcEJDfw0ezg1ChIwnVI7L3hh
7bHgWt6gaClnIN/Wb8GHgxdD/3BWv8jqsOSccloW45PeWHT2kosXVMsjBl5BUzq5
wURFbQQH3UzhulkxXCJciSvkk9HCV1XK4xaJ25iQgsEyasc/bfvd+Qn07Qsyyz3T
jVNeo2Se49XFaFm+C/KNDgPhQUmsAcRTisoRZYd/hykZ0gmUg6mkg7GFHzNyLqYh
lHAcoaWfZjaoZy3oryF8GqcCAwEAAQ==
-----END PUBLIC KEY-----"

11
.github/stale.yml vendored
View File

@ -1,11 +0,0 @@
daysUntilStale: 7
daysUntilClose: 7
exemptLabels:
- bug
- enhancement
staleLabel: stale
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs.
该 issue 在近期内缺少更新或答复如果仍然缺少更新或响应issue 将被关闭。

View File

@ -20,20 +20,19 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: 8.0 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
@ -45,24 +44,24 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
php: ['8.0', '8.1'] php: ['8.2', '8.3']
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Setup PHP only - name: Setup PHP only
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
if: matrix.php != '8.0' if: matrix.php != '8.3'
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.0' 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:
@ -72,14 +71,14 @@ jobs:
- name: Install Composer dependencies - name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Run tests only - name: Run tests only
if: matrix.php != '8.0' if: matrix.php != '8.3'
run: ./vendor/bin/phpunit run: ./vendor/bin/phpunit
- name: Run tests with coverage report - name: Run tests with coverage report
if: matrix.php == '8.0' if: matrix.php == '8.3'
run: ./vendor/bin/phpunit --coverage-clover=coverage.xml run: ./vendor/bin/phpunit --coverage-clover=coverage.xml
- name: Upload coverage report - name: Upload coverage report
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v1
if: matrix.php == '8.0' && success() if: matrix.php == '8.3' && success()
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
name: github-actions name: github-actions
@ -88,7 +87,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Install dependencies - name: Install dependencies
run: yarn install --frozen-lockfile run: yarn install --frozen-lockfile
- name: Run checks - name: Run checks
@ -101,7 +100,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Install dependencies - name: Install dependencies
run: yarn run: yarn
- name: Run tests - name: Run tests
@ -115,8 +114,14 @@ jobs:
name: Snapshot Build name: Snapshot Build
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.2
coverage: none
extensions: mbstring, dom, fileinfo, sqlite, gd, zip, imagick
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Cache Node dependencies - name: Cache Node dependencies
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
@ -138,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

View File

@ -9,32 +9,32 @@ jobs:
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Build and create archive - name: Build and create archive
run: ./tools/release.ps1 run: ./tools/release.ps1
shell: pwsh shell: pwsh
env: env:
AZURE_TOKEN: ${{ secrets.AZURE_TOKEN }} AZURE_TOKEN: ${{ secrets.AZURE_TOKEN }}
- name: Create Release - name: Create Release
id: create_release id: create_release
uses: actions/create-release@v1 uses: actions/create-release@v1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
tag_name: ${{ github.ref }} tag_name: ${{ github.ref }}
release_name: ${{ github.ref }} release_name: ${{ github.ref }}
prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc') }} prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
- name: Get version - name: Get version
id: get_version id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
- name: Upload release asset - name: Upload release asset
id: upload_release_asset id: upload_release_asset
uses: actions/upload-release-asset@v1 uses: actions/upload-release-asset@v1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./blessing-skin-server-${{ steps.get_version.outputs.VERSION }}.zip asset_path: ./blessing-skin-server-${{ steps.get_version.outputs.VERSION }}.zip
asset_name: blessing-skin-server-${{ steps.get_version.outputs.VERSION }}.zip asset_name: blessing-skin-server-${{ steps.get_version.outputs.VERSION }}.zip
asset_content_type: application/zip asset_content_type: application/zip

4
.gitignore vendored
View File

@ -25,5 +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
public/sw.js .DS_Store
public/meta.js */.DS_Store

View File

@ -8,11 +8,16 @@ $finder = PhpCsFixer\Finder::create()
$config = new PhpCsFixer\Config(); $config = new PhpCsFixer\Config();
return $config->setRules([ return $config->setRules([
'@Symfony' => true, '@Symfony' => true,
'align_multiline_comment' => true, 'align_multiline_comment' => true,
'array_syntax' => ['syntax' => 'short'], 'array_syntax' => ['syntax' => 'short'],
'increment_style' => ['style' => 'post'], 'increment_style' => ['style' => 'post'],
'list_syntax' => ['syntax' => 'short'], 'list_syntax' => ['syntax' => 'short'],
'yoda_style' => false, 'yoda_style' => false,
]) 'global_namespace_import' => [
'import_constants' => true,
'import_functions' => true,
'import_classes' => null,
],
])
->setFinder($finder); ->setFinder($finder);

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

@ -40,9 +40,9 @@ Blessing Skin 是一个开源的 PHP 项目,这意味着您可以自由地在
Blessing Skin 对您的服务器有一定的要求。在大多数情况下,下列所需的 PHP 扩展已经开启。 Blessing Skin 对您的服务器有一定的要求。在大多数情况下,下列所需的 PHP 扩展已经开启。
- 一台支持 URL 重写的主机Nginx 或 Apache - 一台支持 URL 重写的主机Nginx 或 Apache
- PHP >= 8.0.2 - PHP >= 8.1.0
- 安装并启用如下 PHP 扩展: - 安装并启用如下 PHP 扩展:
- OpenSSL - OpenSSL >= 1.1.1 (TLS 1.3)
- PDO - PDO
- Mbstring - Mbstring
- Tokenizer - Tokenizer
@ -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)。
## 相关链接 ## 相关链接

103
README.md
View File

@ -4,7 +4,7 @@
<p align="center"><img src="https://media.githubusercontent.com/media/bs-community/logo/main/logo.png"></p> <p align="center"><img src="https://media.githubusercontent.com/media/bs-community/logo/main/logo.png"></p>
<p align="center"> <p align="center">
<a href="https://github.com/bs-community/blessing-skin-server/actions"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/workflow/status/bs-community/blessing-skin-server/CI?style=flat-square"></a> <a href="https://github.com/bs-community/blessing-skin-server/actions"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/bs-community/blessing-skin-server/CI.yml?branch=dev&style=flat-square"></a>
<a href="https://codecov.io/gh/bs-community/blessing-skin-server"><img alt="Codecov" src="https://img.shields.io/codecov/c/github/bs-community/blessing-skin-server?style=flat-square"></a> <a href="https://codecov.io/gh/bs-community/blessing-skin-server"><img alt="Codecov" src="https://img.shields.io/codecov/c/github/bs-community/blessing-skin-server?style=flat-square"></a>
<a href="https://github.com/bs-community/blessing-skin-server/releases"><img alt="GitHub release (latest SemVer including pre-releases)" src="https://img.shields.io/github/v/release/bs-community/blessing-skin-server?include_prereleases&style=flat-square"></a> <a href="https://github.com/bs-community/blessing-skin-server/releases"><img alt="GitHub release (latest SemVer including pre-releases)" src="https://img.shields.io/github/v/release/bs-community/blessing-skin-server?include_prereleases&style=flat-square"></a>
<a href="https://github.com/bs-community/blessing-skin-server/blob/master/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/bs-community/blessing-skin-server?style=flat-square"></a> <a href="https://github.com/bs-community/blessing-skin-server/blob/master/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/bs-community/blessing-skin-server?style=flat-square"></a>
@ -40,9 +40,9 @@ Blessing Skin is an open-source project written in PHP, which means you can depl
Blessing Skin has only a few system requirements. In most cases, these PHP extensions are already enabled. Blessing Skin has only a few system requirements. In most cases, these PHP extensions are already enabled.
- Web server with URL rewriting enabled (Nginx or Apache) - Web server with URL rewriting enabled (Nginx or Apache)
- PHP >= 8.0.2 - PHP >= 8.1.0
- PHP Extensions - PHP Extensions
- OpenSSL - OpenSSL >= 1.1.1 (TLS 1.3)
- PDO - PDO
- Mbstring - Mbstring
- Tokenizer - Tokenizer
@ -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

@ -53,8 +53,9 @@ class Handler extends ExceptionHandler
}) })
->filter(function ($trace) { ->filter(function ($trace) {
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
$isFromPlugins = !app()->runningUnitTests() && $isFromPlugins = !app()->runningUnitTests()
Str::contains($trace['file'], resolve('plugins')->getPluginsDirs()->all()); && Str::contains($trace['file'], resolve('plugins')->getPluginsDirs()->all());
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
return Str::startsWith($trace['file'], 'app') || $isFromPlugins; return Str::startsWith($trace['file'], 'app') || $isFromPlugins;
}) })

View File

@ -53,7 +53,7 @@ class AdminController extends Controller
$grouping = fn ($field) => fn ($item) => Carbon::parse($item->$field)->isoFormat('l'); $grouping = fn ($field) => fn ($item) => Carbon::parse($item->$field)->isoFormat('l');
$mapping = fn ($item) => count($item); $mapping = fn ($item) => count($item);
$aligning = fn ($data) => fn ($day) => ($data->get($day) ?? 0); $aligning = fn ($data) => fn ($day) => $data->get($day) ?? 0;
/** @var Collection */ /** @var Collection */
$userRegistration = User::where('register_at', '>=', $oneMonthAgo) $userRegistration = User::where('register_at', '>=', $oneMonthAgo)
@ -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) {
@ -176,8 +176,8 @@ class AuthController extends Controller
$dispatcher->dispatch('auth.registration.attempt', [$data]); $dispatcher->dispatch('auth.registration.attempt', [$data]);
if ( if (
option('register_with_player_name') && option('register_with_player_name')
Player::where('name', $playerName)->count() > 0 && Player::where('name', $playerName)->count() > 0
) { ) {
return json(trans('user.player.add.repeated'), 1); return json(trans('user.player.add.repeated'), 1);
} }
@ -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

@ -17,8 +17,15 @@ class ClosetManagementController extends Controller
public function add(Request $request, Dispatcher $dispatcher, User $user) public function add(Request $request, Dispatcher $dispatcher, User $user)
{ {
$tid = $request->input('tid'); $tid = $request->input('tid');
/** @var Texture */ $texture = Texture::find($tid);
$texture = Texture::findOrFail($tid); if (!$texture) {
return json(trans('user.closet.add.not-found'), 1);
}
if ($user->closet()->where('tid', $request->tid)->count() > 0) {
return json(trans('user.closet.add.repeated'), 1);
}
$name = $texture->name; $name = $texture->name;
$dispatcher->dispatch('closet.adding', [$tid, $name, $user]); $dispatcher->dispatch('closet.adding', [$tid, $name, $user]);
@ -35,10 +42,14 @@ class ClosetManagementController extends Controller
$tid = $request->input('tid'); $tid = $request->input('tid');
$dispatcher->dispatch('closet.removing', [$tid, $user]); $dispatcher->dispatch('closet.removing', [$tid, $user]);
/** @var Texture */ $item = $user->closet()->find($tid);
$texture = Texture::findOrFail($tid); if (empty($item)) {
return json(trans('user.closet.remove.non-existent'), 1);
}
$user->closet()->detach($texture->tid); $user->closet()->detach($tid);
$texture = Texture::find($tid);
$dispatcher->dispatch('closet.removed', [$texture, $user]); $dispatcher->dispatch('closet.removed', [$texture, $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

@ -23,8 +23,8 @@ class PlayersManagementController extends Controller
$currentUser = $request->user(); $currentUser = $request->user();
if ( if (
$owner->uid !== $currentUser->uid && $owner->uid !== $currentUser->uid
$owner->permission >= $currentUser->permission && $owner->permission >= $currentUser->permission
) { ) {
return json(trans('admin.players.no-permission'), 1) return json(trans('admin.players.no-permission'), 1)
->setStatusCode(403); ->setStatusCode(403);
@ -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'])],
@ -88,9 +88,9 @@ class ReportController extends Controller
if ($action == 'reject') { if ($action == 'reject') {
if ( if (
$report->informer && $report->informer
($score = option('reporter_score_modification', 0)) > 0 && && ($score = option('reporter_score_modification', 0)) > 0
$report->status == Report::PENDING && $report->status == Report::PENDING
) { ) {
$report->informer->score -= $score; $report->informer->score -= $score;
$report->informer->save(); $report->informer->save();
@ -151,9 +151,9 @@ class ReportController extends Controller
public static function returnScore($report) public static function returnScore($report)
{ {
if ( if (
$report->status == Report::PENDING && $report->status == Report::PENDING
($score = option('reporter_score_modification', 0)) < 0 && && ($score = option('reporter_score_modification', 0)) < 0
$report->informer && $report->informer
) { ) {
$report->informer->score -= $score; $report->informer->score -= $score;
$report->informer->save(); $report->informer->save();

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
{ {
@ -134,7 +135,7 @@ class SkinlibController extends Controller
->with('texture', $texture) ->with('texture', $texture)
->with('grid', $grid) ->with('grid', $grid)
->with('extra', [ ->with('extra', [
'download' => option('allow_downloading_texture'), 'download' => (bool) option('allow_downloading_texture'),
'currentUid' => $user ? $user->uid : 0, 'currentUid' => $user ? $user->uid : 0,
'admin' => $user && $user->isAdmin(), 'admin' => $user && $user->isAdmin(),
'inCloset' => $user && $user->closet()->where('tid', $texture->tid)->count() > 0, 'inCloset' => $user && $user->closet()->where('tid', $texture->tid)->count() > 0,
@ -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
{ {
@ -330,9 +330,9 @@ class UserController extends Controller
} }
if ( if (
!$texture->public && !$texture->public
$user->uid !== $texture->uploader && && $user->uid !== $texture->uploader
!$user->isAdmin() && !$user->isAdmin()
) { ) {
return json(trans('skinlib.show.private'), 1); return json(trans('skinlib.show.private'), 1);
} }

View File

@ -18,8 +18,8 @@ class UsersManagementController extends Controller
$authUser = $request->user(); $authUser = $request->user();
if ( if (
$targetUser->isNot($authUser) && $targetUser->isNot($authUser)
$targetUser->permission >= $authUser->permission && $targetUser->permission >= $authUser->permission
) { ) {
return json(trans('admin.users.operations.no-permission'), 1) return json(trans('admin.users.operations.no-permission'), 1)
->setStatusCode(403); ->setStatusCode(403);
@ -134,8 +134,8 @@ class UsersManagementController extends Controller
$permission = (int) $data['permission']; $permission = (int) $data['permission'];
if ( if (
$permission === User::ADMIN && $permission === User::ADMIN
$request->user()->permission < User::SUPER_ADMIN && $request->user()->permission < User::SUPER_ADMIN
) { ) {
return json(trans('admin.users.operations.no-permission'), 1) return json(trans('admin.users.operations.no-permission'), 1)
->setStatusCode(403); ->setStatusCode(403);

View File

@ -11,19 +11,20 @@ class Kernel extends HttpKernel
* *
* These middleware are run during every request to your application. * These middleware are run during every request to your application.
* *
* @var array * @var array<int, class-string|string>
*/ */
protected $middleware = [ protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class, \Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\Illuminate\Foundation\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\TrimStrings::class,
\App\Http\Middleware\ConvertEmptyStringsToNull::class, Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\DetectLanguagePrefer::class, Middleware\DetectLanguagePrefer::class,
]; ];
/** /**
* The application's route middleware groups. * The application's route middleware groups.
* *
* @var array * @var array<string, array<int, class-string|string>>
*/ */
protected $middlewareGroups = [ protected $middlewareGroups = [
'web' => [ 'web' => [
@ -32,39 +33,38 @@ class Kernel extends HttpKernel
\Illuminate\Session\Middleware\StartSession::class, \Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class,
\App\Http\Middleware\EnforceEverGreen::class, Middleware\EnforceEverGreen::class,
\App\Http\Middleware\RedirectToSetup::class, Middleware\RedirectToSetup::class,
'bindings', \Illuminate\Routing\Middleware\SubstituteBindings::class,
], ],
'api' => [ 'api' => [
'bindings', \Illuminate\Routing\Middleware\SubstituteBindings::class,
], ],
'authorize' => [ 'authorize' => [
'auth:web', 'auth:web',
\App\Http\Middleware\RejectBannedUser::class, Middleware\RejectBannedUser::class,
\App\Http\Middleware\EnsureEmailFilled::class, Middleware\EnsureEmailFilled::class,
\App\Http\Middleware\FireUserAuthenticated::class, Middleware\FireUserAuthenticated::class,
], ],
]; ];
/** /**
* The application's route middleware. * The application's middleware aliases.
* *
* These middleware may be assigned to groups or used individually. * Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
* *
* @var array * @var array<string, class-string|string>
*/ */
protected $routeMiddleware = [ protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class, 'auth' => Middleware\Authenticate::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'guest' => Middleware\RedirectIfAuthenticated::class,
'role' => \App\Http\Middleware\CheckRole::class, 'role' => Middleware\CheckRole::class,
'setup' => \App\Http\Middleware\CheckInstallation::class, 'setup' => Middleware\CheckInstallation::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \App\Http\Middleware\CheckUserVerified::class, 'verified' => Middleware\CheckUserVerified::class,
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class, 'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class, 'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
]; ];

View File

@ -14,7 +14,7 @@ class Authenticate extends Middleware
'msg' => trans('auth.check.anonymous'), 'msg' => trans('auth.check.anonymous'),
]); ]);
return '/auth/login'; return route('auth.login');
} }
} }
} }

View File

@ -2,16 +2,17 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use App\Models\User;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class CheckRole class CheckRole
{ {
protected $roles = [ protected $roles = [
'banned' => -1, 'banned' => User::BANNED,
'normal' => 0, 'normal' => User::NORMAL,
'admin' => 1, 'admin' => User::ADMIN,
'super-admin' => 2, 'super-admin' => User::SUPER_ADMIN,
]; ];
public function handle(Request $request, Closure $next, $role) public function handle(Request $request, Closure $next, $role)

View File

@ -14,8 +14,8 @@ class DetectLanguagePrefer
?? $request->cookie('locale') ?? $request->cookie('locale')
?? $request->getPreferredLanguage(); ?? $request->getPreferredLanguage();
if ( if (
($info = Arr::get(config('locales'), $locale)) && ($info = Arr::get(config('locales'), $locale))
($alias = Arr::get($info, 'alias')) && ($alias = Arr::get($info, 'alias'))
) { ) {
$locale = $alias; $locale = $alias;
} }
@ -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

@ -3,12 +3,16 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use App\Models\User; use App\Models\User;
use Closure; use Illuminate\Http\Request;
class RejectBannedUser class RejectBannedUser
{ {
public function handle($request, Closure $next) public function handle(Request $request, \Closure $next)
{ {
if ($request->route()->getName() === 'auth.logout') {
return $next($request);
}
if ($request->user()->permission == User::BANNED) { if ($request->user()->permission == User::BANNED) {
if ($request->expectsJson()) { if ($request->expectsJson()) {
$response = json(trans('auth.check.banned'), -1); $response = json(trans('auth.check.banned'), -1);

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

@ -29,13 +29,28 @@ class UserMenuComposer
false false
); );
$avatarPNG = $this->filter->apply('user_avatar', $avatarPNG, [$user]); $avatarPNG = $this->filter->apply('user_avatar', $avatarPNG, [$user]);
$cli = $this->request->is('admin', 'admin/*');
$menuItems = [
['label' => trans('general.user-center'), 'link' => route('user.home')],
['label' => trans('general.profile'), 'link' => route('user.profile.view')],
];
if ($user->isAdmin()) {
array_push(
$menuItems,
['label' => '', 'link' => '#divider'],
['label' => trans('general.admin-panel'), 'link' => route('admin.view')],
['label' => trans('general.user-manage'), 'link' => route('admin.users.view')],
['label' => trans('general.report-manage'), 'link' => route('admin.reports.view')],
['label' => 'Web CLI', 'link' => '#launch-cli'],
);
}
$menuItems = $this->filter->apply('user_menu', $menuItems, [$user]);
$view->with([ $view->with([
'user' => $user, 'user' => $user,
'avatar' => $avatar, 'avatar' => $avatar,
'avatar_png' => $avatarPNG, 'avatar_png' => $avatarPNG,
'cli' => $cli, 'menu' => $menuItems,
]); ]);
} }
} }

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

@ -25,8 +25,6 @@ class SiteMessage extends Notification implements ShouldQueue
/** /**
* Get the notification's delivery channels. * Get the notification's delivery channels.
* *
* @param mixed $notifiable
*
* @return array * @return array
*/ */
public function via($notifiable) public function via($notifiable)
@ -37,8 +35,6 @@ class SiteMessage extends Notification implements ShouldQueue
/** /**
* Get the array representation of the notification. * Get the array representation of the notification.
* *
* @param mixed $notifiable
*
* @return array * @return array
*/ */
public function toArray($notifiable) public function toArray($notifiable)

View File

@ -9,25 +9,21 @@ class ScopeObserver
{ {
/** /**
* Handle the Scope "saved" event. * Handle the Scope "saved" event.
*
* @return void
*/ */
public function saved() public function saved(): void
{ {
$this->refreshCachedScopes(); $this->refreshCachedScopes();
} }
/** /**
* Handle the Scope "deleted" event. * Handle the Scope "deleted" event.
*
* @return void
*/ */
public function deleted() public function deleted(): void
{ {
$this->refreshCachedScopes(); $this->refreshCachedScopes();
} }
protected function refreshCachedScopes() protected function refreshCachedScopes(): void
{ {
Cache::forget('scopes'); Cache::forget('scopes');
Cache::rememberForever('scopes', function () { Cache::rememberForever('scopes', function () {

View File

@ -10,14 +10,14 @@ use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
public function register() public function register(): void
{ {
$this->app->singleton('cipher', 'App\Services\Cipher\\'.config('secure.cipher')); $this->app->singleton('cipher', 'App\Services\Cipher\\'.config('secure.cipher'));
$this->app->singleton(Services\Option::class); $this->app->singleton(Services\Option::class);
$this->app->alias(Services\Option::class, 'options'); $this->app->alias(Services\Option::class, 'options');
} }
public function boot(Request $request) public function boot(Request $request): void
{ {
Paginator::useBootstrap(); Paginator::useBootstrap();

View File

@ -2,31 +2,27 @@
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
{ {
/** /**
* The policy mappings for the application. * The model to policy mappings for the application.
* *
* @var array * @var array<class-string, class-string>
*/ */
protected $policies = [ protected $policies = [
]; ];
/** /**
* Register any authentication / authorization services. * Register any authentication / authorization services.
*
* @return void
*/ */
public function boot() public function boot(): void
{ {
$this->registerPolicies();
Passport::routes();
$defaultScopes = [ $defaultScopes = [
'User.Read' => 'auth.oauth.scope.user.read', 'User.Read' => 'auth.oauth.scope.user.read',
'Notification.Read' => 'auth.oauth.scope.notification.read', 'Notification.Read' => 'auth.oauth.scope.notification.read',
@ -45,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

@ -9,7 +9,11 @@ use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvi
class EventServiceProvider extends ServiceProvider class EventServiceProvider extends ServiceProvider
{ {
// The event listener mappings for the application. /**
* The event to listener mappings for the application.
*
* @var array<class-string, array<int, class-string>>
*/
protected $listen = [ protected $listen = [
'App\Events\PluginWasEnabled' => [ 'App\Events\PluginWasEnabled' => [
Listeners\CopyPluginAssets::class, Listeners\CopyPluginAssets::class,
@ -44,10 +48,8 @@ class EventServiceProvider extends ServiceProvider
/** /**
* Register any events for your application. * Register any events for your application.
*
* @return void
*/ */
public function boot() public function boot(): void
{ {
Scope::observe(ScopeObserver::class); Scope::observe(ScopeObserver::class);
} }

View File

@ -7,13 +7,13 @@ use Illuminate\Support\ServiceProvider;
class PluginServiceProvider extends ServiceProvider class PluginServiceProvider extends ServiceProvider
{ {
public function register() public function register(): void
{ {
$this->app->singleton(PluginManager::class); $this->app->singleton(PluginManager::class);
$this->app->alias(PluginManager::class, 'plugins'); $this->app->alias(PluginManager::class, 'plugins');
} }
public function boot(PluginManager $plugins) public function boot(PluginManager $plugins): void
{ {
$plugins->boot(); $plugins->boot();
} }

View File

@ -5,9 +5,8 @@ namespace App\Providers;
use App\Events\ConfigureRoutes; use App\Events\ConfigureRoutes;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Routing\Router; use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Laravel\Passport\Passport;
use Route;
class RouteServiceProvider extends ServiceProvider class RouteServiceProvider extends ServiceProvider
{ {
@ -20,7 +19,7 @@ class RouteServiceProvider extends ServiceProvider
/** /**
* Define the routes for the application. * Define the routes for the application.
*/ */
public function map(Router $router) public function map(Router $router): void
{ {
$this->mapStaticRoutes($router); $this->mapStaticRoutes($router);
@ -28,10 +27,9 @@ class RouteServiceProvider extends ServiceProvider
$this->mapApiRoutes(); $this->mapApiRoutes();
Passport::routes();
foreach ($router->getRoutes()->getRoutesByName() as $name => $route) { foreach ($router->getRoutes()->getRoutesByName() as $name => $route) {
if (Str::startsWith($name, ['passport.authorizations', 'passport.tokens', 'passport.clients'])) { if (Str::startsWith($name, ['passport.authorizations', 'passport.tokens', 'passport.clients'])) {
$route->middleware('verified'); $route->middleware(['auth', 'verified']);
} }
} }
@ -42,7 +40,7 @@ class RouteServiceProvider extends ServiceProvider
* Define the "web" routes for the application. * Define the "web" routes for the application.
* These routes all receive session state, CSRF protection, etc. * These routes all receive session state, CSRF protection, etc.
*/ */
protected function mapWebRoutes(Router $router) protected function mapWebRoutes(Router $router): void
{ {
Route::middleware(['web']) Route::middleware(['web'])
->namespace($this->namespace) ->namespace($this->namespace)
@ -53,7 +51,7 @@ class RouteServiceProvider extends ServiceProvider
* Define the "static" routes for the application. * Define the "static" routes for the application.
* These routes will not load session, etc. * These routes will not load session, etc.
*/ */
protected function mapStaticRoutes(Router $router) protected function mapStaticRoutes(Router $router): void
{ {
Route::namespace($this->namespace) Route::namespace($this->namespace)
->group(base_path('routes/static.php')); ->group(base_path('routes/static.php'));
@ -63,7 +61,7 @@ class RouteServiceProvider extends ServiceProvider
* Define the "api" routes for the application. * Define the "api" routes for the application.
* These routes are typically stateless. * These routes are typically stateless.
*/ */
protected function mapApiRoutes() protected function mapApiRoutes(): void
{ {
Route::prefix('api') Route::prefix('api')
->middleware( ->middleware(

View File

@ -3,12 +3,12 @@
namespace App\Providers; namespace App\Providers;
use App\Http\View\Composers; use App\Http\View\Composers;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use View;
class ViewServiceProvider extends ServiceProvider class ViewServiceProvider extends ServiceProvider
{ {
public function boot() public function boot(): void
{ {
View::composer([ View::composer([
'home', 'home',

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;
/** /**
@ -38,7 +38,7 @@ class OptionForm
protected $hookBefore; protected $hookBefore;
protected $hookAfter; protected $hookAfter;
protected $alwaysCallback = null; protected $alwaysCallback;
protected $renderWithoutTable = false; protected $renderWithoutTable = false;
protected $renderInputTagsOnly = false; protected $renderInputTagsOnly = false;
@ -59,7 +59,7 @@ class OptionForm
} }
/** /**
* @throws \BadMethodCallException * @throws BadMethodCallException
*/ */
public function __call(string $method, array $params): OptionFormItem public function __call(string $method, array $params): OptionFormItem
{ {
@ -105,7 +105,6 @@ class OptionForm
* Add a piece of data to the option form. * Add a piece of data to the option form.
* *
* @param string|array $key * @param string|array $key
* @param mixed $value
*/ */
public function with($key, $value = null): self public function with($key, $value = null): self
{ {
@ -204,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();
@ -352,7 +351,7 @@ class OptionFormItem
public $format; public $format;
public $value = null; public $value;
public $disabled; public $disabled;

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

@ -6,14 +6,15 @@ use Spatie\TranslationLoader\TranslationLoaderManager;
class Loader extends TranslationLoaderManager class Loader extends TranslationLoaderManager
{ {
protected function loadPath($path, $locale, $group) protected function loadPaths(array $paths, $locale, $group)
{ {
$translations = parent::loadPath($path, $locale, $group); return collect($paths)
->reduce(function ($output, $path) use ($locale, $group) {
if ($this->files->exists($full = "{$path}/{$locale}/{$group}.yml")) {
$output = resolve(Yaml::class)->parse($full);
}
$full = "{$path}/{$locale}/{$group}.yml"; return $output;
}, []);
return count($translations) === 0 && $this->files->exists($full)
? resolve(Yaml::class)->parse($full)
: [];
} }
} }

View File

@ -52,21 +52,11 @@ if (!function_exists('option')) {
* Get / set the specified option value. * Get / set the specified option value.
* *
* If an array is passed as the key, we will assume you want to set an array of values. * If an array is passed as the key, we will assume you want to set an array of values.
*
* @param array|string $key
* @param mixed $default
* @param bool $raw return raw value without convertion
*
* @return mixed
*/ */
function option($key = null, $default = null, $raw = false) function option(string|array $key, mixed $default = null, bool $raw = false)
{ {
$options = app('options'); $options = app('options');
if (is_null($key)) {
return $options;
}
if (is_array($key)) { if (is_array($key)) {
$options->set($key); $options->set($key);

View File

@ -29,27 +29,7 @@ ini_set('display_errors', true);
file_put_contents($envPath, preg_replace('/APP_KEY\s*=\s*/', 'APP_KEY='.$key."\n\n", $envFile)); file_put_contents($envPath, preg_replace('/APP_KEY\s*=\s*/', 'APP_KEY='.$key."\n\n", $envFile));
} }
$requiredFunctions = ['symlink', 'readlink', 'putenv', 'realpath']; $requiredVersion = '8.1.0';
$disabledFunctions = preg_split('/,\s*/', ini_get('disable_functions'));
foreach ($requiredFunctions as $fn) {
if (in_array($fn, $disabledFunctions)) {
die_with_utf8_encoding(
'[Error] Please don\'t disable built-in function "'.$fn.'", which is specified in "php.ini" file.<br>'.
"[错误] 请不要在 php.ini 中禁用 $fn 函数。".
'<strong>我们不建议使用您使用宝塔等面板软件,因为容易引起兼容性问题。</strong>'
);
}
}
if (!empty(ini_get('open_basedir'))) {
die_with_utf8_encoding(
'[Error] Please disable "open_basedir" option by editing "php.ini" file.<br>'.
'[错误] 请修改 php.ini 以关闭 "open_basedir" 选项。'.
'<strong>我们不建议使用您使用宝塔等面板软件,因为容易引起兼容性问题。</strong>'
);
}
$requiredVersion = '8.0.2';
preg_match('/(\d+\.\d+\.\d+)/', PHP_VERSION, $matches); preg_match('/(\d+\.\d+\.\d+)/', PHP_VERSION, $matches);
$version = $matches[1]; $version = $matches[1];
if (version_compare($version, $requiredVersion, '<')) { if (version_compare($version, $requiredVersion, '<')) {
@ -71,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

@ -3,9 +3,10 @@
"description": "A web application brings your custom skins back in offline Minecraft servers.", "description": "A web application brings your custom skins back in offline Minecraft servers.",
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": ">=8.0.2", "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": "*",
@ -18,35 +19,35 @@
"blessing/texture-renderer": "^0.2", "blessing/texture-renderer": "^0.2",
"composer/ca-bundle": "^1.2", "composer/ca-bundle": "^1.2",
"composer/semver": "^3.2", "composer/semver": "^3.2",
"doctrine/dbal": "^2.10", "doctrine/dbal": "^3.0",
"doctrine/inflector": "^1.3", "doctrine/inflector": "^2.0",
"facade/ignition": "^2.0", "spatie/laravel-ignition": "^2.0",
"gregwar/captcha": "1.*", "gregwar/captcha": "1.*",
"guzzlehttp/guzzle": "^7.0", "guzzlehttp/guzzle": "^7.0",
"intervention/image": "^2.7", "intervention/image": "^2.7",
"laravel/framework": "^8.0", "laravel/framework": "^10.0",
"laravel/passport": "^10.0", "laravel/passport": "^11.0",
"lorisleiva/laravel-search-string": "^1.0", "lorisleiva/laravel-search-string": "^1.0",
"nesbot/carbon": "^2.0", "nesbot/carbon": "^2.0",
"nunomaduro/collision": "^5.0", "nunomaduro/collision": "^7.0",
"rcrowe/twigbridge": "^0.12", "rcrowe/twigbridge": "^0.14",
"spatie/laravel-translation-loader": "^2.6", "spatie/laravel-translation-loader": "^2.7",
"symfony/process": "^5.0", "symfony/process": "^6.0",
"symfony/yaml": "^5.0", "symfony/yaml": "^5.0",
"twig/twig": "^2.11", "twig/twig": "^3.0",
"vectorface/whip": "^0.3.2" "vectorface/whip": "^0.4.0"
}, },
"require-dev": { "require-dev": {
"barryvdh/laravel-debugbar": "^3.5", "barryvdh/laravel-debugbar": "^3.5",
"barryvdh/laravel-ide-helper": "^2.10", "barryvdh/laravel-ide-helper": "^2.10",
"fakerphp/faker": "^1.13", "fakerphp/faker": "^1.13",
"friendsofphp/php-cs-fixer": "^3.0", "friendsofphp/php-cs-fixer": "^3.13",
"laravel/browser-kit-testing": "^6.1", "laravel/browser-kit-testing": "^7.0",
"laravel/tinker": "^2.4", "laravel/tinker": "^2.4",
"mockery/mockery": "^1.4", "mockery/mockery": "^1.4",
"phpunit/phpunit": "^9.4", "phpunit/phpunit": "^10.0",
"symfony/css-selector": "^5.0", "symfony/css-selector": "^6.2",
"symfony/dom-crawler": "^5.0" "symfony/dom-crawler": "^6.2"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@ -70,6 +71,7 @@
] ]
}, },
"config": { "config": {
"optimize-autoloader": true,
"preferred-install": "dist", "preferred-install": "dist",
"sort-packages": true "sort-packages": true
}, },

24283
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,8 @@
<?php <?php
use Illuminate\Support\Facades\Facade;
use Illuminate\Support\ServiceProvider;
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -9,6 +12,7 @@ return [
| Version of Blessing Skin Server. | Version of Blessing Skin Server.
| |
*/ */
'version' => '6.0.2', 'version' => '6.0.2',
/* /*
@ -19,12 +23,24 @@ return [
| Where to get information of new versions. | Where to get information of new versions.
| |
*/ */
'update_source' => env( 'update_source' => env(
'UPDATE_SOURCE', 'UPDATE_SOURCE',
'https://dev.azure.com/blessing-skin/51010f6d-9f99-40f1-a262-0a67f788df32/_apis/git/'. 'https://dev.azure.com/blessing-skin/51010f6d-9f99-40f1-a262-0a67f788df32/_apis/git/'.
'repositories/a9ff8df7-6dc3-4ff8-bb22-4871d3a43936/Items?path=%2Fupdate.json' 'repositories/a9ff8df7-6dc3-4ff8-bb22-4871d3a43936/Items?path=%2Fupdate.json'
), ),
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application. This value is used when the
| framework needs to place the application's name in a notification or
| any other location as required by the application or its packages.
|
*/
'name' => env('APP_NAME', 'blessing_skin'), 'name' => env('APP_NAME', 'blessing_skin'),
/* /*
@ -133,6 +149,24 @@ return [
'cipher' => 'AES-256-CBC', 'cipher' => 'AES-256-CBC',
/*
|--------------------------------------------------------------------------
| Maintenance Mode Driver
|--------------------------------------------------------------------------
|
| These configuration options determine the driver used to determine and
| manage Laravel's "maintenance mode" status. The "cache" driver will
| allow maintenance mode to be controlled across multiple machines.
|
| Supported drivers: "file", "cache"
|
*/
'maintenance' => [
'driver' => 'file',
// 'store' => 'redis',
],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Autoloaded Service Providers | Autoloaded Service Providers
@ -144,29 +178,10 @@ return [
| |
*/ */
'providers' => [ 'providers' => ServiceProvider::defaultProviders()->merge([
/* /*
* Laravel Framework Service Providers... * Package Service Providers...
*/ */
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
/* /*
* Application Service Providers... * Application Service Providers...
@ -177,7 +192,7 @@ return [
App\Providers\PluginServiceProvider::class, App\Providers\PluginServiceProvider::class,
App\Providers\RouteServiceProvider::class, App\Providers\RouteServiceProvider::class,
App\Providers\ViewServiceProvider::class, App\Providers\ViewServiceProvider::class,
], ])->toArray(),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -190,44 +205,7 @@ return [
| |
*/ */
'aliases' => [ 'aliases' => Facade::defaultAliases()->merge([
'Option' => App\Services\Facades\Option::class,
'App' => Illuminate\Support\Facades\App::class, ])->toArray(),
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Http' => Illuminate\Support\Facades\Http::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class,
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
/*
* Blessing Skin
*/
'Option' => App\Services\Facades\Option::class,
],
]; ];

View File

@ -1,7 +1,6 @@
<?php <?php
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Authentication Defaults | Authentication Defaults
@ -103,5 +102,4 @@ return [
'expire' => 60, 'expire' => 60,
], ],
], ],
]; ];

View File

@ -1,7 +1,6 @@
<?php <?php
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Default Broadcaster | Default Broadcaster
@ -11,7 +10,7 @@ return [
| framework when an event needs to be broadcast. You may set this to | framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below. | any of the connections defined in the "connections" array below.
| |
| Supported: "pusher", "redis", "log", "null" | Supported: "pusher", "ably", "redis", "log", "null"
| |
*/ */
@ -29,7 +28,6 @@ return [
*/ */
'connections' => [ 'connections' => [
'pusher' => [ 'pusher' => [
'driver' => 'pusher', 'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'), 'key' => env('PUSHER_APP_KEY'),
@ -37,8 +35,20 @@ return [
'app_id' => env('PUSHER_APP_ID'), 'app_id' => env('PUSHER_APP_ID'),
'options' => [ 'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'), 'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => true, 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
'port' => env('PUSHER_PORT', 443),
'scheme' => env('PUSHER_SCHEME', 'https'),
'encrypted' => true,
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
], ],
'client_options' => [
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
],
],
'ably' => [
'driver' => 'ably',
'key' => env('ABLY_KEY'),
], ],
'redis' => [ 'redis' => [
@ -53,7 +63,5 @@ return [
'null' => [ 'null' => [
'driver' => 'null', 'driver' => 'null',
], ],
], ],
]; ];

View File

@ -3,7 +3,6 @@
use Illuminate\Support\Str; use Illuminate\Support\Str;
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Default Cache Store | Default Cache Store
@ -13,9 +12,6 @@ return [
| using this caching library. This connection is used when another is | using this caching library. This connection is used when another is
| not explicitly specified when executing a given caching function. | not explicitly specified when executing a given caching function.
| |
| Supported: "apc", "array", "database", "file",
| "memcached", "redis", "dynamodb"
|
*/ */
'default' => env('CACHE_DRIVER', 'file'), 'default' => env('CACHE_DRIVER', 'file'),
@ -29,10 +25,12 @@ return [
| well as their drivers. You may even define multiple stores for the | well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches. | same cache driver to group types of items stored in your caches.
| |
| Supported drivers: "apc", "array", "database", "file",
| "memcached", "redis", "dynamodb", "octane", "null"
|
*/ */
'stores' => [ 'stores' => [
'apc' => [ 'apc' => [
'driver' => 'apc', 'driver' => 'apc',
], ],
@ -46,11 +44,13 @@ return [
'driver' => 'database', 'driver' => 'database',
'table' => 'cache', 'table' => 'cache',
'connection' => null, 'connection' => null,
'lock_connection' => null,
], ],
'file' => [ 'file' => [
'driver' => 'file', 'driver' => 'file',
'path' => storage_path('framework/cache/data'), 'path' => storage_path('framework/cache/data'),
'lock_path' => storage_path('framework/cache/data'),
], ],
'memcached' => [ 'memcached' => [
@ -75,6 +75,7 @@ return [
'redis' => [ 'redis' => [
'driver' => 'redis', 'driver' => 'redis',
'connection' => 'cache', 'connection' => 'cache',
'lock_connection' => 'default',
], ],
'dynamodb' => [ 'dynamodb' => [
@ -86,6 +87,9 @@ return [
'endpoint' => env('DYNAMODB_ENDPOINT'), 'endpoint' => env('DYNAMODB_ENDPOINT'),
], ],
'octane' => [
'driver' => 'octane',
],
], ],
/* /*
@ -93,12 +97,11 @@ return [
| Cache Key Prefix | Cache Key Prefix
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| When utilizing a RAM based store such as APC or Memcached, there might | When utilizing the APC, database, memcached, Redis, or DynamoDB cache
| be other applications utilizing the same cache. So, we'll specify a | stores there might be other applications using the same cache. For
| value to get prefixed to all our keys so we can avoid collisions. | that reason, you may prefix every cache key to avoid collisions.
| |
*/ */
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'blessing_skin'), '_').'_cache'), 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'blessing_skin'), '_').'_cache_'),
]; ];

View File

@ -3,7 +3,6 @@
use Illuminate\Support\Str; use Illuminate\Support\Str;
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Default Database Connection Name | Default Database Connection Name
@ -34,7 +33,6 @@ return [
*/ */
'connections' => [ 'connections' => [
'sqlite' => [ 'sqlite' => [
'driver' => 'sqlite', 'driver' => 'sqlite',
'url' => env('DATABASE_URL'), 'url' => env('DATABASE_URL'),
@ -56,7 +54,7 @@ return [
'collation' => 'utf8mb4_unicode_ci', 'collation' => 'utf8mb4_unicode_ci',
'prefix' => env('DB_PREFIX', ''), 'prefix' => env('DB_PREFIX', ''),
'prefix_indexes' => true, 'prefix_indexes' => true,
'strict' => false, 'strict' => true,
'engine' => null, 'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([ 'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
@ -74,7 +72,7 @@ return [
'charset' => 'utf8', 'charset' => 'utf8',
'prefix' => env('DB_PREFIX', ''), 'prefix' => env('DB_PREFIX', ''),
'prefix_indexes' => true, 'prefix_indexes' => true,
'schema' => 'public', 'search_path' => 'public',
'sslmode' => 'prefer', 'sslmode' => 'prefer',
], ],
@ -89,8 +87,9 @@ return [
'charset' => 'utf8', 'charset' => 'utf8',
'prefix' => env('DB_PREFIX', ''), 'prefix' => env('DB_PREFIX', ''),
'prefix_indexes' => true, 'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
], ],
], ],
/* /*
@ -118,7 +117,6 @@ return [
*/ */
'redis' => [ 'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'), 'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [ 'options' => [
@ -129,7 +127,8 @@ return [
'default' => [ 'default' => [
'url' => env('REDIS_URL'), 'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'), 'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null), 'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'), 'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'), 'database' => env('REDIS_DB', '0'),
], ],
@ -137,11 +136,10 @@ return [
'cache' => [ 'cache' => [
'url' => env('REDIS_URL'), 'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'), 'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null), 'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'), 'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'), 'database' => env('REDIS_CACHE_DB', '1'),
], ],
], ],
]; ];

View File

@ -1,7 +1,6 @@
<?php <?php
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Debugbar Settings | Debugbar Settings
@ -16,7 +15,8 @@ return [
'enabled' => env('DEBUGBAR_ENABLED', null), 'enabled' => env('DEBUGBAR_ENABLED', null),
'except' => [ 'except' => [
// 'telescope*',
'horizon*',
], ],
/* /*
@ -32,13 +32,57 @@ return [
| |
*/ */
'storage' => [ 'storage' => [
'enabled' => true, 'enabled' => true,
'driver' => 'file', // redis, file, pdo, custom 'driver' => 'file', // redis, file, pdo, socket, custom
'path' => storage_path('debugbar'), // For file driver 'path' => storage_path('debugbar'), // For file driver
'connection' => null, // Leave null for default connection (Redis/PDO) 'connection' => null, // Leave null for default connection (Redis/PDO)
'provider' => '', // Instance of StorageInterface for custom driver 'provider' => '', // Instance of StorageInterface for custom driver
'hostname' => '127.0.0.1', // Hostname to use with the "socket" driver
'port' => 2304, // Port to use with the "socket" driver
], ],
/*
|--------------------------------------------------------------------------
| Editor
|--------------------------------------------------------------------------
|
| Choose your preferred editor to use when clicking file name.
|
| Supported: "phpstorm", "vscode", "vscode-insiders", "vscode-remote",
| "vscode-insiders-remote", "vscodium", "textmate", "emacs",
| "sublime", "atom", "nova", "macvim", "idea", "netbeans",
| "xdebug", "espresso"
|
*/
'editor' => env('DEBUGBAR_EDITOR', 'phpstorm'),
/*
|--------------------------------------------------------------------------
| Remote Path Mapping
|--------------------------------------------------------------------------
|
| If you are using a remote dev server, like Laravel Homestead, Docker, or
| even a remote VPS, it will be necessary to specify your path mapping.
|
| Leaving one, or both of these, empty or null will not trigger the remote
| URL changes and Debugbar will treat your editor links as local files.
|
| "remote_sites_path" is an absolute base path for your sites or projects
| in Homestead, Vagrant, Docker, or another remote development server.
|
| Example value: "/home/vagrant/Code"
|
| "local_sites_path" is an absolute base path for your sites or projects
| on your local computer where your IDE or code editor is running on.
|
| Example values: "/Users/<name>/Code", "C:\Users\<name>\Documents\Code"
|
*/
'remote_sites_path' => env('DEBUGBAR_REMOTE_SITES_PATH', ''),
'local_sites_path' => env('DEBUGBAR_LOCAL_SITES_PATH', ''),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Vendors | Vendors
@ -47,7 +91,7 @@ return [
| Vendor files are included by default, but can be set to false. | Vendor files are included by default, but can be set to false.
| This can also be set to 'js' or 'css', to only include javascript or css vendor files. | This can also be set to 'js' or 'css', to only include javascript or css vendor files.
| Vendor files are for css: font-awesome (including fonts) and highlight.js (css files) | Vendor files are for css: font-awesome (including fonts) and highlight.js (css files)
| and for js: jquery and and highlight.js | and for js: jquery and highlight.js
| So if you want syntax highlighting, set it to true. | So if you want syntax highlighting, set it to true.
| jQuery is set to not conflict with existing jQuery scripts. | jQuery is set to not conflict with existing jQuery scripts.
| |
@ -64,6 +108,9 @@ return [
| you can use this option to disable sending the data through the headers. | you can use this option to disable sending the data through the headers.
| |
| Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools. | Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools.
|
| Note for your request to be identified as ajax requests they must either send the header
| X-Requested-With with the value XMLHttpRequest (most JS libraries send this), or have application/json as a Accept header.
*/ */
'capture_ajax' => true, 'capture_ajax' => true,
@ -101,27 +148,29 @@ return [
*/ */
'collectors' => [ 'collectors' => [
'phpinfo' => true, // Php version 'phpinfo' => true, // Php version
'messages' => true, // Messages 'messages' => true, // Messages
'time' => true, // Time Datalogger 'time' => true, // Time Datalogger
'memory' => true, // Memory usage 'memory' => true, // Memory usage
'exceptions' => true, // Exception displayer 'exceptions' => true, // Exception displayer
'log' => true, // Logs from Monolog (merged in messages if enabled) 'log' => true, // Logs from Monolog (merged in messages if enabled)
'db' => true, // Show database (PDO) queries and bindings 'db' => true, // Show database (PDO) queries and bindings
'views' => true, // Views with their data 'views' => true, // Views with their data
'route' => true, // Current route information 'route' => true, // Current route information
'auth' => true, // Display Laravel authentication status 'auth' => false, // Display Laravel authentication status
'gate' => false, // Display Laravel Gate checks 'gate' => true, // Display Laravel Gate checks
'session' => true, // Display session data 'session' => true, // Display session data
'symfony_request' => true, // Only one can be enabled.. 'symfony_request' => true, // Only one can be enabled..
'mail' => false, // Catch mail messages 'mail' => true, // Catch mail messages
'laravel' => false, // Laravel version and environment 'laravel' => false, // Laravel version and environment
'events' => true, // All events fired 'events' => false, // All events fired
'default_request' => false, // Regular or special Symfony request logger 'default_request' => false, // Regular or special Symfony request logger
'logs' => false, // Add the latest log messages 'logs' => false, // Add the latest log messages
'files' => false, // Show the included files 'files' => false, // Show the included files
'config' => false, // Display config settings 'config' => false, // Display config settings
'cache' => false, // Display cache events 'cache' => false, // Display cache events
'models' => true, // Display models
'livewire' => true, // Display Livewire (when available)
], ],
/* /*
@ -138,20 +187,26 @@ return [
'show_name' => true, // Also show the users name/email in the debugbar 'show_name' => true, // Also show the users name/email in the debugbar
], ],
'db' => [ 'db' => [
'with_params' => true, // Render SQL with the parameters substituted 'with_params' => true, // Render SQL with the parameters substituted
'backtrace' => true, // Use a backtrace to find the origin of the query in your files. 'backtrace' => true, // Use a backtrace to find the origin of the query in your files.
'timeline' => false, // Add the queries to the timeline 'backtrace_exclude_paths' => [], // Paths to exclude from backtrace. (in addition to defaults)
'timeline' => false, // Add the queries to the timeline
'duration_background' => true, // Show shaded background on each query relative to how long it took to execute.
'explain' => [ // Show EXPLAIN output on queries 'explain' => [ // Show EXPLAIN output on queries
'enabled' => false, 'enabled' => false,
'types' => ['SELECT'], // ['SELECT', 'INSERT', 'UPDATE', 'DELETE']; for MySQL 5.6.3+ 'types' => ['SELECT'], // Deprecated setting, is always only SELECT
], ],
'hints' => true, // Show hints for common mistakes 'hints' => false, // Show hints for common mistakes
'show_copy' => false, // Show copy button next to the query,
'slow_threshold' => false, // Only track queries that last longer than this time in ms
], ],
'mail' => [ 'mail' => [
'full_log' => false, 'full_log' => false,
], ],
'views' => [ 'views' => [
'data' => false, //Note: Can slow down the application, because the data can be quite large.. 'timeline' => false, // Add the views to the timeline (Experimental)
'data' => false, // Note: Can slow down the application, because the data can be quite large..
'exclude_paths' => [], // Add the paths which you don't want to appear in the views
], ],
'route' => [ 'route' => [
'label' => true, // show complete route on bar 'label' => true, // show complete route on bar
@ -198,4 +253,24 @@ return [
| To override default domain, specify it as a non-empty value. | To override default domain, specify it as a non-empty value.
*/ */
'route_domain' => null, 'route_domain' => null,
/*
|--------------------------------------------------------------------------
| DebugBar theme
|--------------------------------------------------------------------------
|
| Switches between light and dark theme. If set to auto it will respect system preferences
| Possible values: auto, light, dark
*/
'theme' => env('DEBUGBAR_THEME', 'auto'),
/*
|--------------------------------------------------------------------------
| Backtrace stack limit
|--------------------------------------------------------------------------
|
| By default, the DebugBar limits the number of frames returned by the 'debug_backtrace()' function.
| If you need larger stacktraces, you can increase this number. Setting it to 0 will result in no limit.
*/
'debug_backtrace_limit' => 50,
]; ];

View File

@ -1,7 +1,6 @@
<?php <?php
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Default Filesystem Disk | Default Filesystem Disk
@ -42,7 +41,6 @@ return [
*/ */
'disks' => [ 'disks' => [
'local' => [ 'local' => [
'driver' => 'local', 'driver' => 'local',
'root' => storage_path('app'), 'root' => storage_path('app'),
@ -61,7 +59,5 @@ return [
'testing' => [ 'testing' => [
'driver' => 'memory', 'driver' => 'memory',
], ],
], ],
]; ];

View File

@ -1,7 +1,6 @@
<?php <?php
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Default Hash Driver | Default Hash Driver
@ -44,9 +43,8 @@ return [
*/ */
'argon' => [ 'argon' => [
'memory' => 1024, 'memory' => 65536,
'threads' => 2, 'threads' => 1,
'time' => 2, 'time' => 4,
], ],
]; ];

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

@ -10,7 +10,6 @@
*/ */
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| JWT Authentication Secret | JWT Authentication Secret
@ -45,7 +44,6 @@ return [
*/ */
'keys' => [ 'keys' => [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Public Key | Public Key
@ -82,7 +80,6 @@ return [
*/ */
'passphrase' => env('JWT_PASSPHRASE'), 'passphrase' => env('JWT_PASSPHRASE'),
], ],
/* /*
@ -265,7 +262,6 @@ return [
*/ */
'providers' => [ 'providers' => [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| JWT Provider | JWT Provider
@ -298,7 +294,5 @@ return [
*/ */
'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class, 'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class,
], ],
]; ];

View File

@ -29,7 +29,7 @@ return [
], ],
'es_ES' => [ 'es_ES' => [
'name' => 'Español', 'name' => 'Español',
'short_name' => 'es' 'short_name' => 'es',
], ],
'ru' => [ 'ru' => [
'alias' => 'ru_RU', 'alias' => 'ru_RU',

View File

@ -3,9 +3,9 @@
use Monolog\Handler\NullHandler; use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler; use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler; use Monolog\Handler\SyslogUdpHandler;
use Monolog\Processor\PsrLogMessageProcessor;
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Default Log Channel | Default Log Channel
@ -19,6 +19,22 @@ return [
'default' => env('LOG_CHANNEL', 'daily'), 'default' => env('LOG_CHANNEL', 'daily'),
/*
|--------------------------------------------------------------------------
| Deprecations Log Channel
|--------------------------------------------------------------------------
|
| This option controls the log channel that should be used to log warnings
| regarding deprecated PHP and library features. This allows you to get
| your application ready for upcoming major versions of dependencies.
|
*/
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => false,
],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Log Channels | Log Channels
@ -44,14 +60,16 @@ return [
'single' => [ 'single' => [
'driver' => 'single', 'driver' => 'single',
'path' => storage_path('logs/laravel.log'), 'path' => storage_path('logs/laravel.log'),
'level' => 'debug', 'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
], ],
'daily' => [ 'daily' => [
'driver' => 'daily', 'driver' => 'daily',
'path' => storage_path('logs/laravel.log'), 'path' => storage_path('logs/laravel.log'),
'level' => 'debug', 'level' => env('LOG_LEVEL', 'debug'),
'days' => 14, 'days' => 14,
'replace_placeholders' => true,
], ],
'slack' => [ 'slack' => [
@ -59,36 +77,44 @@ return [
'url' => env('LOG_SLACK_WEBHOOK_URL'), 'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log', 'username' => 'Laravel Log',
'emoji' => ':boom:', 'emoji' => ':boom:',
'level' => 'critical', 'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
], ],
'papertrail' => [ 'papertrail' => [
'driver' => 'monolog', 'driver' => 'monolog',
'level' => 'debug', 'level' => env('LOG_LEVEL', 'debug'),
'handler' => SyslogUdpHandler::class, 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
'handler_with' => [ 'handler_with' => [
'host' => env('PAPERTRAIL_URL'), 'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'), 'port' => env('PAPERTRAIL_PORT'),
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
], ],
'processors' => [PsrLogMessageProcessor::class],
], ],
'stderr' => [ 'stderr' => [
'driver' => 'monolog', 'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class, 'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'), 'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [ 'with' => [
'stream' => 'php://stderr', 'stream' => 'php://stderr',
], ],
'processors' => [PsrLogMessageProcessor::class],
], ],
'syslog' => [ 'syslog' => [
'driver' => 'syslog', 'driver' => 'syslog',
'level' => 'debug', 'level' => env('LOG_LEVEL', 'debug'),
'facility' => LOG_USER,
'replace_placeholders' => true,
], ],
'errorlog' => [ 'errorlog' => [
'driver' => 'errorlog', 'driver' => 'errorlog',
'level' => 'debug', 'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
], ],
'null' => [ 'null' => [
@ -100,5 +126,4 @@ return [
'path' => storage_path('logs/laravel.log'), 'path' => storage_path('logs/laravel.log'),
], ],
], ],
]; ];

View File

@ -1,7 +1,6 @@
<?php <?php
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Default Mailer | Default Mailer
@ -28,19 +27,22 @@ return [
| sending an e-mail. You will specify which one you are using for your | sending an e-mail. You will specify which one you are using for your
| mailers below. You are free to add additional mailers as required. | mailers below. You are free to add additional mailers as required.
| |
| Supported: "smtp", "sendmail", "mailgun", "ses", | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
| "postmark", "log", "array" | "postmark", "log", "array", "failover"
| |
*/ */
'mailers' => [ 'mailers' => [
'smtp' => [ 'smtp' => [
'transport' => 'smtp', 'transport' => 'smtp',
'url' => env('MAIL_URL'),
'host' => env('MAIL_HOST', 'smtp.mailgun.org'), 'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
'port' => env('MAIL_PORT', 587), 'port' => env('MAIL_PORT', 587),
'encryption' => env('MAIL_ENCRYPTION', 'tls'), 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
'username' => env('MAIL_USERNAME'), 'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'), 'password' => env('MAIL_PASSWORD'),
'timeout' => null,
'local_domain' => env('MAIL_EHLO_DOMAIN'),
], ],
'ses' => [ 'ses' => [
@ -49,15 +51,21 @@ return [
'mailgun' => [ 'mailgun' => [
'transport' => 'mailgun', 'transport' => 'mailgun',
// 'client' => [
// 'timeout' => 5,
// ],
], ],
'postmark' => [ 'postmark' => [
'transport' => 'postmark', 'transport' => 'postmark',
// 'client' => [
// 'timeout' => 5,
// ],
], ],
'sendmail' => [ 'sendmail' => [
'transport' => 'sendmail', 'transport' => 'sendmail',
'path' => '/usr/sbin/sendmail -bs', 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
], ],
'log' => [ 'log' => [
@ -68,6 +76,14 @@ return [
'array' => [ 'array' => [
'transport' => 'array', 'transport' => 'array',
], ],
'failover' => [
'transport' => 'failover',
'mailers' => [
'smtp',
'log',
],
],
], ],
/* /*
@ -104,5 +120,4 @@ return [
resource_path('views/vendor/mail'), resource_path('views/vendor/mail'),
], ],
], ],
]; ];

View File

@ -36,7 +36,7 @@ $menu['admin'] = [
['title' => 'general.status', 'link' => 'admin/status', 'icon' => 'fa-battery-three-quarters'], ['title' => 'general.status', 'link' => 'admin/status', 'icon' => 'fa-battery-three-quarters'],
['title' => 'general.plugin-manage', 'link' => 'admin/plugins/manage', 'icon' => 'fa-plug'], ['title' => 'general.plugin-manage', 'link' => 'admin/plugins/manage', 'icon' => 'fa-plug'],
['title' => 'general.plugin-market', 'link' => 'admin/plugins/market', 'icon' => 'fa-shopping-bag'], ['title' => 'general.plugin-market', 'link' => 'admin/plugins/market', 'icon' => 'fa-shopping-bag'],
['title' => 'general.plugin-configs', 'id' => 'plugin-configs', 'icon' => 'fa-cogs', 'children' => []], ['title' => 'general.plugin-configs', 'id' => 'plugin-configs', 'icon' => 'fa-cogs', 'children' => []],
['title' => 'general.check-update', 'link' => 'admin/update', 'icon' => 'fa-arrow-up'], ['title' => 'general.check-update', 'link' => 'admin/update', 'icon' => 'fa-arrow-up'],
]; ];

View File

@ -1,56 +1,56 @@
<?php <?php
return [ return [
'site_url' => '', 'site_url' => '',
'site_name' => 'Blessing Skin', 'site_name' => 'Blessing Skin',
'site_description' => 'Open-source PHP Minecraft Skin Hosting Service', 'site_description' => 'Open-source PHP Minecraft Skin Hosting Service',
'register_with_player_name' => 'true', 'register_with_player_name' => 'true',
'require_verification' => 'false', 'require_verification' => 'false',
'regs_per_ip' => '3', 'regs_per_ip' => '3',
'announcement' => 'Welcome to Blessing Skin {version}!', 'announcement' => 'Welcome to Blessing Skin {version}!',
'home_pic_url' => './app/bg.webp', 'home_pic_url' => './app/bg.webp',
'custom_css' => '', 'custom_css' => '',
'custom_js' => '', 'custom_js' => '',
'player_name_rule' => 'official', 'player_name_rule' => 'official',
'custom_player_name_regexp' => '', 'custom_player_name_regexp' => '',
'player_name_length_min' => '3', 'player_name_length_min' => '3',
'player_name_length_max' => '16', 'player_name_length_max' => '16',
'user_initial_score' => '1000', 'user_initial_score' => '1000',
'sign_gap_time' => '24', 'sign_gap_time' => '24',
'sign_score' => '10,100', 'sign_score' => '10,100',
'score_per_storage' => 'true', 'score_per_storage' => 'true',
'private_score_per_storage' => '10', 'private_score_per_storage' => '10',
'return_score' => 'true', 'return_score' => 'true',
'score_per_player' => '100', 'score_per_player' => '100',
'sign_after_zero' => 'false', 'sign_after_zero' => 'false',
'version' => '', 'version' => '',
'copyright_text' => '<b>Copyright &copy; {year} <a href="{site_url}">{site_name}</a>.</b> All rights reserved.', 'copyright_text' => '<b>Copyright &copy; {year} <a href="{site_url}">{site_name}</a>.</b> All rights reserved.',
'auto_del_invalid_texture' => 'false', 'auto_del_invalid_texture' => 'false',
'allow_downloading_texture' => 'true', 'allow_downloading_texture' => 'true',
'texture_name_regexp' => '', 'texture_name_regexp' => '',
'cache_expire_time' => '31536000', 'cache_expire_time' => '31536000',
'max_upload_file_size' => '1024', 'max_upload_file_size' => '1024',
'force_ssl' => 'false', 'force_ssl' => 'false',
'auto_detect_asset_url' => 'true', 'auto_detect_asset_url' => 'true',
'plugins_enabled' => '', 'plugins_enabled' => '',
'copyright_prefer' => '0', 'copyright_prefer' => '0',
'score_per_closet_item' => '0', 'score_per_closet_item' => '0',
'favicon_url' => 'app/favicon.ico', 'favicon_url' => 'app/favicon.ico',
'score_award_per_texture' => '0', 'score_award_per_texture' => '0',
'take_back_scores_after_deletion' => 'true', 'take_back_scores_after_deletion' => 'true',
'score_award_per_like' => '0', 'score_award_per_like' => '0',
'meta_keywords' => '', 'meta_keywords' => '',
'meta_description' => '', 'meta_description' => '',
'meta_extras' => '', 'meta_extras' => '',
'cdn_address' => '', 'cdn_address' => '',
'recaptcha_sitekey' => '', 'recaptcha_sitekey' => '',
'recaptcha_secretkey' => '', 'recaptcha_secretkey' => '',
'recaptcha_invisible' => 'false', 'recaptcha_invisible' => 'false',
'reporter_score_modification' => '0', 'reporter_score_modification' => '0',
'reporter_reward_score' => '0', 'reporter_reward_score' => '0',
'content_policy' => '', 'content_policy' => '',
'transparent_navbar' => 'false', 'transparent_navbar' => 'false',
'status_code_for_private' => '403', 'status_code_for_private' => '403',
'navbar_color' => 'cyan', 'navbar_color' => 'cyan',
'sidebar_color' => 'dark-maroon', 'sidebar_color' => 'dark-maroon',
]; ];

62
config/passport.php Normal file
View File

@ -0,0 +1,62 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Passport Guard
|--------------------------------------------------------------------------
|
| Here you may specify which authentication guard Passport will use when
| authenticating users. This value should correspond with one of your
| guards that is already present in your "auth" configuration file.
|
*/
'guard' => 'web',
/*
|--------------------------------------------------------------------------
| Encryption Keys
|--------------------------------------------------------------------------
|
| Passport uses encryption keys while generating secure access tokens for
| your application. By default, the keys are stored as local files but
| can be set via environment variables when that is more convenient.
|
*/
'private_key' => env('PASSPORT_PRIVATE_KEY'),
'public_key' => env('PASSPORT_PUBLIC_KEY'),
/*
|--------------------------------------------------------------------------
| Client UUIDs
|--------------------------------------------------------------------------
|
| By default, Passport uses auto-incrementing primary keys when assigning
| IDs to clients. However, if Passport is installed using the provided
| --uuids switch, this will be set to "true" and UUIDs will be used.
|
*/
'client_uuids' => false,
/*
|--------------------------------------------------------------------------
| Personal Access Client
|--------------------------------------------------------------------------
|
| If you enable client hashing, you should set the personal access client
| ID and unhashed secret within your environment file. The values will
| get used while issuing fresh personal access tokens to your users.
|
*/
'personal_access_client' => [
'id' => env('PASSPORT_PERSONAL_ACCESS_CLIENT_ID'),
'secret' => env('PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET'),
],
];

View File

@ -33,7 +33,7 @@ return [
*/ */
'registry' => env( 'registry' => env(
'PLUGINS_REGISTRY', 'PLUGINS_REGISTRY',
'https://gplane.coding.net/p/blessing-skin-plugins-dist/d/blessing-skin-plugins-dist/git/raw/master/registry_{lang}.json' 'https://bs-plugins.littleservice.cn/registry_{lang}.json'
), ),
/* /*

View File

@ -1,7 +1,6 @@
<?php <?php
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Default Queue Connection Name | Default Queue Connection Name
@ -29,7 +28,6 @@ return [
*/ */
'connections' => [ 'connections' => [
'sync' => [ 'sync' => [
'driver' => 'sync', 'driver' => 'sync',
], ],
@ -39,6 +37,7 @@ return [
'table' => 'jobs', 'table' => 'jobs',
'queue' => 'default', 'queue' => 'default',
'retry_after' => 90, 'retry_after' => 90,
'after_commit' => false,
], ],
'beanstalkd' => [ 'beanstalkd' => [
@ -47,6 +46,7 @@ return [
'queue' => 'default', 'queue' => 'default',
'retry_after' => 90, 'retry_after' => 90,
'block_for' => 0, 'block_for' => 0,
'after_commit' => false,
], ],
'sqs' => [ 'sqs' => [
@ -54,9 +54,10 @@ return [
'key' => env('AWS_ACCESS_KEY_ID'), 'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'), 'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'your-queue-name'), 'queue' => env('SQS_QUEUE', 'default'),
'suffix' => env('SQS_SUFFIX'), 'suffix' => env('SQS_SUFFIX'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'after_commit' => false,
], ],
'redis' => [ 'redis' => [
@ -65,8 +66,24 @@ return [
'queue' => env('REDIS_QUEUE', 'default'), 'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90, 'retry_after' => 90,
'block_for' => null, 'block_for' => null,
'after_commit' => false,
], ],
],
/*
|--------------------------------------------------------------------------
| Job Batching
|--------------------------------------------------------------------------
|
| The following options configure the database and table that store job
| batching information. These options can be updated to any database
| connection and table which has been defined by your application.
|
*/
'batching' => [
'database' => env('DB_CONNECTION', 'mysql'),
'table' => 'job_batches',
], ],
/* /*
@ -81,9 +98,8 @@ return [
*/ */
'failed' => [ 'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database'), 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'mysql'), 'database' => env('DB_CONNECTION', 'mysql'),
'table' => 'failed_jobs', 'table' => 'failed_jobs',
], ],
]; ];

View File

@ -10,5 +10,5 @@ return [
| |
*/ */
'cipher' => env('PWD_METHOD', 'BCRYPT'), 'cipher' => env('PWD_METHOD', 'BCRYPT'),
'salt' => env('SALT', ''), 'salt' => env('SALT', ''),
]; ];

View File

@ -1,7 +1,6 @@
<?php <?php
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Third Party Services | Third Party Services
@ -18,6 +17,7 @@ return [
'domain' => env('MAILGUN_DOMAIN'), 'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'), 'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
'scheme' => 'https',
], ],
'postmark' => [ 'postmark' => [
@ -29,5 +29,4 @@ return [
'secret' => env('AWS_SECRET_ACCESS_KEY'), 'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
], ],
]; ];

View File

@ -1,7 +1,6 @@
<?php <?php
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Default Session Driver | Default Session Driver
@ -70,7 +69,7 @@ return [
| |
*/ */
'connection' => env('SESSION_CONNECTION', null), 'connection' => env('SESSION_CONNECTION'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -90,13 +89,15 @@ return [
| Session Cache Store | Session Cache Store
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| When using the "apc", "memcached", or "dynamodb" session drivers you may | While using one of the framework's cache driven session backends you may
| list a cache store that should be used for these sessions. This value | list a cache store that should be used for these sessions. This value
| must match with one of the application's configured cache "stores". | must match with one of the application's configured cache "stores".
| |
| Affects: "apc", "dynamodb", "memcached", "redis"
|
*/ */
'store' => env('SESSION_STORE', null), 'store' => env('SESSION_STORE'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -148,7 +149,7 @@ return [
| |
*/ */
'domain' => env('SESSION_DOMAIN', null), 'domain' => env('SESSION_DOMAIN'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -157,7 +158,7 @@ return [
| |
| By setting this option to true, session cookies will only be sent back | By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep | to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you if it can not be done securely. | the cookie from being sent to you when it can't be done securely.
| |
*/ */
@ -183,12 +184,11 @@ return [
| |
| This option determines how your cookies behave when cross-site requests | This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we | take place, and can be used to mitigate CSRF attacks. By default, we
| do not enable this as other CSRF protection services are in place. | will set this value to "lax" since this is a secure default value.
| |
| Supported: "lax", "strict", "none" | Supported: "lax", "strict", "none", null
| |
*/ */
'same_site' => 'lax', 'same_site' => 'lax',
]; ];

View File

@ -1,7 +1,6 @@
<?php <?php
return [ return [
/* /*
* Language lines will be fetched by these loaders. You can put any class here that implements * Language lines will be fetched by these loaders. You can put any class here that implements
* the Spatie\TranslationLoader\TranslationLoaders\TranslationLoader-interface. * the Spatie\TranslationLoader\TranslationLoaders\TranslationLoader-interface.
@ -20,5 +19,4 @@ return [
* This is the translation manager which overrides the default Laravel `translation.loader` * This is the translation manager which overrides the default Laravel `translation.loader`
*/ */
'translation_manager' => App\Services\Translations\Loader::class, 'translation_manager' => App\Services\Translations\Loader::class,
]; ];

View File

@ -9,11 +9,10 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
/** /*
* Configuration options for Twig. * Configuration options for Twig.
*/ */
return [ return [
'twig' => [ 'twig' => [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -34,7 +33,6 @@ return [
| |
*/ */
'environment' => [ 'environment' => [
// When set to true, the generated templates have a __toString() method // When set to true, the generated templates have a __toString() method
// that you can use to display the generated nodes. // that you can use to display the generated nodes.
// default: false // default: false
@ -44,10 +42,6 @@ return [
// default: utf-8 // default: utf-8
'charset' => 'utf-8', 'charset' => 'utf-8',
// The base template class to use for generated templates.
// default: TwigBridge\Twig\Template
'base_template_class' => 'TwigBridge\Twig\Template',
// An absolute path where to store the compiled templates, or false to disable caching. If null // An absolute path where to store the compiled templates, or false to disable caching. If null
// then the cache file path is used. // then the cache file path is used.
// default: cache file storage path // default: cache file storage path
@ -73,6 +67,19 @@ return [
'optimizations' => -1, 'optimizations' => -1,
], ],
/*
|--------------------------------------------------------------------------
| Safe Classes
|--------------------------------------------------------------------------
|
| When set, the output of the `__string` method of the following classes will not be escaped.
| default: Laravel's Htmlable, which the HtmlString class implements.
|
*/
'safe_classes' => [
\Illuminate\Contracts\Support\Htmlable::class => ['html'],
],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Global variables | Global variables
@ -86,7 +93,6 @@ return [
], ],
'extensions' => [ 'extensions' => [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Extensions | Extensions
@ -98,21 +104,21 @@ return [
| |
*/ */
'enabled' => [ 'enabled' => [
'Twig\Extension\StringLoaderExtension', 'TwigBridge\Extension\Laravel\Event',
'TwigBridge\Extension\Loader\Facades', 'TwigBridge\Extension\Loader\Facades',
'TwigBridge\Extension\Loader\Filters', 'TwigBridge\Extension\Loader\Filters',
'TwigBridge\Extension\Loader\Functions', 'TwigBridge\Extension\Loader\Functions',
'TwigBridge\Extension\Loader\Globals',
'TwigBridge\Extension\Laravel\Auth', 'TwigBridge\Extension\Laravel\Auth',
'TwigBridge\Extension\Laravel\Config', 'TwigBridge\Extension\Laravel\Config',
'TwigBridge\Extension\Laravel\Dump', 'TwigBridge\Extension\Laravel\Dump',
// 'TwigBridge\Extension\Laravel\Input', 'TwigBridge\Extension\Laravel\Input',
'TwigBridge\Extension\Laravel\Session', 'TwigBridge\Extension\Laravel\Session',
// 'TwigBridge\Extension\Laravel\Str', 'TwigBridge\Extension\Laravel\Str',
'TwigBridge\Extension\Laravel\Translator', 'TwigBridge\Extension\Laravel\Translator',
'TwigBridge\Extension\Laravel\Url', 'TwigBridge\Extension\Laravel\Url',
// 'TwigBridge\Extension\Laravel\Model', 'TwigBridge\Extension\Laravel\Model',
// 'TwigBridge\Extension\Laravel\Gate', // 'TwigBridge\Extension\Laravel\Gate',
// 'TwigBridge\Extension\Laravel\Form', // 'TwigBridge\Extension\Laravel\Form',
@ -177,7 +183,12 @@ return [
| </code> | </code>
| |
*/ */
'functions' => [], 'functions' => [
'elixir',
'head',
'last',
'mix',
],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -1,7 +1,6 @@
<?php <?php
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| View Storage Paths | View Storage Paths
@ -33,5 +32,4 @@ return [
'VIEW_COMPILED_PATH', 'VIEW_COMPILED_PATH',
realpath(storage_path('framework/views')) realpath(storage_path('framework/views'))
), ),
]; ];

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

@ -23,12 +23,13 @@
"dependencies": { "dependencies": {
"@emotion/react": "^11.0.0", "@emotion/react": "^11.0.0",
"@emotion/styled": "^11.0.0", "@emotion/styled": "^11.0.0",
"@fortawesome/fontawesome-free": "^6.3.0",
"@hot-loader/react-dom": "^17.0.0", "@hot-loader/react-dom": "^17.0.0",
"@tweenjs/tween.js": "^18.5.0", "@tweenjs/tween.js": "^18.5.0",
"admin-lte": "^3.2.0", "admin-lte": "^3.2.0",
"blessing-skin-shell": "^0.3.4", "blessing-skin-shell": "^0.3.4",
"bootstrap": "^4.6.1", "bootstrap": "^4.6.1",
"cac": "^6.6.1", "cac": "6.6.1",
"cli-spinners": "^2.5.0", "cli-spinners": "^2.5.0",
"clsx": "^1.1.1", "clsx": "^1.1.1",
"echarts": "^5.1.2", "echarts": "^5.1.2",
@ -44,15 +45,13 @@
"react-draggable": "^4.4.2", "react-draggable": "^4.4.2",
"react-hot-loader": "^4.12.21", "react-hot-loader": "^4.12.21",
"react-loading-skeleton": "^2.1.1", "react-loading-skeleton": "^2.1.1",
"react-use": "^17.4.0",
"reaptcha": "^1.7.2", "reaptcha": "^1.7.2",
"rxjs": "^6.5.5", "rxjs": "^6.5.5",
"skinview-utils": "^0.5.5", "skinview-utils": "^0.5.5",
"skinview3d": "^2.2.1", "skinview3d": "^3.0.0-alpha.1",
"spectre.css": "^0.5.8", "spectre.css": "^0.5.8",
"use-immer": "^0.4.2", "use-immer": "^0.4.2",
"workbox-expiration": "^5.1.3",
"workbox-routing": "^5.1.3",
"workbox-strategies": "^5.1.3",
"xterm": "^4.6.0", "xterm": "^4.6.0",
"xterm-addon-fit": "^0.4.0" "xterm-addon-fit": "^0.4.0"
}, },
@ -152,12 +151,13 @@
"/node_modules/", "/node_modules/",
"<rootDir>/resources/assets/tests/(views|components)/.*\\.ts$" "<rootDir>/resources/assets/tests/(views|components)/.*\\.ts$"
], ],
"maxWorkers": 1, "maxWorkers": "50%",
"globals": { "globals": {
"ts-jest": { "ts-jest": {
"tsconfig": "<rootDir>/resources/assets/tests/tsconfig.json", "tsconfig": "<rootDir>/resources/assets/tests/tsconfig.json",
"isolatedModules": true "isolatedModules": true
} }
} }
} },
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
} }

View File

@ -1,22 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" backupStaticAttributes="false" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" bootstrap="vendor/autoload.php" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<coverage processUncoveredFiles="true"> xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Application Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<source>
<include> <include>
<directory suffix=".php">./app</directory> <directory suffix=".php">./app</directory>
</include> </include>
<exclude> <exclude>
<directory suffix=".php">./app/Services/Cipher</directory> <directory suffix=".php">./app/Services/Cipher</directory>
</exclude> </exclude>
</coverage> </source>
<testsuites>
<testsuite name="Application Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<php> <php>
<env name="APP_ENV" value="testing"/> <env name="APP_ENV" value="testing" />
</php> </php>
<listeners>
<listener class="NunoMaduro\Collision\Adapters\Phpunit\Printer"/>
</listeners>
</phpunit> </phpunit>

View File

@ -1,5 +1,6 @@
/** @jsxImportSource @emotion/react */ /** @jsxImportSource @emotion/react */
import React, { useState, useEffect, useRef } from 'react' import React, { useState, useEffect, useRef } from 'react'
import { useMeasure } from 'react-use'
import { css } from '@emotion/react' import { css } from '@emotion/react'
import styled from '@emotion/styled' import styled from '@emotion/styled'
import * as skinview3d from 'skinview3d' import * as skinview3d from 'skinview3d'
@ -26,17 +27,12 @@ interface Props {
initPositionZ?: number initPositionZ?: number
} }
type AnimationHandles = { const animationFactories = [
walk: skinview3d.SubAnimationHandle | null () => new skinview3d.WalkingAnimation(),
run: skinview3d.SubAnimationHandle | null () => new skinview3d.RunningAnimation(),
rotate: skinview3d.SubAnimationHandle | null () => new skinview3d.FlyingAnimation(),
} () => new skinview3d.IdleAnimation(),
]
const animationHandles: AnimationHandles = {
walk: null,
run: null,
rotate: null,
}
const ActionButton = styled.i` const ActionButton = styled.i`
display: inline; display: inline;
@ -48,13 +44,17 @@ const ActionButton = styled.i`
` `
const cssViewer = css` const cssViewer = css`
flex: 1 1 auto;
${breakpoints.greaterThan(breakpoints.Breakpoint.lg)} { ${breakpoints.greaterThan(breakpoints.Breakpoint.lg)} {
min-height: 500px; min-height: 500px;
} }
min-height: 300px;
width: 100%; width: 100%;
height: 100%;
canvas { canvas {
cursor: move; display: flex;
justify-content: center;
} }
` `
@ -63,10 +63,9 @@ const Viewer: React.FC<Props> = (props) => {
const viewRef: React.MutableRefObject<skinview3d.SkinViewer> = useRef(null!) const viewRef: React.MutableRefObject<skinview3d.SkinViewer> = useRef(null!)
const containerRef = useRef<HTMLCanvasElement>(null) const containerRef = useRef<HTMLCanvasElement>(null)
const animationHandlesRef = useRef(animationHandles)
const [paused, setPaused] = useState(false) const [paused, setPaused] = useState(false)
const [running, setRunning] = useState(false) const [animation, setAnimation] = useState(0)
const [bgPicture, setBgPicture] = useState(-1) const [bgPicture, setBgPicture] = useState(-1)
const indicator = (() => { const indicator = (() => {
@ -83,36 +82,39 @@ const Viewer: React.FC<Props> = (props) => {
useEffect(() => { useEffect(() => {
const container = containerRef.current! const container = containerRef.current!
const viewer = new skinview3d.FXAASkinViewer({ const viewer = new skinview3d.SkinViewer({
canvas: container, canvas: container,
width: container.clientWidth, width: container.clientWidth,
height: container.clientHeight, height: container.clientHeight,
skin: props.skin || SkinSteve, skin: props.skin || SkinSteve,
cape: props.cape || '', cape: props.cape || undefined,
model: props.isAlex ? 'slim' : 'default', model: props.isAlex ? 'slim' : 'default',
zoom: initPositionZ / 100, zoom: initPositionZ / 100,
}) })
viewer.autoRotate = true
if (document.body.classList.contains('dark-mode')) { if (document.body.classList.contains('dark-mode')) {
viewer.background = '#6c757d' viewer.background = '#6c757d'
} }
const rotate = viewer.animations.add(skinview3d.RotatingAnimation)
animationHandlesRef.current.rotate = rotate
const control = skinview3d.createOrbitControls(viewer)
viewRef.current = viewer viewRef.current = viewer
return () => { return () => {
control.dispose()
viewer.dispose() viewer.dispose()
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
const [containerWrapperRef, containerMeasure] = useMeasure<HTMLDivElement>()
useEffect(() => {
viewRef.current.setSize(containerMeasure.width, containerMeasure.height)
})
useEffect(() => { useEffect(() => {
const viewer = viewRef.current const viewer = viewRef.current
viewer.loadSkin(props.skin || SkinSteve, props.isAlex ? 'slim' : 'default') viewer.loadSkin(props.skin || SkinSteve, {
model: props.isAlex ? 'slim' : 'default',
})
}, [props.skin, props.isAlex]) }, [props.skin, props.isAlex])
useEffect(() => { useEffect(() => {
@ -125,52 +127,63 @@ const Viewer: React.FC<Props> = (props) => {
}, [props.cape]) }, [props.cape])
useEffect(() => { useEffect(() => {
const handles = animationHandlesRef.current const viewer = viewRef.current
if (running) { const factory = animationFactories[animation]
handles.walk?.resetAndRemove() if (factory === undefined) {
handles.walk = null viewer.animation = null
const run = viewRef.current.animations.add(skinview3d.RunningAnimation)
run.speed = 0.6
handles.run = run
} else { } else {
handles.run?.resetAndRemove() const newAnimation = factory()
handles.run = null newAnimation.paused = paused // Perseve `paused` state
viewer.animation = newAnimation
const walk = viewRef.current.animations.add(skinview3d.WalkingAnimation)
handles.walk = walk
} }
}, [running]) // eslint-disable-next-line react-hooks/exhaustive-deps
}, [animation])
useEffect(() => { useEffect(() => {
viewRef.current.animations.paused = paused const currentAnimation = viewRef.current.animation
if (currentAnimation !== null) {
currentAnimation.paused = paused
}
}, [paused]) }, [paused])
useEffect(() => { useEffect(() => {
viewRef.current.loadBackground(backgrounds[bgPicture]!) const viewer = viewRef.current
const backgroundUrl = backgrounds[bgPicture]
if (backgroundUrl === undefined) {
viewer.background = null
} else {
viewer.loadBackground(backgroundUrl)
}
}, [bgPicture]) }, [bgPicture])
const togglePause = () => { const togglePause = () => {
setPaused((paused) => !paused) setPaused((paused) => {
if (paused) {
return false
} else {
viewRef.current.autoRotate = false
return true
}
})
} }
const toggleRun = () => { const toggleAnimation = () => {
setRunning((running) => !running) setAnimation((index) => (index + 1) % animationFactories.length)
setPaused(false)
} }
const toggleRotate = () => { const toggleRotate = () => {
const handles = animationHandlesRef.current const viewer = viewRef.current
if (handles.rotate) { viewer.autoRotate = !viewer.autoRotate
handles.rotate.paused = !handles.rotate.paused
}
} }
const handleReset = () => { const toggleBackEquippment = () => {
const handles = animationHandlesRef.current const player = viewRef.current.playerObject
handles.walk?.resetAndRemove() if (player.backEquipment === 'cape') {
handles.run?.resetAndRemove() player.backEquipment = 'elytra'
handles.rotate?.resetAndRemove() } else {
viewRef.current.animations.paused = true player.backEquipment = 'cape'
}
} }
const setWhite = () => { const setWhite = () => {
@ -213,38 +226,42 @@ const Viewer: React.FC<Props> = (props) => {
</h3> </h3>
<div> <div>
<ActionButton <ActionButton
className={`fas fa-${running ? 'walking' : 'running'}`} className={`fas fa-tablet ${props.cape ? '' : 'd-none'}`}
data-toggle="tooltip" data-toggle="tooltip"
data-placement="bottom" data-placement="bottom"
title={`${t('general.walk')} / ${t('general.run')}`} title={t('general.switchCapeElytra')}
onClick={toggleRun} onClick={toggleBackEquippment}
></ActionButton> ></ActionButton>
<ActionButton <ActionButton
className="fas fa-redo-alt" className={`fas fa-person-running`}
data-toggle="tooltip" data-toggle="tooltip"
data-placement="bottom" data-placement="bottom"
title={t('general.rotation')} title={t('general.switchAnimation')}
onClick={toggleRotate} onClick={toggleAnimation}
></ActionButton> ></ActionButton>
<ActionButton <ActionButton
className={`fas fa-${paused ? 'play' : 'pause'}`} className={`fas fa-${paused ? 'play' : 'pause'}`}
data-toggle="tooltip" data-toggle="tooltip"
data-placement="bottom" data-placement="bottom"
title={t('general.pause')} title={
paused
? t('general.playAnimation')
: t('general.pauseAnimation')
}
onClick={togglePause} onClick={togglePause}
></ActionButton> ></ActionButton>
<ActionButton <ActionButton
className="fas fa-stop" className="fas fa-rotate-right"
data-toggle="tooltip" data-toggle="tooltip"
data-placement="bottom" data-placement="bottom"
title={t('general.reset')} title={t('general.rotation')}
onClick={handleReset} onClick={toggleRotate}
></ActionButton> ></ActionButton>
</div> </div>
</div> </div>
</div> </div>
<div className="card-body p-0"> <div ref={containerWrapperRef} css={cssViewer} className="p-0">
<canvas ref={containerRef} css={cssViewer}></canvas> <canvas ref={containerRef}></canvas>
</div> </div>
<div className="card-footer"> <div className="card-footer">
<div className="mt-2 mb-3 d-flex"> <div className="mt-2 mb-3 d-flex">

View File

@ -6,13 +6,11 @@ import routes from './scripts/route'
Object.assign(window, { React, ReactDOM, $ }) Object.assign(window, { React, ReactDOM, $ })
if (blessing.route.startsWith('admin')) { const entry = document.querySelector('[href="#launch-cli"]')
const entry = document.querySelector<HTMLAnchorElement>('#launch-cli') entry?.addEventListener('click', async () => {
entry?.addEventListener('click', async () => { const { launch } = await import('./scripts/cli')
const { launch } = await import('./scripts/cli') launch()
launch() })
})
}
const route = routes.find((route) => const route = routes.find((route) =>
new RegExp(`^${route.path}$`, 'i').test(blessing.route), new RegExp(`^${route.path}$`, 'i').test(blessing.route),

View File

@ -1,9 +0,0 @@
const blessingElement = document.querySelector('#blessing-globals')!
// @ts-ignore
window.blessing = JSON.parse(blessingElement.textContent!)
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js?v6')
})
export {}

View File

@ -1,123 +0,0 @@
import { registerRoute } from 'workbox-routing'
import {
CacheFirst,
StaleWhileRevalidate,
NetworkOnly,
} from 'workbox-strategies'
import { ExpirationPlugin } from 'workbox-expiration'
const oneWeek = 7 * 24 * 3600
if (process.env.NODE_ENV === 'development') {
registerRoute(/\.js/, new NetworkOnly())
registerRoute(/\.css/, new NetworkOnly())
}
//#region Pictures
registerRoute(
/\/preview\/\d+/,
new CacheFirst({
cacheName: 'texture-preview-v2',
fetchOptions: {
credentials: 'omit',
},
plugins: [
new ExpirationPlugin({ maxAgeSeconds: oneWeek, purgeOnQuotaError: true }),
],
}),
)
registerRoute(
/\/app\/.*\.webp/,
new StaleWhileRevalidate({
cacheName: 'webp-resource-v1',
fetchOptions: {
credentials: 'omit',
},
}),
)
registerRoute(
/\/avatar\/\d+/,
new CacheFirst({
cacheName: 'avatar-v2',
fetchOptions: {
credentials: 'omit',
},
plugins: [new ExpirationPlugin({ maxAgeSeconds: oneWeek })],
}),
)
//#endregion
//#region JavaScript files
registerRoute(
/.+\/app\/\w{2,3}\.\w{7}\.js$/,
new CacheFirst({
cacheName: 'javascript-v1',
fetchOptions: {
credentials: 'omit',
mode: 'cors',
},
plugins: [
new ExpirationPlugin({ maxAgeSeconds: oneWeek, purgeOnQuotaError: true }),
],
}),
)
registerRoute(
/.+\/plugins\/.+\.js$/,
new StaleWhileRevalidate({
cacheName: 'javascript-v1',
fetchOptions: {
credentials: 'omit',
mode: 'cors',
},
plugins: [
new ExpirationPlugin({ maxAgeSeconds: oneWeek, purgeOnQuotaError: true }),
],
}),
)
//#endregion
//#region CSS files
registerRoute(
/.+\/app\/.*\.css$/,
new CacheFirst({
cacheName: 'stylesheet-v1',
fetchOptions: {
credentials: 'omit',
mode: 'cors',
},
plugins: [
new ExpirationPlugin({ maxAgeSeconds: oneWeek, purgeOnQuotaError: true }),
],
}),
)
registerRoute(
/.+\/plugins\/.+\.css$/,
new StaleWhileRevalidate({
cacheName: 'stylesheet-v1',
fetchOptions: {
credentials: 'omit',
mode: 'cors',
},
plugins: [
new ExpirationPlugin({ maxAgeSeconds: oneWeek, purgeOnQuotaError: true }),
],
}),
)
//#endregion
//#region Fonts
registerRoute(
({ request }) => request.destination === 'font',
new StaleWhileRevalidate({
cacheName: 'font-v1',
fetchOptions: {
credentials: 'omit',
mode: 'cors',
},
plugins: [new ExpirationPlugin({ maxEntries: 12 })],
}),
)
//#endregion

View File

@ -75,7 +75,7 @@ body {
.main-button { .main-button {
color: #fff; color: #fff;
padding: 0.8em 2.5em; padding: 0.8em 2em;
border-radius: 5px; border-radius: 5px;
background: transparent; background: transparent;
border: 1px solid #fff; border: 1px solid #fff;

View File

@ -72,6 +72,9 @@ function createLineChart(
chart.setOption({ chart.setOption({
title: { title: {
text: data.label, text: data.label,
textStyle: {
color: textColor,
},
}, },
textStyle: { textStyle: {
color: textColor, color: textColor,

View File

@ -257,7 +257,9 @@ const Show: React.FC = () => {
})() })()
const canEdit = currentUid === texture.uploader || isAdmin const canEdit = currentUid === texture.uploader || isAdmin
const textureUrl = `${blessing.base_url}/textures/${texture.hash}` const textureUrl = texture.hash
? `${blessing.base_url}/textures/${texture.hash}`
: ''
return ( return (
<> <>

View File

@ -32,7 +32,7 @@ const EmailVerification: React.FC = () => {
{t('user.verification.sending')} {t('user.verification.sending')}
</> </>
) : ( ) : (
<a href="#" onClick={send}> <a className="link-info" href="#" onClick={send}>
{t('user.verification.resend')} {t('user.verification.resend')}
</a> </a>
)} )}

View File

@ -1,71 +1,55 @@
/* eslint-disable max-params */ /* eslint-disable max-params */
/* eslint-disable max-classes-per-file */ /* eslint-disable max-classes-per-file */
import type { PlayerObject, SkinObject, CapeObject } from 'skinview3d' import type {
PlayerObject,
SkinObject,
CapeObject,
EarsObject,
} from 'skinview3d'
export class FXAASkinViewer { export class SkinViewer {
disposed = false disposed = false
background = '' background = null
animations = new RootAnimation() animation = null
animationPaused = false autoRotate = false
autoRotateSpeed = 1.0
playerObject: PlayerObject playerObject: PlayerObject
constructor() { constructor() {
this.animationPaused = false
this.playerObject = { this.playerObject = {
skin: {} as SkinObject, skin: {} as SkinObject,
cape: {} as CapeObject, cape: {} as CapeObject,
ears: {} as EarsObject,
backEquipment: 'cape',
} as PlayerObject } as PlayerObject
} }
loadSkin() {} loadSkin() {}
resetSkin() {}
loadCape() {} loadCape() {}
resetCape() {} resetCape() {}
loadBackground() {} loadBackground() {}
setSize() {}
dispose() { dispose() {
this.disposed = true this.disposed = true
} }
} }
export class RootAnimation { export class PlayerAnimation {
speed = 1.0
paused = false paused = false
progress = 0
add(animation: unknown) {
return animation
}
} }
export function createOrbitControls() { export class IdleAnimation extends PlayerAnimation {}
return {
dispose() {},
}
}
export const WalkingAnimation = new Proxy( export class WalkingAnimation extends PlayerAnimation {}
{},
{ export class RunningAnimation extends PlayerAnimation {}
get() {
return jest.fn() export class FlyingAnimation extends PlayerAnimation {}
},
},
)
export const RunningAnimation = new Proxy(
{},
{
get() {
return jest.fn()
},
},
)
export const RotatingAnimation = new Proxy(
{},
{
get() {
return jest.fn()
},
},
)
export function isSlimSkin() { export function isSlimSkin() {
return false return false

View File

@ -49,33 +49,27 @@ describe('indicator', () => {
}) })
describe('actions', () => { describe('actions', () => {
it('toggle run', () => { it('toggle animation', () => {
const { getByTitle } = render(<Viewer isAlex={false} />) const component = <Viewer isAlex={false} />
fireEvent.click(getByTitle(`${t('general.walk')} / ${t('general.run')}`)) const { getByTitle } = render(component)
fireEvent.click(getByTitle(`${t('general.switchAnimation')}`)) // should start running
fireEvent.click(getByTitle(`${t('general.switchAnimation')}`)) // should start flying
fireEvent.click(getByTitle(`${t('general.switchAnimation')}`)) // should be idle
fireEvent.click(getByTitle(`${t('general.switchAnimation')}`)) // should start walking
}) })
it('toggle rotation', () => { it('toggle rotation', () => {
const { getByTitle } = render(<Viewer isAlex={false} />) const { getByTitle } = render(<Viewer isAlex={false} />)
fireEvent.click(getByTitle(t('general.rotation'))) fireEvent.click(getByTitle(t('general.rotation'))) // should stop rotation
fireEvent.click(getByTitle(t('general.rotation'))) // should start rotation
}) })
it('toggle pause', () => { it('toggle pause', () => {
const { getByTitle } = render(<Viewer isAlex={false} />) const { getByTitle } = render(<Viewer isAlex={false} />)
const icon = getByTitle(t('general.pause')) const icon = getByTitle(t('general.pauseAnimation'))
fireEvent.click(icon) fireEvent.click(icon)
expect(icon).toHaveClass('fa-play') expect(icon).toHaveClass('fa-play')
}) })
it('reset', () => {
const { getByTitle } = render(<Viewer isAlex={false} />)
fireEvent.click(getByTitle(t('general.reset')))
})
it('reset when running', () => {
const { getByTitle } = render(<Viewer isAlex={false} />)
fireEvent.click(getByTitle(`${t('general.walk')} / ${t('general.run')}`))
fireEvent.click(getByTitle(t('general.reset')))
})
}) })
describe('background', () => { describe('background', () => {

View File

@ -26,7 +26,7 @@ score-intro:
rates: rates:
storage: ':score scores = 1 KB storage' storage: ':score scores = 1 KB storage'
player: ':score scores = 1 player' player: ':score scores = 1 player'
closet: ':score socres = 1 closet item' closet: ':score scores = 1 closet item'
closet: closet:
add: add:
success: Added :name to closet successfully. success: Added :name to closet successfully.

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