Compare commits

..

No commits in common. "dev" and "v5" have entirely different histories.
dev ... v5

385 changed files with 19617 additions and 23985 deletions

View File

@ -1,45 +0,0 @@
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

@ -1,26 +0,0 @@
# [Choice] PHP version (use -bullseye variants on local arm64/Apple Silicon): 8, 8.1, 8.0, 7, 7.4, 7.3, 8-bullseye, 8.1-bullseye, 8.0-bullseye, 7-bullseye, 7.4-bullseye, 7.3-bullseye, 8-buster, 8.1-buster, 8.0-buster, 7-buster, 7.4-buster
ARG VARIANT=8-bullseye
FROM mcr.microsoft.com/vscode/devcontainers/php:0-${VARIANT}
# Install MariaDB client
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get install -y mariadb-client zlib1g-dev libpng-dev libzip-dev libwebp-dev \
&& apt-get clean -y && rm -rf /var/lib/apt/lists/*
# Install php-mysql driver
RUN docker-php-ext-install mysqli pdo pdo_mysql gd zip
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
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
# Enable Apache rewrite module
RUN a2enmod rewrite
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1

View File

@ -1,13 +0,0 @@
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

@ -1,34 +0,0 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// 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
{
"name": "PHP & MariaDB (Community)",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace",
// Set *default* container specific settings.json values on container create.
"settings": {},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"xdebug.php-debug",
"bmewburn.vscode-intelephense-client",
"mrmlnc.vscode-apache"
],
// For use with PHP or Apache (e.g.php -S localhost:8080 or apache2ctl start)
"forwardPorts": [8080, 3306],
// Use 'postCreateCommand' to run commands after the container is created.
"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",
// Start apache2 after the container is started
"postStartCommand": "apache2ctl start && echo '\\n👉 \\e[0;32mPlease run '\\'yarn build\\'' to build the frontend. Application is available on port 8080.\\e[0m 👈\\n'",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode",
"features": {
"powershell": "latest"
}
}

View File

@ -1,47 +0,0 @@
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
args:
# Update 'VARIANT' to pick a version of PHP version: 8, 8.1, 8.0, 7, 7.4
# Append -bullseye or -buster to pin to an OS version.
# Use -bullseye variants on local arm64/Apple Silicon.
VARIANT: "8-bullseye"
# Optional Node.js version
NODE_VERSION: "lts/*"
volumes:
- ..:/workspace:cached
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
network_mode: service:db
# Uncomment the next line to use a non-root user for all processes.
# user: vscode
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
db:
image: mariadb:10.4
restart: unless-stopped
volumes:
- mariadb-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: mariadb
MYSQL_DATABASE: blessingskin
MYSQL_USER: username
MYSQL_PASSWORD: secret
# Add "forwardPorts": ["3306"] to **devcontainer.json** to forward MariaDB locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
volumes:
mariadb-data:

View File

@ -1,9 +1,6 @@
.git/
.github/
.vscode/
.idea/
.cache/
.cache-loader/
coverage/
node_modules/
plugins/**
@ -11,13 +8,7 @@ public/app/*
public/lang/*
public/plugins/**
resources/assets/tests/
storage/*.db
storage/*.sqlite
storage/insane-profile-cache
storage/oauth-public.key
storage/oauth-private.key
storage/install.lock
storage/options.php
scripts/
storage/debugbar
storage/framework/cache/**
storage/framework/sessions/**
@ -26,34 +17,24 @@ storage/framework/views/**
storage/logs/**
storage/packages/**
storage/textures/*
storage/update_cache/*
target/
tests/
vendor/*
vendor/
_ide_helper.php
.dockerignore
.editorconfig
.env
.env.testing
.eslintignore
.eslintrc.yml
.gitignore
.php_cs.*
.php-cs-fixer.cache
.php-cs-fixer.dist.php
.phpstorm.meta.php
.phpunit.result.cache
.sass-cache
.uini
azure-pipelines.yml
crowdin.yml
docker-compose.yml
Dockerfile*
*.Dockerfile
index.html
junit.xml
phpunit.xml
README*.md
server.php
tsconfig.dev.json
tsconfig.eslint.json
yarn-error.log

View File

@ -2,6 +2,9 @@ APP_DEBUG=false
APP_ENV=production
APP_FALLBACK_LOCALE=en
ASSET_ENV=production
ASSET_URL=
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306

View File

@ -35,71 +35,3 @@ PLUGINS_URL=
TEXTURES_DIR=
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-----"

View File

@ -8,7 +8,6 @@ extends:
- eslint:recommended
- plugin:@typescript-eslint/recommended
- plugin:@typescript-eslint/recommended-requiring-type-checking
- plugin:react-hooks/recommended
rules:
prefer-const: error
'@typescript-eslint/no-unsafe-assignment': off
@ -24,4 +23,3 @@ rules:
- off
- checksVoidReturn: false
'@typescript-eslint/unbound-method': off
'@typescript-eslint/restrict-template-expressions': off

View File

@ -46,7 +46,7 @@ location ~* \w+\.hot-update\.json$ {
}
```
`APP_ENV` 为其它值时,您需要事先执行 `pwsh ./tools/build.ps1`。此命令将构建并压缩前端资源。通常用于生产环境。
`APP_ENV` 为其它值时,您需要事先执行 `pwsh ./scripts/build.ps1`。此命令将构建并压缩前端资源。通常用于生产环境。
> 如果传递 `-Simple` 参数给 `build.ps1` 脚本,则只会运行 webpack 来编译代码,而不会复制首页背景以及生成 commit 信息。

36
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,36 @@
<!-- 在提交一个新 issue 前,请先阅读以下内容: -->
<!-- Before opening a new issue, please make sure to READ the articles below: -->
<!-- * FAQ 常见问题: https://git.io/fjRtn -->
<!-- * 报告问题的正确姿势https://git.io/fjRtc -->
<!-- 把下面模板中的占位文字删除,并按照你的情况认真填写,谢谢 -->
<!-- Please remove the placeholders and fill in the template according to your situation. -->
## The Problem 问题描述
<!-- 如果您要报告的问题是与插件相关的,请在 bs-community/blessing-skin-plugins 提 issue谢谢合作 -->
<!-- If you're going to report a problem related to plugins, please open issue at bs-community/blessing-skin-plugins. -->
## Environment 运行环境
- Blessing Skin 版本 (Version of Blessing Skin):
- PHP 版本 (Version of PHP):
- Apache / Nginx:
- 什么浏览器,出现错误时的地址栏 URL 是什么 (Which browser and URL):
## Error Message 错误信息
<!--
请提供详细信息,如截图。日志内容请不要直接贴出来,请把它放在 pastebin 等网站上。
不提供详细信息的 issue 或不按要求提供日志的将被直接忽略,谢谢合作。
Please provide more information, such as screenshots.
For logs, don't paste it in issue directly. You can paste in on pastebin.
You will be ignored if you don't provide enough information or
your logs messes up the issue.
Thanks for your cooperation.
-->
## Steps to Reproduce 重现步骤
<!-- Tell us how to reproduce this issue. -->
<!-- 详细描述你出错前的操作步骤 -->

View File

@ -1,71 +0,0 @@
name: Bug 报告
description: 发起 bug 报告
body:
- type: markdown
attributes:
value: |
在报告问题之前,请确保您已经 **认真** 阅读:
- [FAQ](https://blessing.netlify.app/en/faq.html)
- [报告问题的正确姿势](https://blessing.netlify.app/report.html)
- type: input
id: bs
attributes:
label: Blessing Skin 版本
validations:
required: true
- type: dropdown
id: php
attributes:
label: PHP 版本
options:
- '7.3'
- '7.4'
- '8.0'
- '8.1'
validations:
required: true
- type: dropdown
id: browsers
attributes:
label: 出现问题时所使用的浏览器
multiple: true
options:
- Firefox
- Chrome
- Safari
- Microsoft Edge
validations:
required: true
- type: dropdown
id: web-server
attributes:
label: 您正在使用的 Web Server
options:
- Nginx
- Apache
- type: checkboxes
id: baota
attributes:
label: 您正在使用宝塔吗?
options:
- label:
- type: textarea
id: what-happened
attributes:
label: 出现了什么问题?
description: 顺便告诉我们,您期望的行为是怎样的?
validations:
required: true
- type: textarea
id: logs
attributes:
label: 错误日志
description: 您可以粘贴 Blessing Skin 的日志或 Web Server 的日志。Blessing Skin 的日志位于 `storage/logs` 目录里。
render: text
- type: textarea
id: reproduction
attributes:
label: 重现步骤
description: 详细描述您出错前的操作步骤
validations:
required: true

View File

@ -1,64 +0,0 @@
name: Bug Report
description: File a bug report
body:
- type: markdown
attributes:
value: |
Please filing an issue, please make sure you've read:
- [FAQ](https://blessing.netlify.app/en/faq.html)
- type: input
id: bs
attributes:
label: Which version of Blessing Skin are you using?
validations:
required: true
- type: dropdown
id: php
attributes:
label: Which version of PHP are you using?
options:
- '7.3'
- '7.4'
- '8.0'
- '8.1'
validations:
required: true
- type: dropdown
id: browsers
attributes:
label: What browsers are you seeing the problem on?
multiple: true
options:
- Firefox
- Chrome
- Safari
- Microsoft Edge
validations:
required: true
- type: dropdown
id: web-server
attributes:
label: Which web server are you using?
options:
- Nginx
- Apache
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
validations:
required: true
- type: textarea
id: logs
attributes:
label: Error Logs
description: You can paste logs of Blessing Skin or your web server. Logs of Blessing Skin can be found at `storage/logs` directory.
render: text
- type: textarea
id: reproduction
attributes:
label: Steps to Reproduce
description: Tell us how to reproduce this issue.
validations:
required: true

View File

@ -1,20 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Ask questions about using Blessing Skin
url: https://github.com/bs-community/blessing-skin-server/discussions
about: If you're not going to report a bug, please ask and answer questions there.
- name: Report Issue about Blessing Skin plugins
url: https://github.com/bs-community/blessing-skin-plugins/issues
about: Please ask and answer questions there.
- name: Report Issue about integrating with Flarum
url: https://github.com/bs-community/flarum-oauth-client/issues
about: Please ask and answer questions there.
- name: 询问关于使用 Blessing Skin 的问题
url: https://github.com/bs-community/blessing-skin-server/discussions
about: 如果您并不是要报告 bug请在那里进行讨论。
- name: 报告与 Blessing Skin 插件有关的问题
url: https://github.com/bs-community/blessing-skin-plugins/issues
about: 请在那里报告问题。
- name: 报告与 Flarum 对接有关的问题
url: https://github.com/bs-community/flarum-oauth-client/issues
about: 请在那里报告问题。

View File

@ -18,138 +18,104 @@ jobs:
php-lint:
name: PHP Linting
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'skip ci')"
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.3
coverage: none
extensions: mbstring, dom, fileinfo, gd, imagick
- name: Install dependencies
run: |
composer install --prefer-dist --no-progress
- name: Prepare
run: |
cp .env.example .env
mkdir -p resources/views/overrides
- name: Validate Twig templates
run: php artisan twig:lint -v
- name: Check coding style
run: ./vendor/bin/php-cs-fixer fix --dry-run --stop-on-violation --diff --format=txt
- name: Checkout code
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 7.4
coverage: none
extensions: mbstring, dom, fileinfo, gd
- name: Install dependencies
run: |
composer install --prefer-dist --no-progress --no-suggest
composer global require friendsofphp/php-cs-fixer
- name: Prepare
run: |
cp .env.example .env
php artisan key:generate
mkdir -p resources/views/overrides
- name: Validate Twig templates
run: php artisan twig:lint -v
- name: Check coding style
run: php-cs-fixer fix --dry-run --stop-on-violation --diff-format=udiff
php:
name: PHP ${{ matrix.php }} Tests
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'skip ci')"
strategy:
fail-fast: false
matrix:
php: ['8.2', '8.3']
php: ['7.2', '7.3', '7.4']
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP only
uses: shivammathur/setup-php@v2
if: matrix.php != '8.3'
with:
php-version: ${{ matrix.php }}
coverage: none
extensions: mbstring, dom, fileinfo, sqlite, gd, zip, imagick
- name: Setup PHP with Xdebug
uses: shivammathur/setup-php@v2
if: matrix.php == '8.3'
with:
php-version: ${{ matrix.php }}
coverage: xdebug
extensions: mbstring, dom, fileinfo, sqlite, gd, zip, imagick
- name: Cache Composer dependencies
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Run tests only
if: matrix.php != '8.3'
run: ./vendor/bin/phpunit
- name: Run tests with coverage report
if: matrix.php == '8.3'
run: ./vendor/bin/phpunit --coverage-clover=coverage.xml
- name: Upload coverage report
uses: codecov/codecov-action@v1
if: matrix.php == '8.3' && success()
with:
token: ${{ secrets.CODECOV_TOKEN }}
name: github-actions
- name: Checkout code
uses: actions/checkout@v2
- name: Setup PHP only
uses: shivammathur/setup-php@v2
if: matrix.php != '7.2'
with:
php-version: ${{ matrix.php }}
coverage: none
extensions: mbstring, dom, fileinfo, sqlite, gd, zip
- name: Setup PHP with Xdebug
uses: shivammathur/setup-php@v2
if: matrix.php == '7.2'
with:
php-version: ${{ matrix.php }}
coverage: xdebug
extensions: mbstring, dom, fileinfo, sqlite, gd, zip
- name: Cache Composer dependencies
uses: actions/cache@v1
with:
path: vendor
key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: composer install --no-progress --no-suggest --prefer-dist --optimize-autoloader
- name: Run tests only
if: matrix.php != '7.2'
run: ./vendor/bin/phpunit
shell: pwsh
- name: Run tests with coverage report
if: matrix.php == '7.2'
run: ./vendor/bin/phpunit --coverage-clover=coverage.xml
shell: pwsh
- name: Upload coverage report
uses: codecov/codecov-action@v1
if: matrix.php == '7.2' && success()
with:
token: ${{ secrets.CODECOV_TOKEN }}
name: github-actions
lint:
name: Frontend Linting
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'skip ci')"
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run checks
run: |
yarn lint
yarn fmt:check
yarn type:check
- name: Checkout code
uses: actions/checkout@v2
- name: Install dependencies
run: yarn
- name: Run checks
run: |
yarn lint
yarn fmt:check
yarn tsc -p . --noEmit
yarn tsc -p ./resources/assets/tests --noEmit
jest:
name: Frontend Tests
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'skip ci')"
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: yarn
- name: Run tests
run: yarn test --coverage
- name: Upload coverage report
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
name: github-actions
build:
name: Snapshot Build
runs-on: ubuntu-latest
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
uses: actions/checkout@v4
- name: Cache Node dependencies
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-yarn-lock-${{ hashFiles('yarn.lock') }}
restore-keys: ${{ runner.os }}-yarn-lock-
- name: Cache Composer dependencies
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: |
composer install --prefer-dist --no-progress --no-dev
yarn install --frozen-lockfile
- name: Build frontend
run: |
yarn build
cp resources/assets/src/images/bg.webp public/app/
cp resources/assets/src/images/favicon.ico public/app/
- uses: benjlevesque/short-sha@v3.0
id: short-sha
- 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
- name: Upload artifact
uses: actions/upload-artifact@v4
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
- name: Checkout code
uses: actions/checkout@v2
- name: Install dependencies
run: yarn
- name: Run tests
run: yarn test --coverage
- name: Upload coverage report
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
name: github-actions

View File

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

View File

@ -14,24 +14,23 @@ on:
- 'routes/**'
- '*.lock'
- 'webpack.*'
workflow_dispatch:
jobs:
notification:
name: Send Message
runs-on: ubuntu-latest
steps:
- name: Download bot
run: |
$headers = @{ Authorization = 'Bearer ${{ secrets.GITHUB_TOKEN }}' }
$botRelease = (Invoke-WebRequest -Headers $headers 'https://api.github.com/repos/bs-community/telegram-bot/releases/latest').Content | ConvertFrom-Json
$botBinUrl = ((Invoke-WebRequest -Headers $headers $botRelease.assets_url).Content | ConvertFrom-Json).browser_download_url
bash -c "curl --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' -fSL $botBinUrl -o bot"
chmod +x ./bot
shell: pwsh
- name: Run bot
run: ./bot diff
shell: pwsh
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
- name: Download bot
run: |
$headers = @{ Authorization = 'Bearer ${{ secrets.GITHUB_TOKEN }}' }
$botRelease = (Invoke-WebRequest -Headers $headers 'https://api.github.com/repos/bs-community/telegram-bot/releases/latest').Content | ConvertFrom-Json
$botBinUrl = ((Invoke-WebRequest -Headers $headers $botRelease.assets_url).Content | ConvertFrom-Json).browser_download_url
bash -c "curl --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' -fSL $botBinUrl -o bot"
chmod +x ./bot
shell: pwsh
- name: Run bot
run: ./bot diff
shell: pwsh
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}

7
.gitignore vendored
View File

@ -1,3 +1,4 @@
.DS_Store
.env
.sass-cache
coverage
@ -23,7 +24,7 @@ storage/oauth-private.key
storage/install.lock
storage/options.php
.phpunit.result.cache
.php-cs-fixer.cache
.php_cs.cache
resources/views/overrides
.DS_Store
*/.DS_Store
public/sw.js
public/meta.js

View File

@ -1,41 +0,0 @@
tasks:
- init: yarn install
command: yarn dev
- init: composer install
command: |
cp .env.example .env
mkdir public/app/
cp resources/assets/src/images/bg.webp resources/assets/src/images/favicon.ico public/app
touch storage/database.db
sed 's/DB_CONNECTION=mysql/DB_CONNECTION=sqlite/' -i .env
sed 's/DB_DATABASE=blessingskin/DB_DATABASE=\/workspace\/blessing-skin-server\/storage\/database\.db/' -i .env
php artisan key:generate
php artisan serve --host=0.0.0.0
- command: gp ports await 8080 && gp preview $(gp url 8000)
github:
prebuilds:
# enable for the master/default branch (defaults to true)
master: true
# enable for all branches in this repo (defaults to false)
branches: false
# enable for pull requests coming from this repo (defaults to true)
pullRequests: true
# add a check to pull requests (defaults to true)
addCheck: true
# add a "Review in Gitpod" button as a comment to pull requests (defaults to false)
addComment: false
vscode:
extensions:
- 'editorconfig.editorconfig'
- 'eamodio.gitlens'
- 'bmewburn.vscode-intelephense-client'
- 'esbenp.prettier-vscode'
- 'jpoissonnier.vscode-styled-components'
- 'mblode.twig-language-2'
- 'felixfbecker.php-debug'
ports:
- port: 8080
visibility: public

View File

@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn pretty-quick --staged

View File

@ -1,23 +0,0 @@
<?php
$finder = PhpCsFixer\Finder::create()
->in('app')
->in('database')
->in('routes')
->in('tests');
$config = new PhpCsFixer\Config();
return $config->setRules([
'@Symfony' => true,
'align_multiline_comment' => true,
'array_syntax' => ['syntax' => 'short'],
'increment_style' => ['style' => 'post'],
'list_syntax' => ['syntax' => 'short'],
'yoda_style' => false,
'global_namespace_import' => [
'import_constants' => true,
'import_functions' => true,
'import_classes' => null,
],
])
->setFinder($finder);

21
.php_cs.dist Normal file
View File

@ -0,0 +1,21 @@
<?php
$finder = PhpCsFixer\Finder::create()
->in('app')
->in('database')
->in('routes')
->in('tests')
;
return PhpCsFixer\Config::create()
->setRules([
'@Symfony' => true,
'align_multiline_comment' => true,
'array_syntax' => ['syntax' => 'short'],
'increment_style' => ['style' => 'post'],
'list_syntax' => ['syntax' => 'short'],
'yoda_style' => false,
])
->setFinder($finder)
;

View File

@ -1,7 +1,12 @@
{
"recommendations": [
"editorconfig.editorconfig",
"eamodio.gitlens",
"bmewburn.vscode-intelephense-client",
"esbenp.prettier-vscode"
"esbenp.prettier-vscode",
"jpoissonnier.vscode-styled-components",
"mblode.twig-language-2",
"firefox-devtools.vscode-firefox-debug",
"felixfbecker.php-debug"
]
}

68
.vscode/launch.json vendored
View File

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

View File

@ -1,3 +0,0 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@ -1,82 +0,0 @@
FROM composer:latest as vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install \
--prefer-dist \
--no-dev \
--no-suggest \
--no-progress \
--no-autoloader \
--no-scripts \
--no-interaction \
--ignore-platform-reqs
FROM node:alpine as frontend
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY postcss.config.js tsconfig.build.json tsconfig.json webpack.config.ts ./
COPY tools/*Plugin.ts ./tools/
COPY resources ./resources
RUN yarn build && \
cp resources/assets/src/images/bg.webp public/app/ && \
cp resources/assets/src/images/favicon.ico public/app/ && \
# Strip unused files
rm -rf *.config.js *.config.ts tsconfig.* \
package.json yarn.lock node_modules/ \
resources/assets/ resources/lang resources/misc resources/misc/backgrounds/ \
tools/
FROM composer:latest as builder
WORKDIR /app
COPY . ./
COPY --from=vendor /app ./
COPY --from=frontend /app/public ./public
COPY --from=frontend /app/resources/views/assets ./resources/views/assets
RUN composer dump-autoload -o --no-dev -n && \
rm -rf *.config.js *.config.ts tsconfig.* \
package.json yarn.lock node_modules/ \
resources/assets/ resources/misc resources/misc/backgrounds/ \
tools/ && \
mv .env.example .env && \
php artisan key:generate && \
mv .env storage/ && \
ln -s storage/.env .env && \
touch storage/database.db && \
mkdir storage/plugins && \
sed 's/PLUGINS_DIR=null/PLUGINS_DIR=\/app\/storage\/plugins/' -i storage/.env && \
sed 's/DB_CONNECTION=mysql/DB_CONNECTION=sqlite/' -i storage/.env && \
sed 's/DB_DATABASE=blessingskin/DB_DATABASE=\/app\/storage\/database\.db/' -i storage/.env
FROM php:8-apache
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN chmod +x /usr/local/bin/install-php-extensions && \
install-php-extensions gd zip
WORKDIR /app
COPY --from=builder /app ./
ENV APACHE_DOCUMENT_ROOT /app/public
RUN chown -R www-data:www-data . && \
sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf && \
sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf && \
a2enmod rewrite headers
EXPOSE 80
VOLUME ["/app/storage"]

View File

@ -1,92 +0,0 @@
- **简体中文**
- [English](./README.md)
<p align="center"><img src="https://media.githubusercontent.com/media/bs-community/logo/main/logo.png"></p>
<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://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/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://discord.com/invite/QAsyEyt"><img alt="Discord" src="https://discord.com/api/guilds/761226550921658380/widget.png"></a>
</p>
优雅的开源 Minecraft 皮肤站,现在,回应您的等待。
Blessing Skin 是一款能让您上传、管理和分享您的 Minecraft 皮肤和披风的 Web 应用程序。与修改游戏材质包不同的是,所有人都能在游戏中看到各自的皮肤和披风(当然,前提是玩家们要使用同一个皮肤站)。
Blessing Skin 是一个开源的 PHP 项目,这意味着您可以自由地在您的服务器上部署它。
## 特性
- 完整实现了一个皮肤站该有的功能
- 支持单用户多个角色
- 通过皮肤库来分享您的皮肤和披风!
- 易于使用
- 可视化的用户、角色、材质管理页面
- 详细的站点配置页面
- 多处 UI/UX 优化只为更好的用户体验
- 安全
- 支持多种安全密码 Hash 算法
- 注册可要求 Email 验证
- 防止恶意请求的积分系统
- 强大的可扩展性
- 多种多样的插件
- 支持与 Authme/Discuz 等程序的用户数据对接(插件)
- 支持自定义 Yggdrasil API 外置登录系统(插件)
## 环境要求
Blessing Skin 对您的服务器有一定的要求。在大多数情况下,下列所需的 PHP 扩展已经开启。
- 一台支持 URL 重写的主机Nginx 或 Apache
- PHP >= 8.1.0
- 安装并启用如下 PHP 扩展:
- OpenSSL >= 1.1.1 (TLS 1.3)
- PDO
- Mbstring
- Tokenizer
- GD
- XML
- Ctype
- JSON
- fileinfo
- zip
- Imagick
## 快速使用
请参阅 [安装指南](https://blessing.netlify.app/setup.html)。
## 插件系统
Blessing Skin 提供了强大的插件系统,您可以通过添加多种多样的插件来为您的皮肤站添加功能。
## 自行构建
详情可阅读 [这里](https://blessing.netlify.app/build.html)。
> 您可以订阅我们的 Telegram 频道 [Blessing Skin News](https://t.me/blessing_skin_news) 来获取最新开发动态。当有新的 Commit 被推送时,我们的机器人将会在频道内发送一条消息来提示您能否拉取最新代码,以及拉取后应该做什么。
## 国际化i18n
Blessing Skin 可支持多种语言,当前支持英语、简体中文和西班牙语。
如果您愿意将您的翻译贡献出来,欢迎参与 [我们的 Crowdin 项目](https://crowdin.com/project/blessing-skin)。
## 问题报告
请参阅 [报告问题的正确姿势](https://blessing.netlify.app/report.html)。
## 相关链接
- [用户手册](https://blessing.netlify.app/)
- [插件开发文档](https://bs-plugin.netlify.app/)
## 版权
MIT License
Copyright (c) 2016-present The Blessing Skin Team
程序原作者为 [@printempw](https://printempw.github.io/),转载请注明。

173
README.md
View File

@ -1,48 +1,48 @@
- [简体中文](./README-zh.md)
- **English**
- **简体中文**
- [English](./README_EN.md)
<p align="center"><img src="https://media.githubusercontent.com/media/bs-community/logo/main/logo.png"></p>
<p align="center"><img src="https://img.blessing.studio/images/2017/01/01/bs-logo.png"></p>
<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/actions/workflow/status/bs-community/blessing-skin-server/CI.yml?branch=dev&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/workflow/status/bs-community/blessing-skin-server/CI?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/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://discord.com/invite/QAsyEyt"><img alt="Discord" src="https://discord.com/api/guilds/761226550921658380/widget.png"></a>
</p>
Puzzled by losing your custom skins in Minecraft servers runing in offline mode? Now you can easily get them back with the help of Blessing Skin!
优雅的开源 Minecraft 皮肤站,现在,回应您的等待。
Blessing Skin is a web application where you can upload, manage and share your custom skins & capes! Unlike modifying a resource pack, everyone in the game will see the different skins of each other (of course they should register at the same website too).
Blessing Skin 是一款能让您上传、管理和分享您的 Minecraft 皮肤和披风的 Web 应用程序。与修改游戏材质包不同的是,所有人都能在游戏中看到各自的皮肤和披风(当然,前提是玩家们要使用同一个皮肤站)。
Blessing Skin is an open-source project written in PHP, which means you can deploy it freely on your own web server!
Blessing Skin 是一个开源的 PHP 项目,这意味着您可以自由地在您的服务器上部署它。
## Features
## 特性
- A fully functional skin hosting service
- Multiple player names can be owned by one user on the website
- Share your skins and capes online with skin library!
- Easy-to-use
- Visual page for user/player/texture management
- Detailed option pages
- Many tweaks for a better UI/UX
- Security
- Support many secure password hash algorithms
- Email verification for registration
- Score system for preventing evil requests
- Incredibly extensible
- Plenty of plugins available
- Integration with Authme/Discuz (available as plugin)
- Support custom Yggdrasil API authentication (available as plugin)
- 完整实现了一个皮肤站该有的功能
- 支持单用户多个角色
- 通过皮肤库来分享您的皮肤和披风!
- 易于使用
- 可视化的用户、角色、材质管理页面
- 详细的站点配置页面
- 多处 UI/UX 优化只为更好的用户体验
- 安全
- 支持多种安全密码 Hash 算法
- 注册可要求 Email 验证
- 防止恶意请求的积分系统
- 强大的可扩展性
- 多种多样的插件
- 支持与 Authme/Discuz 等程序的用户数据对接(插件)
- 支持自定义 Yggdrasil API 外置登录系统(插件)
## Requirements
## 环境要求
Blessing Skin has only a few system requirements. In most cases, these PHP extensions are already enabled.
Blessing Skin 对您的服务器有一定的要求。在大多数情况下,下列所需的 PHP 扩展已经开启。
- Web server with URL rewriting enabled (Nginx or Apache)
- PHP >= 8.1.0
- PHP Extensions
- OpenSSL >= 1.1.1 (TLS 1.3)
- 一台支持 URL 重写的主机Nginx 或 Apache
- PHP >= 7.2.5
- 安装并启用如下 PHP 扩展:
- OpenSSL
- PDO
- Mbstring
- Tokenizer
@ -52,39 +52,120 @@ Blessing Skin has only a few system requirements. In most cases, these PHP exten
- JSON
- fileinfo
- zip
- Imagick
## Quick Install
## 快速使用
Please read [Installation Guide](https://blessing.netlify.app/en/setup.html).
请参阅 [安装指南](https://blessing.netlify.app/setup.html)。
## Plugin System
## 插件系统
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 提供了强大的插件系统,您可以通过添加多种多样的插件来为您的皮肤站添加功能。
## Build From Source
## 支持并赞助 Blessing Skin
Please refer to [Manual Build](https://blessing.netlify.app/build.html).
如果您觉得这个软件对您很有帮助,欢迎通过赞助来支持开发!
## Internationalization
目前可在 [爱发电](https://afdian.net/@blessing-skin) 上赞助。
Blessing Skin supports multiple languages, while currently supporting English, Simplified Chinese and Spanish.
### Sponsors
If you are willing to contribute your translation, welcome to join [our Crowdin project](https://crowdin.com/project/blessing-skin).
<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="120" height="120">
<br>
飒爽师叔
</a>
</td>
<td align=center>
<a href="https://afdian.net/@Luohuayu">
<img src="https://pic1.afdiancdn.com/user/66c740fad75011ea9fce52540025c377/avatar/870ee9ea29a1c179c435f1ad64aee79b_w640_h640_s52.jpg" width="120" height="120">
<br>
落花雨
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/default/avatar/avatar-purple.png" width="120" height="120">
<br>
graytoowolf
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/user/e227ea708ac911eaa6c852540025c377/avatar/a56a7ceafa12e96ba6750e880f04b7e4_w1024_h1024_s883.jpg" width="120" height="120">
<br>
mcha0
</a>
</td>
<td align=center>
<a href="https://afdian.net/@mengluorj">
<img src="https://pic1.afdiancdn.com/user/ffc6500452ed11e9994e52540025c377/avatar/ae9c5ec36b51e8314787cc19acf2d12e_w815_h815_s459.jpg" width="120" height="120">
<br>
MengLuoRJ
</a>
</td>
</tr>
</tbody>
</table>
## Report Bugs
### Backers
Read [FAQ](https://blessing.netlify.app/faq.html) and double check if your situation doesn't suit any case mentioned there before reporting.
<table>
<tbody>
<tr>
<td align=center>
<a href="https://afdian.net/@mfwg6">
<img src="https://pic1.afdiancdn.com/user/18ad3338e58a11e9b29352540025c377/avatar/eb04b4b54975d0d229e77fbcd4220dc4_w1080_h1920_s541.jpg" width="75" height="75">
<br>
皮皮帕
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/user/68d07bf851fc11e98e5652540025c377/avatar/48538be153c8eebc3eb5cb6bc085cde9_w574_h574_s173.jpg" width="75" height="75">
<br>
dz_paji
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/user/b68f3a9aaef511e9826f52540025c377/avatar/03b244e92f9c4198672ce46e3fd7e100_w690_h690_s129.jpeg" width="75" height="75">
<br>
神奇威廉
</a>
</td>
</tr>
</tbody>
</table>
When reporting a problem, please attach your log file (located at `storage/logs/laravel.log`) and the information of your server where the error occured on. You should also read this [guide](https://blessing.netlify.app/report.html) before reporting a problem.
## 自行构建
## Related Links
详情可阅读 [这里](https://blessing.netlify.com/build.html)。
- [User Manual](https://blessing.netlify.app/en/)
- [Plugins Development Documentation](https://bs-plugin.netlify.app/)
> 您可以订阅我们的 Telegram 频道 [Blessing Skin News](https://t.me/blessing_skin_news) 来获取最新开发动态。当有新的 Commit 被推送时,我们的机器人将会在频道内发送一条消息来提示您能否拉取最新代码,以及拉取后应该做什么。
## Copyright & License
## 国际化i18n
Blessing Skin 可支持多种语言,当前支持英语、简体中文和西班牙语。
如果您愿意将您的翻译贡献出来,欢迎参与 [我们的 Crowdin 项目](https://crowdin.com/project/blessing-skin)。
## 问题报告
请参阅 [报告问题的正确姿势](https://blessing.netlify.com/report.html)。
## 相关链接
- [用户手册](https://blessing.netlify.app/)
- [插件开发文档](https://bs-plugin.netlify.app/)
## 版权
MIT License
Copyright (c) 2016-present The Blessing Skin Team
程序原作者为 [@printempw](https://blessing.studio/),转载请注明。

169
README_EN.md Normal file
View File

@ -0,0 +1,169 @@
- [简体中文](./README.md)
- **English**
<p align="center"><img src="https://img.blessing.studio/images/2017/01/01/bs-logo.png"></p>
<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://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/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://discord.com/invite/QAsyEyt"><img alt="Discord" src="https://discord.com/api/guilds/761226550921658380/widget.png"></a>
</p>
Puzzled by losing your custom skins in Minecraft servers runing in offline mode? Now you can easily get them back with the help of Blessing Skin!
Blessing Skin is a web application where you can upload, manage and share your custom skins & capes! Unlike modifying a resource pack, everyone in the game will see the different skins of each other (of course they should register at the same website too).
Blessing Skin is an open-source project written in PHP, which means you can deploy it freely on your own web server!
## Features
- A fully functional skin hosting service
- Multiple player names can be owned by one user on the website
- Share your skins and capes online with skin library!
- Easy-to-use
- Visual page for user/player/texture management
- Detailed option pages
- Many tweaks for a better UI/UX
- Security
- Support many secure password hash algorithms
- Email verification for registration
- Score system for preventing evil requests
- Incredibly extensible
- Plenty of plugins available
- Integration with Authme/CrazyLogin/Discuz (available as plugin)
- Support custom Yggdrasil API authentication (available as plugin)
## Requirements
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)
- PHP >= 7.2.5
- PHP Extensions
- OpenSSL
- PDO
- Mbstring
- Tokenizer
- GD
- XML
- Ctype
- JSON
- fileinfo
- zip
## Quick Install
Please read [Installation Guide](https://blessing.netlify.app/en/setup.html).
## Plugin System
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/@ValiantShishu976400">
<img src="https://pic1.afdiancdn.com/user/178a08963a5e11e9addd52540025c377/avatar/ece9f089aaf2c2f83204a8de11697caf_w350_h350_s16.jpg" width="120" height="120">
<br>
飒爽师叔
</a>
</td>
<td align=center>
<a href="https://afdian.net/@Luohuayu">
<img src="https://pic1.afdiancdn.com/user/66c740fad75011ea9fce52540025c377/avatar/870ee9ea29a1c179c435f1ad64aee79b_w640_h640_s52.jpg" width="120" height="120">
<br>
落花雨
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/default/avatar/avatar-purple.png" width="120" height="120">
<br>
graytoowolf
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/user/e227ea708ac911eaa6c852540025c377/avatar/a56a7ceafa12e96ba6750e880f04b7e4_w1024_h1024_s883.jpg" width="120" height="120">
<br>
mcha0
</a>
</td>
<td align=center>
<a href="https://afdian.net/@mengluorj">
<img src="https://pic1.afdiancdn.com/user/ffc6500452ed11e9994e52540025c377/avatar/ae9c5ec36b51e8314787cc19acf2d12e_w815_h815_s459.jpg" width="120" height="120">
<br>
MengLuoRJ
</a>
</td>
</tr>
</tbody>
</table>
### Backers
<table>
<tbody>
<tr>
<td align=center>
<a href="https://afdian.net/@mfwg6">
<img src="https://pic1.afdiancdn.com/user/18ad3338e58a11e9b29352540025c377/avatar/eb04b4b54975d0d229e77fbcd4220dc4_w1080_h1920_s541.jpg" width="75" height="75">
<br>
皮皮帕
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/user/68d07bf851fc11e98e5652540025c377/avatar/48538be153c8eebc3eb5cb6bc085cde9_w574_h574_s173.jpg" width="75" height="75">
<br>
dz_paji
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/user/b68f3a9aaef511e9826f52540025c377/avatar/03b244e92f9c4198672ce46e3fd7e100_w690_h690_s129.jpeg" width="75" height="75">
<br>
神奇威廉
</a>
</td>
</tr>
</tbody>
</table>
## Build From Source
Please refer to [Manual Build](https://blessing.netlify.app/build.html).
## Internationalization
Blessing Skin supports multiple languages, while currently supporting English, Simplified Chinese and Spanish.
If you are willing to contribute your translation, welcome to join [our Crowdin project](https://crowdin.com/project/blessing-skin).
## Report Bugs
Read [FAQ](https://blessing.netlify.app/faq.html) and double check if your situation doesn't suit any case mentioned there before reporting.
When reporting a problem, please attach your log file (located at `storage/logs/laravel.log`) and the information of your server where the error occured on. You should also read this [guide](https://blessing.netlify.app/report.html) before reporting a problem.
## Related Links
- [User Manual](https://blessing.netlify.app/en/)
- [Plugins Development Documentation](https://bs-plugin.netlify.app/)
## Copyright & License
MIT License
Copyright (c) 2016-present The Blessing Skin Team

View File

@ -25,6 +25,7 @@ class BsInstallCommand extends Command
if (!$this->getLaravel()->runningUnitTests()) {
// @codeCoverageIgnoreStart
$this->call('key:generate');
$this->call('jwt:secret', ['--no-interaction' => true]);
$this->call('passport:keys', ['--no-interaction' => true]);
// @codeCoverageIgnoreEnd
}

View File

@ -37,8 +37,10 @@ class UpdateCommand extends Command
protected function procedures()
{
return collect([
// this is just for testing
'0.0.1' => fn () => event('__0.0.1'),
'0.0.1' => function () {
// this is just for testing
event('__0.0.1');
},
'5.0.0' => function () {
if (option('home_pic_url') === './app/bg.jpg') {
option(['home_pic_url' => './app/bg.webp']);

View File

@ -6,7 +6,8 @@ use App\Services\Plugin;
class PluginBootFailed extends Event
{
public Plugin $plugin;
/** @var Plugin */
public $plugin;
public function __construct(Plugin $plugin)
{

View File

@ -6,7 +6,6 @@ use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Laravel\Passport\Exceptions\MissingScopeException;
use Throwable;
class Handler extends ExceptionHandler
@ -31,8 +30,6 @@ class Handler extends ExceptionHandler
if (Str::endsWith($model, 'Texture')) {
$exception = new ModelNotFoundException(trans('skinlib.non-existent'));
}
} elseif ($exception instanceof MissingScopeException) {
return json($exception->getMessage(), 403);
}
return parent::render($request, $exception);
@ -44,8 +41,12 @@ class Handler extends ExceptionHandler
'message' => $e->getMessage(),
'exception' => true,
'trace' => collect($e->getTrace())
->map(fn ($trace) => Arr::only($trace, ['file', 'line']))
->filter(fn ($trace) => Arr::has($trace, 'file'))
->map(function ($trace) {
return Arr::only($trace, ['file', 'line']);
})
->filter(function ($trace) {
return Arr::has($trace, 'file');
})
->map(function ($trace) {
$trace['file'] = str_replace(base_path().DIRECTORY_SEPARATOR, '', $trace['file']);
@ -53,9 +54,8 @@ class Handler extends ExceptionHandler
})
->filter(function ($trace) {
// @codeCoverageIgnoreStart
$isFromPlugins = !app()->runningUnitTests()
&& Str::contains($trace['file'], resolve('plugins')->getPluginsDirs()->all());
$isFromPlugins = !app()->runningUnitTests() &&
Str::contains($trace['file'], resolve('plugins')->getPluginsDirs()->all());
// @codeCoverageIgnoreEnd
return Str::startsWith($trace['file'], 'app') || $isFromPlugins;
})

View File

@ -47,22 +47,32 @@ class AdminController extends Controller
public function chartData()
{
$xAxis = Collection::times(31, fn ($i) => Carbon::today()->subDays(31 - $i)->isoFormat('l'));
$xAxis = Collection::times(31, function ($i) {
return Carbon::today()->subDays(31 - $i)->format('m-d');
});
$oneMonthAgo = Carbon::today()->subMonth();
$grouping = fn ($field) => fn ($item) => Carbon::parse($item->$field)->isoFormat('l');
$mapping = fn ($item) => count($item);
$aligning = fn ($data) => fn ($day) => $data->get($day) ?? 0;
$grouping = function ($field) {
return function ($item) use ($field) {
return substr($item->$field, 5, 5);
};
};
$mapping = function ($item) {
return count($item);
};
$aligning = function ($data) {
return function ($day) use ($data) {
return $data->get($day) ?? 0;
};
};
/** @var Collection */
$userRegistration = User::where('register_at', '>=', $oneMonthAgo)
->select('register_at')
->get()
->groupBy($grouping('register_at'))
->map($mapping);
/** @var Collection */
$textureUploads = Texture::where('upload_at', '>=', $oneMonthAgo)
->select('upload_at')
->get()
@ -86,7 +96,7 @@ class AdminController extends Controller
Request $request,
PluginManager $plugins,
Filesystem $filesystem,
Filter $filter,
Filter $filter
) {
$db = config('database.connections.'.config('database.default'));
$dbType = Arr::get([
@ -95,9 +105,9 @@ class AdminController extends Controller
'pgsql' => 'PostgreSQL',
], config('database.default'), '');
$enabledPlugins = $plugins->getEnabledPlugins()->map(fn ($plugin) => [
'title' => trans($plugin->title), 'version' => $plugin->version,
]);
$enabledPlugins = $plugins->getEnabledPlugins()->map(function ($plugin) {
return ['title' => trans($plugin->title), 'version' => $plugin->version];
});
if ($filesystem->exists(base_path('.git'))) {
$process = new \Symfony\Component\Process\Process(
@ -127,7 +137,11 @@ class AdminController extends Controller
'version' => config('app.version'),
'env' => config('app.env'),
'debug' => config('app.debug') ? trans('general.yes') : trans('general.no'),
'commit' => Str::limit($commit ?? '', 16, ''),
'commit' => Str::limit(
$commit ?? resolve(\App\Services\Webpack::class)->commit,
16,
''
),
'laravel' => app()->version(),
],
'server' => [

View File

@ -8,16 +8,16 @@ use App\Mail\ForgotPassword;
use App\Models\Player;
use App\Models\User;
use App\Rules;
use Auth;
use Blessing\Filter;
use Blessing\Rejection;
use Cache;
use Carbon\Carbon;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\URL;
use Mail;
use Session;
use URL;
use Vectorface\Whip\Whip;
class AuthController extends Controller
@ -50,7 +50,7 @@ class AuthController extends Controller
Request $request,
Rules\Captcha $captcha,
Dispatcher $dispatcher,
Filter $filter,
Filter $filter
) {
$data = $request->validate([
'identification' => 'required',
@ -151,7 +151,7 @@ class AuthController extends Controller
Request $request,
Rules\Captcha $captcha,
Dispatcher $dispatcher,
Filter $filter,
Filter $filter
) {
$can = $filter->apply('can_register', null);
if ($can instanceof Rejection) {
@ -176,8 +176,8 @@ class AuthController extends Controller
$dispatcher->dispatch('auth.registration.attempt', [$data]);
if (
option('register_with_player_name')
&& Player::where('name', $playerName)->count() > 0
option('register_with_player_name') &&
Player::where('name', $playerName)->count() > 0
) {
return json(trans('user.player.add.repeated'), 1);
}
@ -248,7 +248,7 @@ class AuthController extends Controller
Request $request,
Rules\Captcha $captcha,
Dispatcher $dispatcher,
Filter $filter,
Filter $filter
) {
$data = $request->validate([
'email' => 'required|email',
@ -370,4 +370,26 @@ class AuthController extends Controller
return redirect()->route('user.home');
}
public function jwtLogin(Request $request)
{
$token = Auth::guard('jwt')->attempt([
'email' => $request->input('email'),
'password' => $request->input('password'),
]) ?: '';
return json(compact('token'));
}
public function jwtLogout()
{
Auth::guard('jwt')->logout();
return response('', 204);
}
public function jwtRefresh()
{
return json(['token' => Auth::guard('jwt')->refresh()]);
}
}

View File

@ -4,12 +4,12 @@ namespace App\Http\Controllers;
use App\Models\Texture;
use App\Models\User;
use Auth;
use Blessing\Filter;
use Blessing\Rejection;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ClosetController extends Controller
{
@ -51,16 +51,14 @@ class ClosetController extends Controller
return $user
->closet()
->when(
$category === 'cape',
fn (Builder $query) => $query->where('type', 'cape'),
fn (Builder $query) => $query->whereIn('type', ['steve', 'alex']),
)
->when(
$request->input('q'),
fn (Builder $query, $search) => $query->like('item_name', $search)
)
->orderBy('texture_tid', 'DESC')
->when($category === 'cape', function (Builder $query) {
return $query->where('type', 'cape');
}, function (Builder $query) {
return $query->whereIn('type', ['steve', 'alex']);
})
->when($request->input('q'), function (Builder $query, $search) {
return $query->like('item_name', $search);
})
->paginate((int) $request->input('perPage', 6));
}
@ -75,7 +73,7 @@ class ClosetController extends Controller
public function add(
Request $request,
Dispatcher $dispatcher,
Filter $filter,
Filter $filter
) {
['tid' => $tid, 'name' => $name] = $request->validate([
'tid' => 'required|integer',
@ -132,7 +130,7 @@ class ClosetController extends Controller
Request $request,
Dispatcher $dispatcher,
Filter $filter,
$tid,
$tid
) {
['name' => $name] = $request->validate(['name' => 'required']);
/** @var User */

View File

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

View File

@ -2,11 +2,12 @@
namespace App\Http\Controllers;
use App\Services\Webpack;
use Illuminate\Support\Arr;
class HomeController extends Controller
{
public function index()
public function index(Webpack $webpack)
{
return view('home')
->with('user', auth()->user())
@ -14,7 +15,13 @@ class HomeController extends Controller
->with('transparent_navbar', (bool) option('transparent_navbar', false))
->with('fixed_bg', option('fixed_bg'))
->with('hide_intro', option('hide_intro'))
->with('home_pic_url', option('home_pic_url') ?: config('options.home_pic_url'));
->with('home_pic_url', option('home_pic_url') ?: config('options.home_pic_url'))
->with('home_page_css', $webpack->url('home.css'))
->with(
'home_page_css_loader',
config('app.asset.env') === 'development' ? $webpack->url('home.js') : null
)
->with('app_js', $webpack->url('app.js'));
}
public function apiRoot()
@ -25,8 +32,6 @@ class HomeController extends Controller
'Powered by Blessing Skin Server.',
'Proudly powered by Blessing Skin Server.',
'由 Blessing Skin Server 强力驱动。',
'采用 Blessing Skin Server 搭建。',
'使用 Blessing Skin Server 稳定运行。',
'自豪地采用 Blessing Skin Server。',
],
option_localized('copyright_prefer', 0)

View File

@ -45,12 +45,12 @@ class NotificationsController extends Controller
public function all()
{
return auth()->user()
->unreadNotifications
->map(fn ($notification) => [
return auth()->user()->unreadNotifications->map(function ($notification) {
return [
'id' => $notification->id,
'title' => $notification->data['title'],
]);
];
});
}
public function read($id)
@ -58,14 +58,16 @@ class NotificationsController extends Controller
$notification = auth()
->user()
->unreadNotifications
->first(fn ($notification) => $notification->id === $id);
->first(function ($notification) use ($id) {
return $notification->id === $id;
});
$notification->markAsRead();
$converter = new GithubFlavoredMarkdownConverter();
return [
'title' => $notification->data['title'],
'content' => $converter->convertToHtml($notification->data['content'] ?? '')->getContent(),
'content' => $converter->convertToHtml($notification->data['content']),
'time' => $notification->created_at->toDateTimeString(),
];
}

View File

@ -28,9 +28,7 @@ class OptionsController extends Controller
->option('1', 'Powered by Blessing Skin Server.')
->option('2', 'Proudly powered by Blessing Skin Server.')
->option('3', '由 Blessing Skin Server 强力驱动。')
->option('4', '采用 Blessing Skin Server 搭建。')
->option('5', '使用 Blessing Skin Server 稳定运行。')
->option('6', '自豪地采用 Blessing Skin Server。')
->option('4', '自豪地采用 Blessing Skin Server。')
->description();
$form->textarea('copyright_text')->rows(6)->description();
@ -163,14 +161,9 @@ class OptionsController extends Controller
->text('max_upload_file_size')->addon('KB')
->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')
->option('official', trans('options.general.player_name_rule.official'))
->option('cjk', trans('options.general.player_name_rule.cjk'))
->option('utf8', trans('options.general.player_name_rule.utf8'))
->option('custom', trans('options.general.player_name_rule.custom'));
$form->text('custom_player_name_regexp')->hint()->placeholder();

View File

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

View File

@ -23,8 +23,8 @@ class PlayersManagementController extends Controller
$currentUser = $request->user();
if (
$owner->uid !== $currentUser->uid
&& $owner->permission >= $currentUser->permission
$owner->uid !== $currentUser->uid &&
$owner->permission >= $currentUser->permission
) {
return json(trans('admin.players.no-permission'), 1)
->setStatusCode(403);
@ -44,7 +44,7 @@ class PlayersManagementController extends Controller
public function name(
Player $player,
Request $request,
Dispatcher $dispatcher,
Dispatcher $dispatcher
) {
$name = $request->validate([
'player_name' => [
@ -70,7 +70,7 @@ class PlayersManagementController extends Controller
public function owner(
Player $player,
Request $request,
Dispatcher $dispatcher,
Dispatcher $dispatcher
) {
$uid = $request->validate(['uid' => 'required|integer'])['uid'];
@ -96,7 +96,7 @@ class PlayersManagementController extends Controller
public function texture(
Player $player,
Request $request,
Dispatcher $dispatcher,
Dispatcher $dispatcher
) {
$data = $request->validate([
'tid' => 'required|integer',
@ -123,7 +123,7 @@ class PlayersManagementController extends Controller
public function delete(
Player $player,
Dispatcher $dispatcher,
Dispatcher $dispatcher
) {
$dispatcher->dispatch('player.deleting', [$player]);

View File

@ -77,7 +77,7 @@ class ReportController extends Controller
public function review(
Report $report,
Request $request,
Dispatcher $dispatcher,
Dispatcher $dispatcher
) {
$data = $request->validate([
'action' => ['required', Rule::in(['delete', 'ban', 'reject'])],
@ -88,9 +88,9 @@ class ReportController extends Controller
if ($action == 'reject') {
if (
$report->informer
&& ($score = option('reporter_score_modification', 0)) > 0
&& $report->status == Report::PENDING
$report->informer &&
($score = option('reporter_score_modification', 0)) > 0 &&
$report->status == Report::PENDING
) {
$report->informer->score -= $score;
$report->informer->save();
@ -150,11 +150,7 @@ class ReportController extends Controller
public static function returnScore($report)
{
if (
$report->status == Report::PENDING
&& ($score = option('reporter_score_modification', 0)) < 0
&& $report->informer
) {
if ($report->status == Report::PENDING && ($score = option('reporter_score_modification', 0)) < 0) {
$report->informer->score -= $score;
$report->informer->save();
}
@ -162,7 +158,7 @@ class ReportController extends Controller
public static function giveAward($report)
{
if ($report->status == Report::PENDING && $report->informer) {
if ($report->status == Report::PENDING) {
$report->informer->score += option('reporter_reward_score', 0);
$report->informer->save();
}

View File

@ -20,7 +20,7 @@ class SetupController extends Controller
Request $request,
Filesystem $filesystem,
Connection $connection,
DatabaseManager $manager,
DatabaseManager $manager
) {
if ($request->isMethod('get')) {
try {
@ -52,7 +52,7 @@ class SetupController extends Controller
try {
$manager->connection('temp')->getPdo();
} catch (\Exception $e) {
$msg = $e->getMessage();
$msg = iconv('gbk', 'utf-8', $e->getMessage());
$type = Arr::get([
'mysql' => 'MySQL/MariaDB',
'sqlite' => 'SQLite',
@ -112,6 +112,7 @@ class SetupController extends Controller
'site_name' => 'required',
]);
$artisan->call('jwt:secret', ['--no-interaction' => true]);
$artisan->call('passport:keys', ['--no-interaction' => true]);
// Create tables
@ -121,7 +122,7 @@ class SetupController extends Controller
'database/migrations',
'vendor/laravel/passport/database/migrations',
],
]);
]);
$siteUrl = url('/');
if (Str::endsWith($siteUrl, '/index.php')) {

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Models\Texture;
use App\Models\User;
use Auth;
use Blessing\Filter;
use Blessing\Rejection;
use Illuminate\Contracts\Events\Dispatcher;
@ -11,12 +12,10 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Intervention\Image\Facades\Image;
use League\CommonMark\GithubFlavoredMarkdownConverter;
use Storage;
class SkinlibController extends Controller
{
@ -69,13 +68,17 @@ class SkinlibController extends Controller
$sortBy = $sort == 'time' ? 'upload_at' : $sort;
return Texture::orderBy($sortBy, 'desc')
->when(
$type === 'skin',
fn (Builder $query) => $query->whereIn('type', ['steve', 'alex']),
fn (Builder $query) => $query->where('type', $type),
)
->when($keyword, fn (Builder $query, $keyword) => $query->like('name', $keyword))
->when($uploader, fn (Builder $query, $uploader) => $query->where('uploader', $uploader))
->when($type === 'skin', function (Builder $query) {
return $query->whereIn('type', ['steve', 'alex']);
}, function (Builder $query) use ($type) {
return $query->where('type', $type);
})
->when($keyword, function (Builder $query, $keyword) {
return $query->like('name', $keyword);
})
->when($uploader, function (Builder $query, $uploader) {
return $query->where('uploader', $uploader);
})
->when($user, function (Builder $query, User $user) {
if (!$user->isAdmin()) {
// use closure-style `where` clause to lift up SQL priority
@ -135,7 +138,7 @@ class SkinlibController extends Controller
->with('texture', $texture)
->with('grid', $grid)
->with('extra', [
'download' => (bool) option('allow_downloading_texture'),
'download' => option('allow_downloading_texture'),
'currentUid' => $user ? $user->uid : 0,
'admin' => $user && $user->isAdmin(),
'inCloset' => $user && $user->closet()->where('tid', $texture->tid)->count() > 0,
@ -183,14 +186,14 @@ class SkinlibController extends Controller
'scorePrivate' => (int) option('private_score_per_storage'),
'closetItemCost' => (int) option('score_per_closet_item'),
'award' => (int) option('score_award_per_texture'),
'contentPolicy' => $converter->convertToHtml(option_localized('content_policy'))->getContent(),
'contentPolicy' => $converter->convertToHtml(option_localized('content_policy')),
]);
}
public function handleUpload(
Request $request,
Filter $filter,
Dispatcher $dispatcher,
Dispatcher $dispatcher
) {
$file = $request->file('file');
if ($file && !$file->isValid()) {
@ -220,36 +223,24 @@ class SkinlibController extends Controller
$type = $data['type'];
$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) {
$message = trans('skinlib.upload.invalid-size', [
'type' => $type === 'cape' ? trans('general.cape') : trans('general.skin'),
'width' => $size[0],
'height' => $size[1],
]);
return json($message, 1);
}
$ratio = $size[0] / $size[1];
if ($type == 'steve' || $type == 'alex') {
if ($ratio != 2 && $ratio != 1 || $type === 'alex' && $ratio === 2) {
if ($ratio != 2 && $ratio != 1) {
$message = trans('skinlib.upload.invalid-size', [
'type' => trans('general.skin'),
'width' => $size[0],
'height' => $size[1],
]);
return json($message, 1);
}
if ($size[0] % 64 != 0 || $size[1] % 32 != 0) {
$message = trans('skinlib.upload.invalid-hd-skin', [
'type' => trans('general.skin'),
'width' => $size[0],
'height' => $size[1],
]);
return json($message, 1);
}
} elseif ($type == 'cape') {
@ -264,25 +255,17 @@ class SkinlibController extends Controller
}
}
$image = Image::make($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]);
$hash = hash_file('sha256', $file);
$hash = $filter->apply('uploaded_texture_hash', $hash, [$file]);
/** @var User */
$user = Auth::user();
$duplicated = Texture::where('hash', $hash)
->where(
fn (Builder $query) => $query->where('public', true)->orWhere('uploader', $user->uid)
)
->where(function ($query) use ($user) {
return $query->where('public', true)
->orWhere('uploader', $user->uid);
})
->first();
if ($duplicated) {
// if the texture already uploaded was set to private,
@ -290,11 +273,11 @@ class SkinlibController extends Controller
return json(trans('skinlib.upload.repeated'), 2, ['tid' => $duplicated->tid]);
}
$fileSize = ceil(strlen($sanitized) / 1024);
$size = ceil($file->getSize() / 1024);
$isPublic = is_string($data['public'])
? $data['public'] === '1'
: $data['public'];
$cost = $fileSize * (
$cost = $size * (
$isPublic
? option('score_per_storage')
: option('private_score_per_storage')
@ -305,13 +288,13 @@ class SkinlibController extends Controller
return json(trans('skinlib.upload.lack-score'), 1);
}
$dispatcher->dispatch('texture.uploading', [$image, $name, $hash]);
$dispatcher->dispatch('texture.uploading', [$file, $name, $hash]);
$texture = new Texture();
$texture->name = $name;
$texture->type = $type;
$texture->hash = $hash;
$texture->size = $fileSize;
$texture->size = $size;
$texture->public = $isPublic;
$texture->uploader = $user->uid;
$texture->likes = 1;
@ -320,14 +303,14 @@ class SkinlibController extends Controller
/** @var FilesystemAdapter */
$disk = Storage::disk('textures');
if ($disk->missing($hash)) {
$disk->put($hash, $sanitized);
$file->storePubliclyAs('', $hash, ['disk' => 'textures']);
}
$user->score -= $cost;
$user->closet()->attach($texture->tid, ['item_name' => $name]);
$user->save();
$dispatcher->dispatch('texture.uploaded', [$texture, $image]);
$dispatcher->dispatch('texture.uploaded', [$texture, $file]);
return json(trans('skinlib.upload.success', ['name' => $name]), 0, [
'tid' => $texture->tid,
@ -406,7 +389,7 @@ class SkinlibController extends Controller
Request $request,
Dispatcher $dispatcher,
Filter $filter,
Texture $texture,
Texture $texture
) {
$data = $request->validate(['name' => [
'required',
@ -436,7 +419,7 @@ class SkinlibController extends Controller
Request $request,
Dispatcher $dispatcher,
Filter $filter,
Texture $texture,
Texture $texture
) {
$data = $request->validate([
'type' => ['required', Rule::in(['steve', 'alex', 'cape'])],

View File

@ -6,11 +6,11 @@ use App\Models\Player;
use App\Models\Texture;
use App\Models\User;
use Blessing\Minecraft;
use Cache;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Facades\Image;
use Image;
use Storage;
class TextureController extends Controller
{
@ -71,8 +71,7 @@ class TextureController extends Controller
$lastModified = $disk->lastModified($hash);
// TODO: refactor
return \Intervention\Image\ImageManagerStatic::configure(['driver' => 'gd'])->make($image)
return Image::make($image)
->response($usePNG ? 'png' : 'webp', 100)
->setLastModified(Carbon::createFromTimestamp($lastModified));
}
@ -133,12 +132,8 @@ class TextureController extends Controller
return $this->avatar($minecraft, $request, $texture);
}
protected function avatar(Minecraft $minecraft, Request $request, ?Texture $texture)
protected function avatar(Minecraft $minecraft, Request $request, Texture $texture = null)
{
if (!empty($texture) && $texture->type !== 'steve' && $texture->type !== 'alex') {
return abort(422);
}
$size = (int) $request->query('size', 100);
$mode = $request->has('3d') ? '3d' : '2d';
$usePNG = $request->has('png') || !(imagetypes() & IMG_WEBP);
@ -146,8 +141,7 @@ class TextureController extends Controller
$disk = Storage::disk('textures');
if (is_null($texture) || $disk->missing($texture->hash)) {
// TODO: refactor
return \Intervention\Image\ImageManagerStatic::configure(['driver' => 'gd'])->make(resource_path("misc/textures/avatar$mode.png"))
return Image::make(resource_path("misc/textures/avatar$mode.png"))
->resize($size, $size)
->response($usePNG ? 'png' : 'webp', 100);
}
@ -167,8 +161,7 @@ class TextureController extends Controller
$lastModified = Carbon::createFromTimestamp($disk->lastModified($hash));
// TODO: refactor
return \Intervention\Image\ImageManagerStatic::configure(['driver' => 'gd'])->make($image)
return Image::make($image)
->resize($size, $size)
->response($usePNG ? 'png' : 'webp', 100)
->setLastModified($lastModified);

View File

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

View File

@ -12,7 +12,7 @@ use Illuminate\Support\Facades\Http;
class UpdateController extends Controller
{
public const SPEC = 2;
const SPEC = 2;
public function showUpdatePage()
{

View File

@ -6,16 +6,16 @@ use App\Events\UserProfileUpdated;
use App\Mail\EmailVerification;
use App\Models\Texture;
use App\Models\User;
use Auth;
use Blessing\Filter;
use Blessing\Rejection;
use Carbon\Carbon;
use Illuminate\Contracts\Events\Dispatcher;
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 Mail;
use Session;
use URL;
class UserController extends Controller
{
@ -330,9 +330,9 @@ class UserController extends Controller
}
if (
!$texture->public
&& $user->uid !== $texture->uploader
&& !$user->isAdmin()
!$texture->public &&
$user->uid !== $texture->uploader &&
!$user->isAdmin()
) {
return json(trans('skinlib.show.private'), 1);
}
@ -347,14 +347,4 @@ class UserController extends Controller
return json(trans('skinlib.non-existent'), 1);
}
}
public function toggleDarkMode()
{
/** @var User */
$user = auth()->user();
$user->is_dark_mode = !$user->is_dark_mode;
$user->save();
return response()->noContent();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -14,8 +14,8 @@ class DetectLanguagePrefer
?? $request->cookie('locale')
?? $request->getPreferredLanguage();
if (
($info = Arr::get(config('locales'), $locale))
&& ($alias = Arr::get($info, 'alias'))
($info = Arr::get(config('locales'), $locale)) &&
($alias = Arr::get($info, 'alias'))
) {
$locale = $alias;
}
@ -28,9 +28,7 @@ class DetectLanguagePrefer
/** @var Response */
$response = $next($request);
if (!in_array('api', optional($request->route())->middleware() ?? [])) {
$response->cookie('locale', $locale, 120);
}
$response->cookie('locale', $locale, 120);
return $response;
}

View File

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

View File

@ -3,28 +3,39 @@
namespace App\Http\View\Composers;
use App\Services\Translations\JavaScript;
use App\Services\Webpack;
use Blessing\Filter;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\View\View;
class FootComposer
{
protected Request $request;
/** @var Request */
protected $request;
protected JavaScript $javascript;
/** @var Webpack */
protected $webpack;
protected Dispatcher $dispatcher;
/** @var JavaScript */
protected $javascript;
protected Filter $filter;
/** @var Dispatcher */
protected $dispatcher;
/** @var Filter */
protected $filter;
public function __construct(
Request $request,
Webpack $webpack,
JavaScript $javascript,
Dispatcher $dispatcher,
Filter $filter,
Filter $filter
) {
$this->request = $request;
$this->webpack = $webpack;
$this->javascript = $javascript;
$this->dispatcher = $dispatcher;
$this->filter = $filter;
@ -39,10 +50,47 @@ class FootComposer
public function injectJavaScript(View $view)
{
$scripts = [];
$locale = app()->getLocale();
$scripts[] = [
'src' => $this->javascript->generate($locale),
];
if (Str::startsWith(config('app.asset.env'), 'dev')) {
$scripts[] = [
'src' => $this->webpack->url('style.js'),
'async' => true,
'defer' => true,
];
} elseif (!$this->request->is('/')) {
$scripts[] = [
'src' => 'https://cdn.jsdelivr.net/npm/react@16.13.1/umd/react.production.min.js',
'integrity' => 'sha256-yUhvEmYVhZ/GGshIQKArLvySDSh6cdmdcIx0spR3UP4=',
'crossorigin' => 'anonymous',
];
$scripts[] = [
'src' => 'https://cdn.jsdelivr.net/npm/react-dom@16.13.1/umd/react-dom.production.min.js',
'integrity' => 'sha256-vFt3l+illeNlwThbDUdoPTqF81M8WNSZZZt3HEjsbSU=',
'crossorigin' => 'anonymous',
];
}
$scripts[] = [
'src' => 'https://cdn.jsdelivr.net/npm/@blessing-skin/admin-lte@3.0.5/dist/admin-lte.min.js',
'integrity' => 'sha256-8RoBtV28TLYWlTMCRwqGv4NQW9bgc4jZphsQV3iLV4g=',
'crossorigin' => 'anonymous',
];
if ($this->request->is('/')) {
$scripts[] = [
'src' => $this->webpack->url('home.js'),
];
} else {
$scripts[] = [
'src' => $this->webpack->url('app.js'),
];
}
$scripts = $this->filter->apply('scripts', $scripts);
$view->with([
'i18n' => $this->javascript->generate(app()->getLocale()),
'scripts' => $scripts,
'inline_js' => option('custom_js'),
]);

View File

@ -2,6 +2,7 @@
namespace App\Http\View\Composers;
use App\Services\Webpack;
use Blessing\Filter;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Http\Request;
@ -11,17 +12,25 @@ use Illuminate\View\View;
class HeadComposer
{
protected Dispatcher $dispatcher;
/** @var Webpack */
protected $webpack;
protected Request $request;
/** @var Dispatcher */
protected $dispatcher;
protected Filter $filter;
/** @var Request */
protected $request;
/** @var Filter */
protected $filter;
public function __construct(
Webpack $webpack,
Dispatcher $dispatcher,
Request $request,
Filter $filter,
Filter $filter
) {
$this->webpack = $webpack;
$this->dispatcher = $dispatcher;
$this->request = $request;
$this->filter = $filter;
@ -80,6 +89,37 @@ class HeadComposer
public function injectStyles(View $view)
{
$links = [];
$links[] = [
'rel' => 'stylesheet',
'href' => 'https://cdn.jsdelivr.net/npm/@blessing-skin/admin-lte@3.0.5/dist/admin-lte.min.css',
'integrity' => 'sha256-zG+BobiRcnWbKPGtQ++LoogyH9qSHjhBwhbFFP8HbDM=',
'crossorigin' => 'anonymous',
];
$links[] = [
'rel' => 'preload',
'as' => 'font',
'href' => 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.13.0/webfonts/fa-solid-900.woff2',
'crossorigin' => 'anonymous',
];
$links[] = [
'rel' => 'preload',
'as' => 'font',
'href' => 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.13.0/webfonts/fa-regular-400.woff2',
'crossorigin' => 'anonymous',
];
$links[] = [
'rel' => 'stylesheet',
'href' => 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.13.0/css/all.min.css',
'integrity' => 'sha256-h20CPZ0QyXlBuAw7A+KluUYx/3pK+c7lYEpqLTlxjYQ=',
'crossorigin' => 'anonymous',
];
if (!$this->request->is('/') && config('app.asset.env') !== 'development') {
$links[] = [
'rel' => 'stylesheet',
'href' => $this->webpack->url('style.css'),
'crossorigin' => 'anonymous',
];
}
$links = $this->filter->apply('head_links', $links);
$view->with('links', $links);
$view->with('inline_css', option('custom_css'));

View File

@ -8,7 +8,8 @@ use Illuminate\View\View;
class LanguagesMenuComposer
{
protected Request $request;
/** @var Request */
protected $request;
public function __construct(Request $request)
{
@ -21,7 +22,9 @@ class LanguagesMenuComposer
$path = $this->request->path();
$langs = collect(config('locales'))
->reject(fn ($locale) => Arr::has($locale, 'alias'))
->reject(function ($locale) {
return Arr::has($locale, 'alias');
})
->map(function ($locale, $id) use ($query, $path) {
$query = array_merge($query, ['lang' => $id]);
$locale['url'] = url($path.'?'.http_build_query($query));

View File

@ -3,7 +3,6 @@
namespace App\Http\View\Composers;
use App\Events;
use App\Services\Plugin;
use App\Services\PluginManager;
use Blessing\Filter;
use Illuminate\Http\Request;
@ -12,9 +11,11 @@ use Illuminate\View\View;
class SideMenuComposer
{
protected Request $request;
/** @var Request */
protected $request;
protected Filter $filter;
/** @var Filter */
protected $filter;
public function __construct(Request $request, Filter $filter)
{
@ -43,7 +44,9 @@ class SideMenuComposer
$menu = $menu[$type];
$menu = $this->filter->apply('side_menu', $menu, [$type]);
$view->with('items', array_map(fn ($item) => $this->transform($item), $menu));
$view->with('items', array_map(function ($item) {
return $this->transform($item);
}, $menu));
}
public function transform(array $item): array
@ -63,10 +66,9 @@ class SideMenuComposer
}
if (Arr::has($item, 'children')) {
$item['children'] = array_map(
fn ($item) => $this->transform($item),
$item['children'],
);
$item['children'] = array_map(function ($item) {
return $this->transform($item);
}, $item['children']);
}
$item['classes'] = $classes;
@ -80,7 +82,9 @@ class SideMenuComposer
if (Arr::get($item, 'id') === 'plugin-configs') {
$pluginConfigs = resolve(PluginManager::class)
->getEnabledPlugins()
->filter(fn (Plugin $plugin) => $plugin->hasConfig())
->filter(function ($plugin) {
return $plugin->hasConfig();
})
->map(function ($plugin) {
return [
'title' => trans($plugin->title),
@ -91,7 +95,7 @@ class SideMenuComposer
// Don't display this menu item when no plugin config is available
if ($pluginConfigs->isNotEmpty()) {
array_push($item['children'], ...$pluginConfigs->values()->all());
$item['children'] = array_merge($item['children'], $pluginConfigs->values()->all());
return $item;
}

View File

@ -8,9 +8,11 @@ use Illuminate\View\View;
class UserMenuComposer
{
protected Request $request;
/** @var Request */
protected $request;
protected Filter $filter;
/** @var Filter */
protected $filter;
public function __construct(Request $request, Filter $filter)
{
@ -23,34 +25,8 @@ class UserMenuComposer
$user = auth()->user();
$avatarUrl = route('avatar.texture', ['tid' => $user->avatar, 'size' => 36], false);
$avatar = $this->filter->apply('user_avatar', $avatarUrl, [$user]);
$avatarPNG = route(
'avatar.texture',
['tid' => $user->avatar, 'size' => 36, 'png' => true],
false
);
$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([
'user' => $user,
'avatar' => $avatar,
'avatar_png' => $avatarPNG,
'menu' => $menuItems,
]);
$view->with(compact('user', 'avatar', 'cli'));
}
}

View File

@ -9,9 +9,11 @@ use Illuminate\View\View;
class UserPanelComposer
{
protected Dispatcher $dispatcher;
/** @var Dispatcher */
protected $dispatcher;
protected Filter $filter;
/** @var Filter */
protected $filter;
public function __construct(Dispatcher $dispatcher, Filter $filter)
{
@ -25,12 +27,6 @@ class UserPanelComposer
$user = auth()->user();
$avatarUrl = route('avatar.texture', ['tid' => $user->avatar, 'size' => 45], false);
$avatar = $this->filter->apply('user_avatar', $avatarUrl, [$user]);
$avatarPNG = route(
'avatar.texture',
['tid' => $user->avatar, 'size' => 45, 'png' => true],
false
);
$avatarPNG = $this->filter->apply('user_avatar', $avatarPNG, [$user]);
$badges = [];
if ($user->isAdmin()) {
@ -39,11 +35,6 @@ class UserPanelComposer
$this->dispatcher->dispatch(new \App\Events\RenderingBadges($badges));
$badges = $this->filter->apply('user_badges', $badges, [$user]);
$view->with([
'user' => $user,
'avatar' => $avatar,
'avatar_png' => $avatarPNG,
'badges' => $badges,
]);
$view->with(compact('user', 'avatar', 'badges'));
}
}

View File

@ -7,7 +7,8 @@ use Symfony\Component\Finder\SplFileInfo;
class CleanUpFrontEndLocaleFiles
{
protected Filesystem $filesystem;
/** @var Filesystem */
protected $filesystem;
public function __construct(Filesystem $filesystem)
{

View File

@ -7,7 +7,8 @@ use Illuminate\Filesystem\Filesystem;
class CopyPluginAssets
{
protected Filesystem $filesystem;
/** @var Filesystem */
protected $filesystem;
public function __construct(Filesystem $filesystem)
{

View File

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

View File

@ -7,7 +7,8 @@ use Illuminate\Http\Request;
class SetAppLocale
{
protected Request $request;
/** @var Request */
protected $request;
public function __construct(Request $request)
{

View File

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

View File

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

View File

@ -3,8 +3,8 @@
namespace App\Models;
use App\Events\PlayerProfileUpdated;
use App\Models;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Lorisleiva\LaravelSearchString\Concerns\SearchString;
@ -23,7 +23,6 @@ use Lorisleiva\LaravelSearchString\Concerns\SearchString;
*/
class Player extends Model
{
use HasFactory;
use SearchString;
public const CREATED_AT = null;
@ -56,17 +55,17 @@ class Player extends Model
public function user()
{
return $this->belongsTo(User::class, 'uid');
return $this->belongsTo(Models\User::class, 'uid');
}
public function skin()
{
return $this->belongsTo(Texture::class, 'tid_skin');
return $this->belongsTo(Models\Texture::class, 'tid_skin');
}
public function cape()
{
return $this->belongsTo(Texture::class, 'tid_cape');
return $this->belongsTo(Models\Texture::class, 'tid_cape');
}
public function getModelAttribute()

View File

@ -1,19 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property string $name
* @property string $description
*/
class Scope extends Model
{
public $timestamps = false;
protected $fillable = [
'name', 'description',
];
}

View File

@ -4,7 +4,6 @@ namespace App\Models;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
@ -24,8 +23,6 @@ use Illuminate\Support\Carbon;
*/
class Texture extends Model
{
use HasFactory;
public $primaryKey = 'tid';
public const CREATED_AT = 'upload_at';
public const UPDATED_AT = null;

View File

@ -4,11 +4,11 @@ namespace App\Models;
use App\Models\Concerns\HasPassword;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
use Lorisleiva\LaravelSearchString\Concerns\SearchString;
use Tymon\JWTAuth\Contracts\JWTSubject;
/**
* @property int $uid
@ -20,7 +20,6 @@ use Lorisleiva\LaravelSearchString\Concerns\SearchString;
* @property int $score
* @property int $permission
* @property string $ip
* @property bool $is_dark_mode
* @property string $last_sign_at
* @property string $register_at
* @property bool $verified
@ -28,18 +27,17 @@ use Lorisleiva\LaravelSearchString\Concerns\SearchString;
* @property Collection $players
* @property Collection $closet
*/
class User extends Authenticatable
class User extends Authenticatable implements JWTSubject
{
use Notifiable;
use HasFactory;
use HasPassword;
use HasApiTokens;
use SearchString;
public const BANNED = -1;
public const NORMAL = 0;
public const ADMIN = 1;
public const SUPER_ADMIN = 2;
const BANNED = -1;
const NORMAL = 0;
const ADMIN = 1;
const SUPER_ADMIN = 2;
protected $primaryKey = 'uid';
public $timestamps = false;
@ -54,7 +52,6 @@ class User extends Authenticatable
'avatar' => 'integer',
'permission' => 'integer',
'verified' => 'bool',
'is_dark_mode' => 'bool',
];
protected $hidden = ['password', 'remember_token'];
@ -67,7 +64,6 @@ class User extends Authenticatable
'last_sign_at' => ['date' => true],
'register_at' => ['date' => true],
'verified' => ['boolean' => true],
'is_dark_mode' => ['boolean' => true],
];
public function isAdmin(): bool
@ -112,4 +108,14 @@ class User extends Authenticatable
{
return $this->uid;
}
public function getJWTIdentifier()
{
return $this->getKey();
}
public function getJWTCustomClaims()
{
return [];
}
}

View File

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

View File

@ -1,33 +0,0 @@
<?php
namespace App\Observers;
use App\Models\Scope;
use Illuminate\Support\Facades\Cache;
class ScopeObserver
{
/**
* Handle the Scope "saved" event.
*/
public function saved(): void
{
$this->refreshCachedScopes();
}
/**
* Handle the Scope "deleted" event.
*/
public function deleted(): void
{
$this->refreshCachedScopes();
}
protected function refreshCachedScopes(): void
{
Cache::forget('scopes');
Cache::rememberForever('scopes', function () {
return Scope::pluck('description', 'name')->toArray();
});
}
}

View File

@ -4,23 +4,20 @@ namespace App\Providers;
use App\Services;
use Illuminate\Http\Request;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
public function register()
{
$this->app->singleton('cipher', 'App\Services\Cipher\\'.config('secure.cipher'));
$this->app->singleton(Services\Option::class);
$this->app->alias(Services\Option::class, 'options');
$this->app->singleton(Services\Webpack::class);
}
public function boot(Request $request): void
public function boot(Request $request)
{
Paginator::useBootstrap();
$this->configureUrlGenerator($request);
}
@ -36,8 +33,8 @@ class AppServiceProvider extends ServiceProvider
// Replace HTTP_HOST with site_url set in options,
// to prevent CDN source problems.
if (URL::isValidUrl($rootUrl)) {
URL::forceRootUrl($rootUrl);
if ($this->app['url']->isValidUrl($rootUrl)) {
$this->app['url']->forceRootUrl($rootUrl);
}
}
@ -53,7 +50,7 @@ class AppServiceProvider extends ServiceProvider
|| $request->server('HTTP_X_FORWARDED_SSL') === 'on';
if (option('force_ssl') || $isRequestSecure) {
URL::forceScheme('https');
$this->app['url']->forceScheme('https');
}
}
}

View File

@ -1,62 +0,0 @@
<?php
namespace App\Providers;
use App\Models\Scope;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Laravel\Passport\Passport;
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
*
* @var array<class-string, class-string>
*/
protected $policies = [
];
/**
* Register any authentication / authorization services.
*/
public function boot(): void
{
$defaultScopes = [
'User.Read' => 'auth.oauth.scope.user.read',
'Notification.Read' => 'auth.oauth.scope.notification.read',
'Notification.ReadWrite' => 'auth.oauth.scope.notification.readwrite',
'Player.Read' => 'auth.oauth.scope.player.read',
'Player.ReadWrite' => 'auth.oauth.scope.player.readwrite',
'Closet.Read' => 'auth.oauth.scope.closet.read',
'Closet.ReadWrtie' => 'auth.oauth.scope.closet.readwrite',
'UsersManagement.Read' => 'auth.oauth.scope.users-management.read',
'UsersManagement.ReadWrite' => 'auth.oauth.scope.users-management.readwrite',
'PlayersManagement.Read' => 'auth.oauth.scope.players-management.read',
'PlayersManagement.ReadWrite' => 'auth.oauth.scope.players-management.readwrite',
'ClosetManagement.Read' => 'auth.oauth.scope.closet-management.read',
'ClosetManagement.ReadWrite' => 'auth.oauth.scope.closet-management.readwrite',
'ReportsManagement.Read' => 'auth.oauth.scope.reports-management.read',
'ReportsManagement.ReadWrite' => 'auth.oauth.scope.reports-management.readwrite',
];
/*
* 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::setDefaultScope(['User.Read']);
}
}

View File

@ -3,17 +3,11 @@
namespace App\Providers;
use App\Listeners;
use App\Models\Scope;
use App\Observers\ScopeObserver;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* The event to listener mappings for the application.
*
* @var array<class-string, array<int, class-string>>
*/
// The event listener mappings for the application.
protected $listen = [
'App\Events\PluginWasEnabled' => [
Listeners\CopyPluginAssets::class,
@ -45,12 +39,4 @@ class EventServiceProvider extends ServiceProvider
Listeners\SetAppLocale::class,
],
];
/**
* Register any events for your application.
*/
public function boot(): void
{
Scope::observe(ScopeObserver::class);
}
}

View File

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

View File

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

View File

@ -3,12 +3,14 @@
namespace App\Providers;
use App\Http\View\Composers;
use Illuminate\Support\Facades\View;
use App\Services\Webpack;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use View;
class ViewServiceProvider extends ServiceProvider
{
public function boot(): void
public function boot(Webpack $webpack)
{
View::composer([
'home',
@ -22,7 +24,6 @@ class ViewServiceProvider extends ServiceProvider
'site_name' => option_localized('site_name'),
'navbar_color' => $color,
'color_mode' => in_array($color, $lightColors) ? 'light' : 'dark',
'dark_mode' => (bool) optional(auth()->user())->is_dark_mode,
'locale' => str_replace('_', '-', app()->getLocale()),
]);
});
@ -47,11 +48,7 @@ class ViewServiceProvider extends ServiceProvider
View::composer('shared.user-menu', Composers\UserMenuComposer::class);
View::composer('shared.sidebar', function ($view) {
$isDarkMode = (bool) optional(auth()->user())->is_dark_mode;
$color = option('sidebar_color');
$color = $isDarkMode ? str_replace('light', 'dark', $color) : $color;
$view->with('sidebar_color', $color);
$view->with('sidebar_color', option('sidebar_color'));
});
View::composer('shared.side-menu', Composers\SideMenuComposer::class);
@ -69,14 +66,14 @@ class ViewServiceProvider extends ServiceProvider
View::composer('shared.foot', Composers\FootComposer::class);
View::composer('shared.dark-mode', function ($view) {
$view->with([
'dark_mode' => (bool) optional(auth()->user())->is_dark_mode,
]);
});
View::composer('assets.*', function ($view) {
$view->with('cdn_base', option('cdn_address', ''));
View::composer(['errors.*', 'setup.*'], function ($view) use ($webpack) {
// @codeCoverageIgnoreStart
if (Str::startsWith(config('app.asset.env'), 'dev')) {
$view->with(['scripts' => [$webpack->url('spectre.js')]]);
} else {
$view->with('styles', [$webpack->url('spectre.css')]);
}
// @codeCoverageIgnoreEnd
});
}
}

View File

@ -21,9 +21,6 @@ class PlayerName implements Rule
$regexp = '/^[A-Za-z0-9_§\x{4e00}-\x{9fff}]+$/u';
break;
case 'utf8':
return mb_check_encoding($value, 'UTF-8') && !preg_match('/\s/', $value);
case 'custom':
$regexp = option('custom_player_name_regexp') ?: $regexp;
break;

View File

@ -8,10 +8,10 @@ use App\Events;
use App\Notifications;
use Blessing\Filter;
use Closure;
use Event;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Str;
use Notification;
class Hook
{
@ -66,7 +66,9 @@ class Hook
$urls = collect($urls);
$pages = collect($pages);
resolve(Filter::class)->add('head_links', function ($links) use ($urls, $pages) {
$matched = $pages->some(fn ($page) => request()->is($page));
$matched = $pages->some(function ($page) {
return request()->is($page);
});
if ($matched) {
$urls->each(function ($url) use (&$links) {
$links[] = [
@ -86,7 +88,9 @@ class Hook
$urls = collect($urls);
$pages = collect($pages);
resolve(Filter::class)->add('scripts', function ($scripts) use ($urls, $pages) {
$matched = $pages->some(fn ($page) => request()->is($page));
$matched = $pages->some(function ($page) {
return request()->is($page);
});
if ($matched) {
$urls->each(function ($url) use (&$scripts) {
$scripts[] = ['src' => $url, 'crossorigin' => 'anonymous'];

View File

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

View File

@ -2,10 +2,10 @@
namespace App\Services;
use App\Services\Facades\Option;
use BadMethodCallException;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Option;
use ReflectionClass;
/**
@ -21,7 +21,7 @@ class OptionForm
* Pass this value to tell generator to
* load text from language files automatically.
*/
public const AUTO_DETECT = 0x97AB1;
const AUTO_DETECT = 0x97ab1;
protected $id;
protected $title;
@ -38,7 +38,7 @@ class OptionForm
protected $hookBefore;
protected $hookAfter;
protected $alwaysCallback;
protected $alwaysCallback = null;
protected $renderWithoutTable = false;
protected $renderInputTagsOnly = false;
@ -59,7 +59,7 @@ class OptionForm
}
/**
* @throws BadMethodCallException
* @throws \BadMethodCallException
*/
public function __call(string $method, array $params): OptionFormItem
{
@ -105,6 +105,7 @@ class OptionForm
* Add a piece of data to the option form.
*
* @param string|array $key
* @param mixed $value
*/
public function with($key, $value = null): self
{
@ -203,7 +204,7 @@ class OptionForm
/**
* 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();
$allPostData = $request->all();
@ -328,7 +329,7 @@ class OptionForm
$this->assignValues();
return view('forms.form')
->with(get_object_vars($this))
->with(array_merge(get_object_vars($this)))
->render();
}
@ -351,7 +352,7 @@ class OptionFormItem
public $format;
public $value;
public $value = null;
public $disabled;

View File

@ -9,17 +9,25 @@ use Illuminate\Support\Str;
class Plugin
{
public const README_FILES = [
const README_FILES = [
'README.md',
'readme.md',
'README.MD',
];
/** The full path of this plugin. */
protected string $path;
/**
* The full path of this plugin.
*
* @var string
*/
protected $path;
/** package.json of the package. */
protected array $manifest;
/**
* package.json of the package.
*
* @var array
*/
protected $manifest;
protected $enabled = false;
@ -53,10 +61,9 @@ class Plugin
public function getReadme()
{
return Arr::first(
self::README_FILES,
fn ($filename) => file_exists($this->path.'/'.$filename)
);
return Arr::first(self::README_FILES, function ($filename) {
return file_exists($this->path.'/'.$filename);
});
}
public function hasConfig(): bool

View File

@ -16,27 +16,35 @@ use Illuminate\Support\Str;
class PluginManager
{
protected bool $booted = false;
/** @var bool */
protected $booted = false;
protected Application $app;
/** @var Application */
protected $app;
protected Option $option;
/** @var Option */
protected $option;
protected Dispatcher $dispatcher;
/** @var Dispatcher */
protected $dispatcher;
protected Filesystem $filesystem;
/** @var Filesystem */
protected $filesystem;
protected ClassLoader $loader;
/** @var ClassLoader */
protected $loader;
protected ?Collection $plugins;
/** @var Collection|null */
protected $plugins;
protected Collection $enabled;
/** @var Collection */
protected $enabled;
public function __construct(
Application $app,
Option $option,
Dispatcher $dispatcher,
Filesystem $filesystem,
Filesystem $filesystem
) {
$this->app = $app;
$this->option = $option;
@ -48,20 +56,28 @@ class PluginManager
public function all(): Collection
{
if (isset($this->plugins)) {
if (filled($this->plugins)) {
return $this->plugins;
}
$this->enabled = collect(json_decode($this->option->get('plugins_enabled', '[]'), true))
->reject(fn ($item) => is_string($item))
->mapWithKeys(fn ($item) => [$item['name'] => ['version' => $item['version']]]);
->reject(function ($item) {
return is_string($item);
})
->mapWithKeys(function ($item) {
return [$item['name'] => ['version' => $item['version']]];
});
$plugins = collect();
$versionChanged = [];
$this->getPluginsDirs()
->flatMap(fn ($dir) => $this->filesystem->directories($dir))
->flatMap(function ($directory) {
return $this->filesystem->directories($directory);
})
->unique()
->filter(fn ($dir) => $this->filesystem->exists($dir.DIRECTORY_SEPARATOR.'package.json'))
->filter(function ($directory) {
return $this->filesystem->exists($directory.DIRECTORY_SEPARATOR.'package.json');
})
->each(function ($directory) use (&$plugins, &$versionChanged) {
$manifest = json_decode(
$this->filesystem->get($directory.DIRECTORY_SEPARATOR.'package.json'),
@ -110,12 +126,18 @@ class PluginManager
return;
}
$this->all()->each(fn ($plugin) => $this->loadViewsAndTranslations($plugin));
$this->all()->each(function ($plugin) {
$this->loadViewsAndTranslations($plugin);
});
$enabled = $this->getEnabledPlugins();
$enabled->each(fn ($plugin) => $this->registerPlugin($plugin));
$enabled->each(function ($plugin) {
$this->registerPlugin($plugin);
});
$this->loader->register();
$enabled->each(fn ($plugin) => $this->bootPlugin($plugin));
$enabled->each(function ($plugin) {
$this->bootPlugin($plugin);
});
$this->registerLifecycleHooks();
$this->booted = true;
@ -302,7 +324,9 @@ class PluginManager
public function getEnabledPlugins(): Collection
{
return $this->all()->filter(fn ($plugin) => $plugin->isEnabled());
return $this->all()->filter(function ($plugin) {
return $plugin->isEnabled();
});
}
/**
@ -310,13 +334,9 @@ class PluginManager
*/
protected function saveEnabled()
{
$this->option->set(
'plugins_enabled',
$this->enabled
->map(fn ($info, $name) => array_merge(['name' => $name], $info))
->values()
->toJson()
);
$this->option->set('plugins_enabled', $this->enabled->map(function ($info, $name) {
return array_merge(compact('name'), $info);
})->values()->toJson());
}
public function getUnsatisfied(Plugin $plugin)
@ -366,7 +386,7 @@ class PluginManager
*/
public function formatUnresolved(
Collection $unsatisfied,
Collection $conflicts,
Collection $conflicts
): array {
$unsatisfied = $unsatisfied->map(function ($detail, $name) {
if ($name === 'blessing-skin-server') {
@ -399,7 +419,9 @@ class PluginManager
$config = config('plugins.directory');
if ($config) {
return collect(preg_split('/,\s*/', $config))
->map(fn ($directory) => realpath($directory) ?: $directory);
->map(function ($directory) {
return realpath($directory) ?: $directory;
});
} else {
return collect([base_path('plugins')]);
}

View File

@ -9,18 +9,21 @@ use Illuminate\Filesystem\Filesystem;
class JavaScript
{
protected Filesystem $filesystem;
/** @var Filesystem */
protected $filesystem;
protected Repository $cache;
/** @var Repository */
protected $cache;
protected PluginManager $plugins;
/** @var PluginManager */
protected $plugins;
protected $prefix = 'front-end-trans-';
public function __construct(
Filesystem $filesystem,
Repository $cache,
PluginManager $plugins,
PluginManager $plugins
) {
$this->filesystem = $filesystem;
$this->cache = $cache;
@ -31,10 +34,16 @@ class JavaScript
{
$plugins = $this->plugins->getEnabledPlugins();
$sourceFiles = $plugins
->map(fn (Plugin $plugin) => $plugin->getPath()."/lang/$locale/front-end.yml")
->filter(fn ($path) => $this->filesystem->exists($path));
->map(function (Plugin $plugin) use ($locale) {
return $plugin->getPath()."/lang/$locale/front-end.yml";
})
->filter(function ($path) {
return $this->filesystem->exists($path);
});
$sourceFiles->push(resource_path("lang/$locale/front-end.yml"));
$sourceModified = $sourceFiles->max(fn ($path) => $this->filesystem->lastModified($path));
$sourceModified = $sourceFiles->max(function ($path) {
return $this->filesystem->lastModified($path);
});
$compiled = public_path("lang/$locale.js");
$compiledModified = (int) $this->cache->get($this->prefix.$locale, 0);
@ -42,7 +51,10 @@ class JavaScript
if ($sourceModified > $compiledModified || !$this->filesystem->exists($compiled)) {
$translations = trans('front-end');
foreach ($plugins as $plugin) {
$translations[$plugin->name] = trans($plugin->namespace.'::front-end');
$translations = array_merge(
$translations,
[$plugin->name => trans($plugin->namespace.'::front-end')]
);
}
$content = 'blessing.i18n = '.json_encode($translations, JSON_UNESCAPED_UNICODE);

View File

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

View File

@ -7,7 +7,8 @@ use Symfony\Component\Yaml\Yaml as YamlParser;
class Yaml
{
protected Repository $cache;
/** @var Repository */
protected $cache;
protected $prefix = 'yaml-trans-';

View File

@ -10,9 +10,11 @@ use ZipArchive;
class Unzip
{
protected Filesystem $filesystem;
/** @var Filesystem */
protected $filesystem;
protected ZipArchive $zipper;
/** @var ZipArchive */
protected $zipper;
public function __construct(Filesystem $filesystem, ZipArchive $zipper)
{

53
app/Services/Webpack.php Normal file
View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace App\Services;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class Webpack
{
protected $manifest = [];
/** @var Option */
protected $options;
protected $urlGenerator;
public function __construct(
Filesystem $filesystem,
Option $options,
UrlGenerator $urlGenerator
) {
$path = public_path('app/manifest.json');
if ($filesystem->exists($path)) {
$this->manifest = json_decode($filesystem->get($path), true);
}
$this->options = $options;
$this->urlGenerator = $urlGenerator;
}
public function __get(string $path)
{
return Arr::get($this->manifest, $path, '');
}
public function url(string $path): string
{
if (Str::startsWith(config('app.asset.env'), 'dev')) {
$root = config('app.asset.url').':8080';
return $this->urlGenerator->assetFrom($root, $path);
} else {
$path = $this->$path;
$cdn = $this->options->get('cdn_address');
return $this->urlGenerator->assetFrom($cdn, "/app/$path");
}
}
}

View File

@ -52,11 +52,21 @@ if (!function_exists('option')) {
* 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.
*
* @param array|string $key
* @param mixed $default
* @param bool $raw return raw value without convertion
*
* @return mixed
*/
function option(string|array $key, mixed $default = null, bool $raw = false)
function option($key = null, $default = null, $raw = false)
{
$options = app('options');
if (is_null($key)) {
return $options;
}
if (is_array($key)) {
$options->set($key);

View File

@ -29,7 +29,27 @@ ini_set('display_errors', true);
file_put_contents($envPath, preg_replace('/APP_KEY\s*=\s*/', 'APP_KEY='.$key."\n\n", $envFile));
}
$requiredVersion = '8.1.0';
$requiredFunctions = ['symlink', 'readlink', 'putenv', 'realpath'];
$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 = '7.2.5';
preg_match('/(\d+\.\d+\.\d+)/', PHP_VERSION, $matches);
$version = $matches[1];
if (version_compare($version, $requiredVersion, '<')) {
@ -51,7 +71,6 @@ ini_set('display_errors', true);
'json',
'fileinfo',
'zip',
'imagick',
],
'write_permission' => [
'bootstrap/cache',

View File

@ -3,10 +3,9 @@
"description": "A web application brings your custom skins back in offline Minecraft servers.",
"license": "MIT",
"require": {
"php": "^8.1",
"php": ">=7.2.5",
"ext-ctype": "*",
"ext-gd": "*",
"ext-imagick": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
@ -16,43 +15,46 @@
"ext-zip": "*",
"blessing/filter": "^1.0",
"blessing/rejection": "^1.0",
"blessing/texture-renderer": "^0.2",
"blessing/texture-renderer": "^0.1.1",
"composer/ca-bundle": "^1.2",
"composer/semver": "^3.2",
"doctrine/dbal": "^3.0",
"doctrine/inflector": "^2.0",
"spatie/laravel-ignition": "^2.0",
"composer/semver": "^1.5",
"doctrine/dbal": "^2.10",
"doctrine/inflector": "^1.3",
"facade/ignition": "^2.0",
"gregwar/captcha": "1.*",
"guzzlehttp/guzzle": "^7.0",
"intervention/image": "^2.7",
"laravel/framework": "^10.0",
"laravel/passport": "^11.0",
"lorisleiva/laravel-search-string": "^1.0",
"intervention/image": "^2.5",
"laravel/framework": "^7.0",
"laravel/passport": "^9.2",
"lorisleiva/laravel-search-string": "^0.1.6",
"nesbot/carbon": "^2.0",
"nunomaduro/collision": "^7.0",
"rcrowe/twigbridge": "^0.14",
"spatie/laravel-translation-loader": "^2.7",
"symfony/process": "^6.0",
"nunomaduro/collision": "^4.1",
"rcrowe/twigbridge": "^0.11.3",
"spatie/laravel-translation-loader": "^2.6",
"symfony/process": "^5.0",
"symfony/yaml": "^5.0",
"twig/twig": "^3.0",
"vectorface/whip": "^0.4.0"
"twig/twig": "^2.11",
"tymon/jwt-auth": "dev-develop",
"vectorface/whip": "^0.3.2"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.5",
"barryvdh/laravel-ide-helper": "^2.10",
"fakerphp/faker": "^1.13",
"friendsofphp/php-cs-fixer": "^3.13",
"laravel/browser-kit-testing": "^7.0",
"laravel/tinker": "^2.4",
"mockery/mockery": "^1.4",
"phpunit/phpunit": "^10.0",
"symfony/css-selector": "^6.2",
"symfony/dom-crawler": "^6.2"
"barryvdh/laravel-debugbar": "^3.2",
"fzaninotto/faker": "~1.9",
"laravel/browser-kit-testing": "^6.0",
"laravel/tinker": "^2.2",
"mockery/mockery": "1.3.*",
"phpdocumentor/reflection-docblock": "^5.1",
"phpunit/phpunit": "^8.5",
"symfony/css-selector": "^5.0",
"symfony/dom-crawler": "^5.0"
},
"autoload": {
"classmap": [
"app/Models",
"database"
],
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/"
"App\\": "app/"
},
"files": [
"app/helpers.php"
@ -71,7 +73,6 @@
]
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true
},

20799
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,5 @@
<?php
use Illuminate\Support\Facades\Facade;
use Illuminate\Support\ServiceProvider;
return [
/*
|--------------------------------------------------------------------------
@ -12,8 +9,7 @@ return [
| Version of Blessing Skin Server.
|
*/
'version' => '6.0.2',
'version' => '5.2.0',
/*
|--------------------------------------------------------------------------
@ -23,24 +19,12 @@ return [
| Where to get information of new versions.
|
*/
'update_source' => env(
'UPDATE_SOURCE',
'https://dev.azure.com/blessing-skin/51010f6d-9f99-40f1-a262-0a67f788df32/_apis/git/'.
'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'),
/*
@ -82,6 +66,19 @@ return [
'url' => env('APP_URL', 'http://localhost'),
/*
|--------------------------------------------------------------------------
| Assets
|--------------------------------------------------------------------------
|
| This is related to front-end assets. The asset URL is only available for
| development, not for production.
*/
'asset' => [
'env' => env('ASSET_ENV', 'production'),
'url' => env('ASSET_URL', 'http://localhost'),
],
/*
|--------------------------------------------------------------------------
| Application Timezone
@ -149,24 +146,6 @@ return [
'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
@ -178,21 +157,39 @@ return [
|
*/
'providers' => ServiceProvider::defaultProviders()->merge([
'providers' => [
/*
* Package Service Providers...
* Laravel Framework 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...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\PluginServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\ViewServiceProvider::class,
])->toArray(),
],
/*
|--------------------------------------------------------------------------
@ -205,7 +202,44 @@ return [
|
*/
'aliases' => Facade::defaultAliases()->merge([
'Option' => App\Services\Facades\Option::class,
])->toArray(),
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
'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,6 +1,7 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
@ -102,4 +103,5 @@ return [
'expire' => 60,
],
],
];

View File

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

View File

@ -3,6 +3,7 @@
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
@ -12,6 +13,9 @@ return [
| using this caching library. This connection is used when another is
| not explicitly specified when executing a given caching function.
|
| Supported: "apc", "array", "database", "file",
| "memcached", "redis", "dynamodb"
|
*/
'default' => env('CACHE_DRIVER', 'file'),
@ -25,12 +29,10 @@ return [
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
| Supported drivers: "apc", "array", "database", "file",
| "memcached", "redis", "dynamodb", "octane", "null"
|
*/
'stores' => [
'apc' => [
'driver' => 'apc',
],
@ -44,13 +46,11 @@ return [
'driver' => 'database',
'table' => 'cache',
'connection' => null,
'lock_connection' => null,
],
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
'lock_path' => storage_path('framework/cache/data'),
],
'memcached' => [
@ -75,7 +75,6 @@ return [
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
'lock_connection' => 'default',
],
'dynamodb' => [
@ -87,9 +86,6 @@ return [
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
'octane' => [
'driver' => 'octane',
],
],
/*
@ -97,11 +93,12 @@ return [
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing the APC, database, memcached, Redis, or DynamoDB cache
| stores there might be other applications using the same cache. For
| that reason, you may prefix every cache key to avoid collisions.
| When utilizing a RAM based store such as APC or Memcached, there might
| be other applications utilizing the same cache. So, we'll specify a
| value to get prefixed to all our keys so we can 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,6 +3,7 @@
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
@ -33,6 +34,7 @@ return [
*/
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DATABASE_URL'),
@ -54,7 +56,7 @@ return [
'collation' => 'utf8mb4_unicode_ci',
'prefix' => env('DB_PREFIX', ''),
'prefix_indexes' => true,
'strict' => true,
'strict' => false,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
@ -72,7 +74,7 @@ return [
'charset' => 'utf8',
'prefix' => env('DB_PREFIX', ''),
'prefix_indexes' => true,
'search_path' => 'public',
'schema' => 'public',
'sslmode' => 'prefer',
],
@ -87,9 +89,8 @@ return [
'charset' => 'utf8',
'prefix' => env('DB_PREFIX', ''),
'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
],
],
/*
@ -117,6 +118,7 @@ return [
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
@ -127,8 +129,7 @@ return [
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
@ -136,10 +137,11 @@ return [
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
],
],
];

View File

@ -1,6 +1,7 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Debugbar Settings
@ -15,8 +16,7 @@ return [
'enabled' => env('DEBUGBAR_ENABLED', null),
'except' => [
'telescope*',
'horizon*',
//
],
/*
@ -32,57 +32,13 @@ return [
|
*/
'storage' => [
'enabled' => true,
'driver' => 'file', // redis, file, pdo, socket, custom
'path' => storage_path('debugbar'), // For file driver
'enabled' => true,
'driver' => 'file', // redis, file, pdo, custom
'path' => storage_path('debugbar'), // For file driver
'connection' => null, // Leave null for default connection (Redis/PDO)
'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
'provider' => '', // Instance of StorageInterface for custom 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
@ -91,7 +47,7 @@ return [
| 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.
| Vendor files are for css: font-awesome (including fonts) and highlight.js (css files)
| and for js: jquery and highlight.js
| and for js: jquery and and highlight.js
| So if you want syntax highlighting, set it to true.
| jQuery is set to not conflict with existing jQuery scripts.
|
@ -108,9 +64,6 @@ return [
| 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.
|
| 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,
@ -148,29 +101,27 @@ return [
*/
'collectors' => [
'phpinfo' => true, // Php version
'messages' => true, // Messages
'time' => true, // Time Datalogger
'memory' => true, // Memory usage
'exceptions' => true, // Exception displayer
'log' => true, // Logs from Monolog (merged in messages if enabled)
'db' => true, // Show database (PDO) queries and bindings
'views' => true, // Views with their data
'route' => true, // Current route information
'auth' => false, // Display Laravel authentication status
'gate' => true, // Display Laravel Gate checks
'session' => true, // Display session data
'phpinfo' => true, // Php version
'messages' => true, // Messages
'time' => true, // Time Datalogger
'memory' => true, // Memory usage
'exceptions' => true, // Exception displayer
'log' => true, // Logs from Monolog (merged in messages if enabled)
'db' => true, // Show database (PDO) queries and bindings
'views' => true, // Views with their data
'route' => true, // Current route information
'auth' => true, // Display Laravel authentication status
'gate' => false, // Display Laravel Gate checks
'session' => true, // Display session data
'symfony_request' => true, // Only one can be enabled..
'mail' => true, // Catch mail messages
'laravel' => false, // Laravel version and environment
'events' => false, // All events fired
'mail' => false, // Catch mail messages
'laravel' => false, // Laravel version and environment
'events' => true, // All events fired
'default_request' => false, // Regular or special Symfony request logger
'logs' => false, // Add the latest log messages
'files' => false, // Show the included files
'config' => false, // Display config settings
'cache' => false, // Display cache events
'models' => true, // Display models
'livewire' => true, // Display Livewire (when available)
'logs' => false, // Add the latest log messages
'files' => false, // Show the included files
'config' => false, // Display config settings
'cache' => false, // Display cache events
],
/*
@ -187,26 +138,20 @@ return [
'show_name' => true, // Also show the users name/email in the debugbar
],
'db' => [
'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_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.
'with_params' => true, // Render SQL with the parameters substituted
'backtrace' => true, // Use a backtrace to find the origin of the query in your files.
'timeline' => false, // Add the queries to the timeline
'explain' => [ // Show EXPLAIN output on queries
'enabled' => false,
'types' => ['SELECT'], // Deprecated setting, is always only SELECT
'types' => ['SELECT'], // ['SELECT', 'INSERT', 'UPDATE', 'DELETE']; for MySQL 5.6.3+
],
'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
'hints' => true, // Show hints for common mistakes
],
'mail' => [
'full_log' => false,
],
'views' => [
'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
'data' => false, //Note: Can slow down the application, because the data can be quite large..
],
'route' => [
'label' => true, // show complete route on bar
@ -253,24 +198,4 @@ return [
| To override default domain, specify it as a non-empty value.
*/
'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,
];

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