Compare commits
No commits in common. "dd8fa5f82e2c966498fc08f3c2ed4fc8c2eff9c8" and "bd3a4379bd79c45308cccac13260f3f6329c910a" have entirely different histories.
dd8fa5f82e
...
bd3a4379bd
22 changed files with 4878 additions and 6286 deletions
|
|
@ -1,119 +0,0 @@
|
|||
name: Auto Label
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
jobs:
|
||||
auto-label:
|
||||
if: github.repository != 'WenPai-org/ci-workflows'
|
||||
runs-on: linux-arm64
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 自动打标签
|
||||
env:
|
||||
FORGEJO_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
API_BASE="${GITHUB_SERVER_URL:-https://feicode.com}/api/v1"
|
||||
OWNER="${GITHUB_REPOSITORY%/*}"
|
||||
REPO="${GITHUB_REPOSITORY#*/}"
|
||||
LABELS=""
|
||||
|
||||
# 获取 PR 变更文件
|
||||
FILES=$(curl -s -H "Authorization: token $FORGEJO_TOKEN" \
|
||||
"$API_BASE/repos/$OWNER/$REPO/pulls/$PR_NUMBER/files" | \
|
||||
python3 -c "import json,sys; [print(f['filename']) for f in json.load(sys.stdin)]" 2>/dev/null)
|
||||
|
||||
# 根据文件类型打标签
|
||||
echo "$FILES" | grep -qE '\.php$' && LABELS="$LABELS,php"
|
||||
echo "$FILES" | grep -qE '\.(js|ts|tsx|jsx)$' && LABELS="$LABELS,frontend"
|
||||
echo "$FILES" | grep -qE '\.(css|scss|less)$' && LABELS="$LABELS,style"
|
||||
echo "$FILES" | grep -qE '\.go$' && LABELS="$LABELS,go"
|
||||
echo "$FILES" | grep -qE '\.(yml|yaml)$' && LABELS="$LABELS,ci/cd"
|
||||
echo "$FILES" | grep -qE '(composer\.|package\.json|go\.mod)' && LABELS="$LABELS,dependencies"
|
||||
echo "$FILES" | grep -qE '\.(md|txt|rst)$' && LABELS="$LABELS,documentation"
|
||||
echo "$FILES" | grep -qE '(Dockerfile|docker-compose)' && LABELS="$LABELS,docker"
|
||||
|
||||
# 根据 PR 大小打标签
|
||||
ADDITIONS=$(curl -s -H "Authorization: token $FORGEJO_TOKEN" \
|
||||
"$API_BASE/repos/$OWNER/$REPO/pulls/$PR_NUMBER" | \
|
||||
python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('additions',0)+d.get('deletions',0))" 2>/dev/null)
|
||||
|
||||
if [ "$ADDITIONS" -lt 10 ] 2>/dev/null; then
|
||||
LABELS="$LABELS,size/S"
|
||||
elif [ "$ADDITIONS" -lt 100 ] 2>/dev/null; then
|
||||
LABELS="$LABELS,size/M"
|
||||
elif [ "$ADDITIONS" -lt 500 ] 2>/dev/null; then
|
||||
LABELS="$LABELS,size/L"
|
||||
else
|
||||
LABELS="$LABELS,size/XL"
|
||||
fi
|
||||
|
||||
# 去掉开头逗号
|
||||
LABELS="${LABELS#,}"
|
||||
|
||||
if [ -z "$LABELS" ]; then
|
||||
echo "无需添加标签"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "添加标签: $LABELS"
|
||||
|
||||
# 确保标签存在,不存在则创建
|
||||
IFS=',' read -ra LABEL_ARRAY <<< "$LABELS"
|
||||
LABEL_IDS="["
|
||||
for LABEL in "${LABEL_ARRAY[@]}"; do
|
||||
# 查找标签
|
||||
LABEL_ID=$(curl -s -H "Authorization: token $FORGEJO_TOKEN" \
|
||||
"$API_BASE/repos/$OWNER/$REPO/labels?limit=50" | \
|
||||
python3 -c "
|
||||
import json,sys
|
||||
labels=json.load(sys.stdin)
|
||||
for l in labels:
|
||||
if l['name']=='$LABEL':
|
||||
print(l['id'])
|
||||
break
|
||||
" 2>/dev/null)
|
||||
|
||||
# 标签不存在则创建
|
||||
if [ -z "$LABEL_ID" ]; then
|
||||
# 根据标签类型选颜色
|
||||
case "$LABEL" in
|
||||
php) COLOR="#4F5D95" ;;
|
||||
frontend) COLOR="#f1e05a" ;;
|
||||
go) COLOR="#00ADD8" ;;
|
||||
ci/cd) COLOR="#0075ca" ;;
|
||||
dependencies) COLOR="#0366d6" ;;
|
||||
documentation) COLOR="#0075ca" ;;
|
||||
docker) COLOR="#0db7ed" ;;
|
||||
style) COLOR="#e34c26" ;;
|
||||
size/S) COLOR="#009900" ;;
|
||||
size/M) COLOR="#FFCC00" ;;
|
||||
size/L) COLOR="#FF6600" ;;
|
||||
size/XL) COLOR="#CC0000" ;;
|
||||
*) COLOR="#ededed" ;;
|
||||
esac
|
||||
LABEL_ID=$(curl -s -X POST -H "Authorization: token $FORGEJO_TOKEN" \
|
||||
"$API_BASE/repos/$OWNER/$REPO/labels" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\":\"$LABEL\",\"color\":\"$COLOR\"}" | \
|
||||
python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null)
|
||||
fi
|
||||
|
||||
[ -n "$LABEL_ID" ] && LABEL_IDS="$LABEL_IDS$LABEL_ID,"
|
||||
done
|
||||
LABEL_IDS="${LABEL_IDS%,}]"
|
||||
|
||||
# 添加标签到 PR
|
||||
curl -s -X POST -H "Authorization: token $FORGEJO_TOKEN" \
|
||||
"$API_BASE/repos/$OWNER/$REPO/issues/$PR_NUMBER/labels" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"labels\":$LABEL_IDS}" > /dev/null
|
||||
|
||||
echo "标签添加完成"
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
name: gitleaks 密钥泄露扫描
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['*']
|
||||
pull_request:
|
||||
branches: ['*']
|
||||
|
||||
jobs:
|
||||
gitleaks:
|
||||
runs-on: linux-arm64
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run gitleaks
|
||||
run: |
|
||||
if [ "$GITHUB_EVENT_NAME" = "push" ]; then
|
||||
gitleaks detect --source=. --log-opts="$GITHUB_SHA~1..$GITHUB_SHA" --verbose --exit-code 1 || {
|
||||
echo "::error::gitleaks 发现了潜在的密钥泄露!请检查上方输出并移除敏感信息。"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
gitleaks detect --source=. --verbose --exit-code 1 || {
|
||||
echo "::error::gitleaks 发现了潜在的密钥泄露!请检查上方输出并移除敏感信息。"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
echo "gitleaks 扫描通过,未发现密钥泄露。"
|
||||
|
|
@ -127,7 +127,6 @@ jobs:
|
|||
--exclude="*.md" \
|
||||
--exclude="LICENSE" \
|
||||
--exclude="Makefile" \
|
||||
--exclude="lib" \
|
||||
./ "$BUILD_DIR/"
|
||||
|
||||
(
|
||||
|
|
@ -361,39 +360,3 @@ jobs:
|
|||
fi
|
||||
|
||||
echo "Update API verification passed"
|
||||
|
||||
- name: Notify AI CI assistant on failure (optional)
|
||||
if: ${{ failure() && secrets.AI_WEBHOOK_ENDPOINT != '' }}
|
||||
continue-on-error: true
|
||||
shell: bash
|
||||
env:
|
||||
AI_WEBHOOK_ENDPOINT: ${{ secrets.AI_WEBHOOK_ENDPOINT }}
|
||||
AI_WEBHOOK_TOKEN: ${{ secrets.AI_WEBHOOK_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
OWNER="${GITHUB_REPOSITORY%%/*}"
|
||||
REPO="${GITHUB_REPOSITORY##*/}"
|
||||
BRANCH="${GITHUB_REF#refs/heads/}"
|
||||
RUN_URL="${GITHUB_SERVER_URL%/}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
|
||||
FAILED_STEPS_JSON="$(printf '%s' '${{ toJson(steps) }}' | jq -c '[to_entries[] | select(.value.outcome=="failure" or .value.conclusion=="failure") | .key]')"
|
||||
|
||||
PAYLOAD="$(jq -cn \
|
||||
--arg event "ci_failure_report" \
|
||||
--arg owner "$OWNER" \
|
||||
--arg repo "$REPO" \
|
||||
--arg sha "${GITHUB_SHA}" \
|
||||
--arg branch "$BRANCH" \
|
||||
--arg workflow "${GITHUB_WORKFLOW}" \
|
||||
--arg run_url "$RUN_URL" \
|
||||
--arg log_excerpt "release workflow failed; inspect run_url for full logs" \
|
||||
--argjson failed_steps "$FAILED_STEPS_JSON" \
|
||||
'{event:$event, owner:$owner, repo:$repo, sha:$sha, branch:$branch, workflow:$workflow, run_url:$run_url, failed_steps:$failed_steps, log_excerpt:$log_excerpt}')"
|
||||
|
||||
CURL_HEADERS=(-H "Content-Type: application/json")
|
||||
if [[ -n "${AI_WEBHOOK_TOKEN:-}" ]]; then
|
||||
CURL_HEADERS+=( -H "Authorization: Bearer ${AI_WEBHOOK_TOKEN}" )
|
||||
fi
|
||||
|
||||
curl -sS -X POST "${CURL_HEADERS[@]}" --data "$PAYLOAD" "$AI_WEBHOOK_ENDPOINT" > /dev/null
|
||||
echo "AI CI assistant notified"
|
||||
|
|
|
|||
|
|
@ -1,128 +0,0 @@
|
|||
name: 安全扫描
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main', 'master']
|
||||
paths:
|
||||
- 'Dockerfile*'
|
||||
- 'docker-compose*.yml'
|
||||
- 'composer.json'
|
||||
- 'composer.lock'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'yarn.lock'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
pull_request:
|
||||
branches: ['main', 'master']
|
||||
schedule:
|
||||
- cron: '0 3 * * 1' # 每周一凌晨 3 点
|
||||
|
||||
jobs:
|
||||
security-scan:
|
||||
runs-on: linux-arm64
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Hadolint Dockerfile 检查
|
||||
run: |
|
||||
# 检查是否存在 Dockerfile
|
||||
DOCKERFILES=$(find . -name 'Dockerfile*' -not -path './.git/*' 2>/dev/null)
|
||||
if [ -z "$DOCKERFILES" ]; then
|
||||
echo "未找到 Dockerfile,跳过"
|
||||
exit 0
|
||||
fi
|
||||
# 如果未安装 hadolint 则跳过
|
||||
if ! command -v hadolint &> /dev/null; then
|
||||
echo "hadolint 未安装,跳过 Dockerfile 检查"
|
||||
exit 0
|
||||
fi
|
||||
FAILED=0
|
||||
for df in $DOCKERFILES; do
|
||||
echo "--- 检查 $df ---"
|
||||
hadolint "$df" || FAILED=1
|
||||
done
|
||||
if [ $FAILED -eq 1 ]; then
|
||||
echo "::warning::Dockerfile 存在规范问题,请检查上方输出"
|
||||
fi
|
||||
|
||||
- name: PHP Composer 安全审计
|
||||
run: |
|
||||
if [ ! -f composer.lock ]; then
|
||||
echo "未找到 composer.lock,跳过"
|
||||
exit 0
|
||||
fi
|
||||
composer audit --format=table || {
|
||||
echo "::error::Composer 依赖存在已知安全漏洞"
|
||||
exit 1
|
||||
}
|
||||
echo "Composer 安全审计通过"
|
||||
|
||||
- name: npm 安全审计
|
||||
run: |
|
||||
if [ ! -f package-lock.json ] && [ ! -f yarn.lock ] && [ ! -f pnpm-lock.yaml ]; then
|
||||
echo "未找到 JS 锁文件,跳过"
|
||||
exit 0
|
||||
fi
|
||||
# npm audit 只报告 high 和 critical
|
||||
if [ -f package-lock.json ]; then
|
||||
npm audit --audit-level=high || {
|
||||
echo "::error::npm 依赖存在高危安全漏洞"
|
||||
exit 1
|
||||
}
|
||||
elif [ -f pnpm-lock.yaml ]; then
|
||||
pnpm audit --audit-level=high || {
|
||||
echo "::error::pnpm 依赖存在高危安全漏洞"
|
||||
exit 1
|
||||
}
|
||||
elif [ -f yarn.lock ]; then
|
||||
yarn npm audit --severity high || {
|
||||
echo "::error::yarn 依赖存在高危安全漏洞"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
echo "JS 依赖安全审计通过"
|
||||
|
||||
- name: Go 依赖漏洞检查
|
||||
run: |
|
||||
if [ ! -f go.mod ]; then
|
||||
echo "未找到 go.mod,跳过"
|
||||
exit 0
|
||||
fi
|
||||
# govulncheck 检查已知漏洞
|
||||
if ! command -v govulncheck &> /dev/null; then
|
||||
go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
export PATH="$(go env GOPATH)/bin:$PATH"
|
||||
fi
|
||||
govulncheck ./... || {
|
||||
echo "::error::Go 依赖存在已知安全漏洞"
|
||||
exit 1
|
||||
}
|
||||
echo "Go 依赖安全检查通过"
|
||||
|
||||
- name: Trivy 容器镜像扫描
|
||||
run: |
|
||||
DOCKERFILES=$(find . -name 'Dockerfile' -not -path './.git/*' 2>/dev/null)
|
||||
if [ -z "$DOCKERFILES" ]; then
|
||||
echo "未找到 Dockerfile,跳过镜像扫描"
|
||||
exit 0
|
||||
fi
|
||||
if ! command -v trivy &> /dev/null; then
|
||||
echo "trivy 未安装,跳过容器镜像扫描"
|
||||
exit 0
|
||||
fi
|
||||
# 构建并扫描镜像
|
||||
IMAGE_NAME="ci-security-scan:$$"
|
||||
podman build -t "$IMAGE_NAME" -f Dockerfile . || {
|
||||
echo "::warning::容器构建失败,跳过镜像扫描"
|
||||
exit 0
|
||||
}
|
||||
trivy image --severity HIGH,CRITICAL --exit-code 1 "$IMAGE_NAME" || {
|
||||
echo "::error::容器镜像存在高危漏洞"
|
||||
podman rmi "$IMAGE_NAME" 2>/dev/null
|
||||
exit 1
|
||||
}
|
||||
podman rmi "$IMAGE_NAME" 2>/dev/null
|
||||
echo "容器镜像安全扫描通过"
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
name: Stale Issue/PR 清理
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 5 * * 1' # 每周一凌晨 5 点
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
stale-cleanup:
|
||||
if: github.repository != 'WenPai-org/ci-workflows'
|
||||
runs-on: linux-arm64
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 清理过期 Issue 和 PR
|
||||
env:
|
||||
FORGEJO_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
API_BASE="${GITHUB_SERVER_URL:-https://feicode.com}/api/v1"
|
||||
OWNER="${GITHUB_REPOSITORY%/*}"
|
||||
REPO="${GITHUB_REPOSITORY#*/}"
|
||||
NOW=$(date +%s)
|
||||
|
||||
# 受保护的标签,带这些标签的不处理
|
||||
PROTECTED_LABELS="pinned|help-wanted|bug|security|wontfix"
|
||||
|
||||
# Issue: 60 天无活动标记 stale,再过 14 天关闭
|
||||
STALE_DAYS=60
|
||||
CLOSE_DAYS=74
|
||||
# PR: 30 天无活动标记 stale,再过 14 天关闭
|
||||
PR_STALE_DAYS=30
|
||||
PR_CLOSE_DAYS=44
|
||||
|
||||
process_items() {
|
||||
local TYPE=$1 # issues 或 pulls
|
||||
local STALE=$2
|
||||
local CLOSE=$3
|
||||
|
||||
PAGE=1
|
||||
while true; do
|
||||
if [ "$TYPE" = "issues" ]; then
|
||||
ITEMS=$(curl -s -H "Authorization: token $FORGEJO_TOKEN" \
|
||||
"$API_BASE/repos/$OWNER/$REPO/issues?state=open&type=issues&page=$PAGE&limit=50")
|
||||
else
|
||||
ITEMS=$(curl -s -H "Authorization: token $FORGEJO_TOKEN" \
|
||||
"$API_BASE/repos/$OWNER/$REPO/issues?state=open&type=pulls&page=$PAGE&limit=50")
|
||||
fi
|
||||
|
||||
COUNT=$(echo "$ITEMS" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null)
|
||||
[ "$COUNT" = "0" ] || [ -z "$COUNT" ] && break
|
||||
|
||||
echo "$ITEMS" | python3 -c "
|
||||
import json, sys, time
|
||||
|
||||
items = json.load(sys.stdin)
|
||||
now = $NOW
|
||||
stale_seconds = $STALE * 86400
|
||||
close_seconds = $CLOSE * 86400
|
||||
protected = set('$PROTECTED_LABELS'.split('|'))
|
||||
|
||||
for item in items:
|
||||
number = item['number']
|
||||
title = item['title']
|
||||
labels = [l['name'] for l in item.get('labels', [])]
|
||||
|
||||
# 跳过受保护标签
|
||||
if protected & set(labels):
|
||||
continue
|
||||
|
||||
updated = item['updated_at']
|
||||
# 解析 ISO 时间
|
||||
from datetime import datetime, timezone
|
||||
dt = datetime.fromisoformat(updated.replace('Z', '+00:00'))
|
||||
updated_ts = int(dt.timestamp())
|
||||
age = now - updated_ts
|
||||
|
||||
if age > close_seconds:
|
||||
print(f'CLOSE|{number}|{title}')
|
||||
elif age > stale_seconds and 'stale' not in labels:
|
||||
print(f'STALE|{number}|{title}')
|
||||
" | while IFS='|' read -r ACTION NUMBER TITLE; do
|
||||
if [ "$ACTION" = "STALE" ]; then
|
||||
echo "标记 #$NUMBER 为 stale: $TITLE"
|
||||
# 添加 stale 标签
|
||||
curl -s -X POST -H "Authorization: token $FORGEJO_TOKEN" \
|
||||
"$API_BASE/repos/$OWNER/$REPO/issues/$NUMBER/labels" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"labels":["stale"]}' > /dev/null
|
||||
# 添加评论
|
||||
curl -s -X POST -H "Authorization: token $FORGEJO_TOKEN" \
|
||||
"$API_BASE/repos/$OWNER/$REPO/issues/$NUMBER/comments" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"body\":\"此 ${TYPE%s} 已超过 $STALE 天无活动,已标记为 stale。如果 14 天内无新活动将自动关闭。\"}" > /dev/null
|
||||
elif [ "$ACTION" = "CLOSE" ]; then
|
||||
echo "关闭 #$NUMBER: $TITLE"
|
||||
curl -s -X PATCH -H "Authorization: token $FORGEJO_TOKEN" \
|
||||
"$API_BASE/repos/$OWNER/$REPO/issues/$NUMBER" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"state":"closed"}' > /dev/null
|
||||
curl -s -X POST -H "Authorization: token $FORGEJO_TOKEN" \
|
||||
"$API_BASE/repos/$OWNER/$REPO/issues/$NUMBER/comments" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"body\":\"此 ${TYPE%s} 因长期无活动已自动关闭。如需继续讨论请重新打开。\"}" > /dev/null
|
||||
fi
|
||||
done
|
||||
|
||||
PAGE=$((PAGE + 1))
|
||||
done
|
||||
}
|
||||
|
||||
echo "=== 处理 Issues (${STALE_DAYS}天stale / ${CLOSE_DAYS}天关闭) ==="
|
||||
process_items "issues" $STALE_DAYS $CLOSE_DAYS
|
||||
|
||||
echo "=== 处理 PRs (${PR_STALE_DAYS}天stale / ${PR_CLOSE_DAYS}天关闭) ==="
|
||||
process_items "pulls" $PR_STALE_DAYS $PR_CLOSE_DAYS
|
||||
|
||||
echo "清理完成"
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
name: Trivy 依赖漏洞扫描
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main', 'master']
|
||||
paths:
|
||||
- 'composer.lock'
|
||||
- 'package-lock.json'
|
||||
- 'yarn.lock'
|
||||
- 'pnpm-lock.yaml'
|
||||
pull_request:
|
||||
branches: ['main', 'master']
|
||||
# 每周一早上 8 点定时扫描
|
||||
schedule:
|
||||
- cron: '0 0 * * 1'
|
||||
|
||||
jobs:
|
||||
trivy-scan:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: aquasec/trivy:latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: https://code.forgejo.org/actions/checkout@v4
|
||||
|
||||
- name: Run Trivy filesystem scan
|
||||
run: |
|
||||
trivy filesystem . --scanners vuln --severity HIGH,CRITICAL --exit-code 1 --format table --ignorefile .trivyignore 2>&1 || {
|
||||
echo
|
||||
echo ::warning::Trivy 发现了高危/严重漏洞,请检查上方输出。
|
||||
echo 如需忽略特定 CVE,请在仓库根目录创建 .trivyignore 文件。
|
||||
exit 1
|
||||
}
|
||||
echo ✅ Trivy 扫描通过,未发现高危漏洞。
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
name: WordPress 插件 CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main', 'master']
|
||||
pull_request:
|
||||
branches: ['main', 'master']
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
if: github.repository != 'WenPai-org/ci-workflows'
|
||||
runs-on: linux-arm64
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: PHP Parallel Lint
|
||||
run: |
|
||||
parallel-lint --exclude vendor --exclude node_modules .
|
||||
|
||||
- name: PHPCS 代码规范检查
|
||||
run: |
|
||||
# 如果仓库有自定义 phpcs 配置则使用,否则用默认 WordPress 标准
|
||||
if [ -f phpcs.xml ] || [ -f phpcs.xml.dist ] || [ -f .phpcs.xml ] || [ -f .phpcs.xml.dist ]; then
|
||||
phpcs .
|
||||
else
|
||||
phpcs --standard=WordPress-Extra \
|
||||
--extensions=php \
|
||||
--ignore=vendor/*,node_modules/*,tests/*,lib/* \
|
||||
--report=full \
|
||||
-s .
|
||||
fi
|
||||
|
||||
- name: Gitleaks 密钥泄露扫描
|
||||
run: |
|
||||
if [ "$GITHUB_EVENT_NAME" = "push" ]; then
|
||||
gitleaks detect --source=. --log-opts="$GITHUB_SHA~1..$GITHUB_SHA" --verbose --exit-code 1
|
||||
else
|
||||
gitleaks detect --source=. --verbose --exit-code 1
|
||||
fi
|
||||
echo "gitleaks 扫描通过"
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
name: WordPress 插件自动发布
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
- '[0-9]+.[0-9]+*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: php:8.2-cli-alpine
|
||||
steps:
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
apk add --no-cache git curl zip unzip rsync npm composer python3
|
||||
|
||||
- name: Checkout
|
||||
uses: https://code.forgejo.org/actions/checkout@v4
|
||||
|
||||
- name: Install WP-CLI
|
||||
run: |
|
||||
curl -sO https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
|
||||
chmod +x wp-cli.phar
|
||||
mv wp-cli.phar /usr/local/bin/wp
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
if [ -f composer.json ]; then
|
||||
composer install --no-dev --optimize-autoloader --no-interaction
|
||||
fi
|
||||
if [ -f package.json ]; then
|
||||
npm ci --production 2>/dev/null || true
|
||||
fi
|
||||
|
||||
- name: Run PHPCS (if configured)
|
||||
run: |
|
||||
if [ -f phpcs.xml ] || [ -f phpcs.xml.dist ] || [ -f .phpcs.xml ] || [ -f .phpcs.xml.dist ]; then
|
||||
composer lint 2>/dev/null || vendor/bin/phpcs . || {
|
||||
echo "::error::PHPCS 检查失败,请修复代码规范问题后重新打 tag。"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
echo "未找到 PHPCS 配置,跳过代码规范检查。"
|
||||
fi
|
||||
|
||||
- name: Generate i18n files
|
||||
run: |
|
||||
SLUG=$(basename $GITHUB_REPOSITORY)
|
||||
if [ -d languages ] || [ -d lang ]; then
|
||||
LANG_DIR=$([ -d languages ] && echo languages || echo lang)
|
||||
wp i18n make-pot . "$LANG_DIR/$SLUG.pot" --allow-root 2>/dev/null || true
|
||||
wp i18n make-json "$LANG_DIR/" --no-purge --allow-root 2>/dev/null || true
|
||||
fi
|
||||
|
||||
- name: Build release ZIP
|
||||
run: |
|
||||
PLUGIN_SLUG=$(basename $GITHUB_REPOSITORY)
|
||||
TAG_NAME=${GITHUB_REF#refs/tags/}
|
||||
mkdir -p /tmp/release
|
||||
rsync -a --exclude='.git' --exclude='.forgejo' --exclude='.github' \
|
||||
--exclude='node_modules' --exclude='.phpcs*' --exclude='phpstan*' \
|
||||
--exclude='tests' --exclude='.editorconfig' --exclude='.gitignore' \
|
||||
--exclude='phpunit*' --exclude='Gruntfile*' --exclude='webpack*' \
|
||||
--exclude='package.json' --exclude='package-lock.json' \
|
||||
--exclude='composer.lock' --exclude='.env*' \
|
||||
. /tmp/release/$PLUGIN_SLUG/
|
||||
cd /tmp/release
|
||||
zip -r /tmp/$PLUGIN_SLUG-$TAG_NAME.zip $PLUGIN_SLUG/
|
||||
echo "ZIP_PATH=/tmp/$PLUGIN_SLUG-$TAG_NAME.zip" >> $GITHUB_ENV
|
||||
echo "PLUGIN_SLUG=$PLUGIN_SLUG" >> $GITHUB_ENV
|
||||
echo "TAG_NAME=$TAG_NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Create Forgejo Release
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
RELEASE_RESPONSE=$(curl -s -X POST \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/releases" \
|
||||
-d "{
|
||||
\"tag_name\": \"$TAG_NAME\",
|
||||
\"name\": \"$PLUGIN_SLUG $TAG_NAME\",
|
||||
\"body\": \"自动发布 $TAG_NAME\",
|
||||
\"draft\": false,
|
||||
\"prerelease\": false
|
||||
}")
|
||||
RELEASE_ID=$(echo $RELEASE_RESPONSE | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null)
|
||||
if [ -z "$RELEASE_ID" ]; then
|
||||
echo "::warning::Release 创建失败或已存在。"
|
||||
exit 0
|
||||
fi
|
||||
curl -s -X POST \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@$ZIP_PATH" \
|
||||
"$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets?name=$PLUGIN_SLUG-$TAG_NAME.zip"
|
||||
echo "Release $TAG_NAME 发布成功!"
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
vendor/
|
||||
node_modules/
|
||||
.env
|
||||
*.log
|
||||
.DS_Store
|
||||
|
|
@ -91,13 +91,13 @@ class WenPai_Updater {
|
|||
// Update URI: https://updates.wenpai.net 触发此 filter
|
||||
add_filter(
|
||||
'update_plugins_updates.wenpai.net',
|
||||
array( $this, 'check_update' ),
|
||||
[ $this, 'check_update' ],
|
||||
10,
|
||||
4
|
||||
);
|
||||
|
||||
// 插件详情弹窗
|
||||
add_filter( 'plugins_api', array( $this, 'plugin_info' ), 20, 3 );
|
||||
add_filter( 'plugins_api', [ $this, 'plugin_info' ], 20, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -117,16 +117,13 @@ class WenPai_Updater {
|
|||
return $update;
|
||||
}
|
||||
|
||||
$response = $this->api_request(
|
||||
'update-check',
|
||||
array(
|
||||
'plugins' => array(
|
||||
$this->plugin_file => array(
|
||||
$response = $this->api_request( 'update-check', [
|
||||
'plugins' => [
|
||||
$this->plugin_file => [
|
||||
'Version' => $this->version,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
],
|
||||
],
|
||||
] );
|
||||
|
||||
if ( is_wp_error( $response ) || empty( $response['plugins'][ $this->plugin_file ] ) ) {
|
||||
return $update;
|
||||
|
|
@ -134,7 +131,7 @@ class WenPai_Updater {
|
|||
|
||||
$data = $response['plugins'][ $this->plugin_file ];
|
||||
|
||||
return (object) array(
|
||||
return (object) [
|
||||
'id' => $data['id'] ?? '',
|
||||
'slug' => $data['slug'] ?? $this->slug,
|
||||
'plugin' => $this->plugin_file,
|
||||
|
|
@ -142,12 +139,12 @@ class WenPai_Updater {
|
|||
'new_version' => $data['version'] ?? '',
|
||||
'url' => $data['url'] ?? '',
|
||||
'package' => $data['package'] ?? '',
|
||||
'icons' => $data['icons'] ?? array(),
|
||||
'banners' => $data['banners'] ?? array(),
|
||||
'icons' => $data['icons'] ?? [],
|
||||
'banners' => $data['banners'] ?? [],
|
||||
'requires' => $data['requires'] ?? '',
|
||||
'tested' => $data['tested'] ?? '',
|
||||
'requires_php' => $data['requires_php'] ?? '',
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -186,9 +183,9 @@ class WenPai_Updater {
|
|||
$info->tested = $response['tested'] ?? '';
|
||||
$info->requires_php = $response['requires_php'] ?? '';
|
||||
$info->last_updated = $response['last_updated'] ?? '';
|
||||
$info->icons = $response['icons'] ?? array();
|
||||
$info->banners = $response['banners'] ?? array();
|
||||
$info->sections = $response['sections'] ?? array();
|
||||
$info->icons = $response['icons'] ?? [];
|
||||
$info->banners = $response['banners'] ?? [];
|
||||
$info->sections = $response['sections'] ?? [];
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
|
@ -203,12 +200,12 @@ class WenPai_Updater {
|
|||
private function api_request( string $endpoint, ?array $body = null ) {
|
||||
$url = self::API_URL . '/' . ltrim( $endpoint, '/' );
|
||||
|
||||
$args = array(
|
||||
$args = [
|
||||
'timeout' => 10,
|
||||
'headers' => array(
|
||||
'headers' => [
|
||||
'Accept' => 'application/json',
|
||||
),
|
||||
);
|
||||
],
|
||||
];
|
||||
|
||||
if ( null !== $body ) {
|
||||
$args['headers']['Content-Type'] = 'application/json';
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -51,14 +51,11 @@ class WPSlug_Converter {
|
|||
} catch (Exception $e) {
|
||||
$execution_time = microtime(true) - $start_time;
|
||||
$this->settings->updateConversionStats($mode, false, $execution_time);
|
||||
$this->settings->logError(
|
||||
'Conversion error in mode ' . $mode . ': ' . $e->getMessage(),
|
||||
array(
|
||||
$this->settings->logError('Conversion error in mode ' . $mode . ': ' . $e->getMessage(), array(
|
||||
'text' => $text,
|
||||
'mode' => $mode,
|
||||
'options' => $options,
|
||||
)
|
||||
);
|
||||
'options' => $options
|
||||
));
|
||||
|
||||
return $this->cleanBasicSlug($text, $options);
|
||||
}
|
||||
|
|
@ -109,13 +106,10 @@ class WPSlug_Converter {
|
|||
}
|
||||
|
||||
// 4. 调用 WPMind AI 进行语义化拼音转换
|
||||
$result = wpmind_pinyin(
|
||||
$text,
|
||||
array(
|
||||
$result = wpmind_pinyin($text, [
|
||||
'context' => 'wpslug_semantic_pinyin',
|
||||
'cache_ttl' => 604800, // 7天
|
||||
)
|
||||
);
|
||||
]);
|
||||
|
||||
// 5. 处理结果
|
||||
if (is_wp_error($result)) {
|
||||
|
|
@ -276,7 +270,7 @@ class WPSlug_Converter {
|
|||
'original' => $item,
|
||||
'converted' => $converted,
|
||||
'mode' => isset($options['conversion_mode']) ? $options['conversion_mode'] : 'pinyin',
|
||||
'detected_language' => $this->detectLanguage( $item ),
|
||||
'detected_language' => $this->detectLanguage($item)
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$this->settings->logError('Batch convert error: ' . $e->getMessage());
|
||||
|
|
@ -284,7 +278,7 @@ class WPSlug_Converter {
|
|||
'original' => $item,
|
||||
'converted' => sanitize_title($item),
|
||||
'mode' => 'fallback',
|
||||
'detected_language' => 'unknown',
|
||||
'detected_language' => 'unknown'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -306,7 +300,7 @@ class WPSlug_Converter {
|
|||
'converted' => $converted,
|
||||
'detected_language' => $detected_lang,
|
||||
'execution_time' => $execution_time,
|
||||
'mode' => isset( $options['conversion_mode'] ) ? $options['conversion_mode'] : 'pinyin',
|
||||
'mode' => isset($options['conversion_mode']) ? $options['conversion_mode'] : 'pinyin'
|
||||
);
|
||||
|
||||
} catch (Exception $e) {
|
||||
|
|
@ -319,7 +313,7 @@ class WPSlug_Converter {
|
|||
'detected_language' => 'unknown',
|
||||
'execution_time' => $execution_time,
|
||||
'mode' => 'fallback',
|
||||
'error' => $e->getMessage(),
|
||||
'error' => $e->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -339,7 +333,7 @@ class WPSlug_Converter {
|
|||
'has_greek' => preg_match('/[\x{0370}-\x{03ff}]/u', $text) ? true : false,
|
||||
'has_latin' => preg_match('/[a-zA-Z]/', $text) ? true : false,
|
||||
'has_numbers' => preg_match('/\d/', $text) ? true : false,
|
||||
'has_special_chars' => preg_match( '/[^\w\s\p{L}\p{N}]/u', $text ) ? true : false,
|
||||
'has_special_chars' => preg_match('/[^\w\s\p{L}\p{N}]/u', $text) ? true : false
|
||||
);
|
||||
|
||||
return $info;
|
||||
|
|
@ -366,20 +360,20 @@ class WPSlug_Converter {
|
|||
if (empty($text)) {
|
||||
return array(
|
||||
'valid' => false,
|
||||
'error' => 'Input text is empty',
|
||||
'error' => 'Input text is empty'
|
||||
);
|
||||
}
|
||||
|
||||
if ($max_length > 0 && mb_strlen($text, 'UTF-8') > $max_length) {
|
||||
return array(
|
||||
'valid' => false,
|
||||
'error' => 'Input text exceeds maximum length of ' . $max_length . ' characters',
|
||||
'error' => 'Input text exceeds maximum length of ' . $max_length . ' characters'
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'valid' => true,
|
||||
'message' => 'Input is valid',
|
||||
'message' => 'Input is valid'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -44,7 +44,8 @@ class WPSlug_Core {
|
|||
/**
|
||||
* Disabled: migrated to WPSlug_Updater (Update URI)
|
||||
*/
|
||||
private function init_update_checker() {
|
||||
private function init_update_checker()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -299,12 +300,10 @@ class WPSlug_Core {
|
|||
if (!empty($new_slug) && $new_slug !== $post->post_name) {
|
||||
$unique_slug = $this->optimizer->generateUniqueSlug($new_slug, $post->ID, $post->post_type);
|
||||
|
||||
wp_update_post(
|
||||
array(
|
||||
wp_update_post(array(
|
||||
'ID' => $post->ID,
|
||||
'post_name' => $unique_slug,
|
||||
)
|
||||
);
|
||||
'post_name' => $unique_slug
|
||||
));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->settings->logError('handlePostStatusTransition error: ' . $e->getMessage());
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
class WPSlug_Optimizer {
|
||||
|
||||
public function __construct() {
|
||||
|
||||
}
|
||||
|
||||
public function optimize($text, $options = array()) {
|
||||
|
|
@ -130,12 +131,9 @@ class WPSlug_Optimizer {
|
|||
}
|
||||
|
||||
$words = preg_split('/[-\s_]+/', $text);
|
||||
$words = array_filter(
|
||||
$words,
|
||||
function ( $word ) {
|
||||
$words = array_filter($words, function($word) {
|
||||
return !empty(trim($word));
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
if (count($words) <= $max_words) {
|
||||
return $text;
|
||||
|
|
@ -180,26 +178,20 @@ class WPSlug_Optimizer {
|
|||
$suffix = 1;
|
||||
|
||||
while (true) {
|
||||
$query = $wpdb->prepare(
|
||||
"
|
||||
$query = $wpdb->prepare("
|
||||
SELECT ID FROM {$wpdb->posts}
|
||||
WHERE post_name = %s
|
||||
AND post_type = %s
|
||||
AND ID != %d
|
||||
LIMIT 1
|
||||
",
|
||||
$slug,
|
||||
$post_type,
|
||||
$post_id
|
||||
);
|
||||
", $slug, $post_type, $post_id);
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $query is prepared above
|
||||
if (!$wpdb->get_var($query)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$slug = $original_slug . '-' . $suffix;
|
||||
++$suffix;
|
||||
$suffix++;
|
||||
|
||||
if ($suffix > 100) {
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@ class WPSlug_Pinyin {
|
|||
$result .= $separator . strtolower($pinyin) . $separator;
|
||||
}
|
||||
}
|
||||
} elseif ( $preserve_english && preg_match( '/[a-zA-Z]/', $char ) ) {
|
||||
} else {
|
||||
if ($preserve_english && preg_match('/[a-zA-Z]/', $char)) {
|
||||
$result .= $char;
|
||||
} elseif ($preserve_numbers && preg_match('/[0-9]/', $char)) {
|
||||
$result .= $char;
|
||||
|
|
@ -56,6 +57,7 @@ class WPSlug_Pinyin {
|
|||
$result .= $separator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($separator)) {
|
||||
$result = trim(preg_replace('/' . preg_quote($separator, '/') . '+/', $separator, $result), $separator);
|
||||
|
|
@ -122,190 +124,43 @@ class WPSlug_Pinyin {
|
|||
|
||||
private function initFallbackDict() {
|
||||
$this->pinyin_dict = array(
|
||||
'一' => 'yi',
|
||||
'二' => 'er',
|
||||
'三' => 'san',
|
||||
'四' => 'si',
|
||||
'五' => 'wu',
|
||||
'六' => 'liu',
|
||||
'七' => 'qi',
|
||||
'八' => 'ba',
|
||||
'九' => 'jiu',
|
||||
'十' => 'shi',
|
||||
'的' => 'de',
|
||||
'了' => 'le',
|
||||
'是' => 'shi',
|
||||
'我' => 'wo',
|
||||
'你' => 'ni',
|
||||
'他' => 'ta',
|
||||
'她' => 'ta',
|
||||
'它' => 'ta',
|
||||
'好' => 'hao',
|
||||
'很' => 'hen',
|
||||
'都' => 'dou',
|
||||
'会' => 'hui',
|
||||
'个' => 'ge',
|
||||
'这' => 'zhe',
|
||||
'那' => 'na',
|
||||
'中' => 'zhong',
|
||||
'国' => 'guo',
|
||||
'人' => 'ren',
|
||||
'有' => 'you',
|
||||
'来' => 'lai',
|
||||
'可' => 'ke',
|
||||
'以' => 'yi',
|
||||
'上' => 'shang',
|
||||
'下' => 'xia',
|
||||
'大' => 'da',
|
||||
'小' => 'xiao',
|
||||
'多' => 'duo',
|
||||
'少' => 'shao',
|
||||
'什' => 'shen',
|
||||
'么' => 'me',
|
||||
'时' => 'shi',
|
||||
'间' => 'jian',
|
||||
'地' => 'di',
|
||||
'方' => 'fang',
|
||||
'年' => 'nian',
|
||||
'月' => 'yue',
|
||||
'日' => 'ri',
|
||||
'天' => 'tian',
|
||||
'水' => 'shui',
|
||||
'火' => 'huo',
|
||||
'木' => 'mu',
|
||||
'金' => 'jin',
|
||||
'土' => 'tu',
|
||||
'山' => 'shan',
|
||||
'海' => 'hai',
|
||||
'河' => 'he',
|
||||
'学' => 'xue',
|
||||
'校' => 'xiao',
|
||||
'老' => 'lao',
|
||||
'师' => 'shi',
|
||||
'生' => 'sheng',
|
||||
'活' => 'huo',
|
||||
'工' => 'gong',
|
||||
'作' => 'zuo',
|
||||
'家' => 'jia',
|
||||
'庭' => 'ting',
|
||||
'朋' => 'peng',
|
||||
'友' => 'you',
|
||||
'爱' => 'ai',
|
||||
'情' => 'qing',
|
||||
'心' => 'xin',
|
||||
'想' => 'xiang',
|
||||
'知' => 'zhi',
|
||||
'道' => 'dao',
|
||||
'看' => 'kan',
|
||||
'见' => 'jian',
|
||||
'听' => 'ting',
|
||||
'说' => 'shuo',
|
||||
'话' => 'hua',
|
||||
'言' => 'yan',
|
||||
'文' => 'wen',
|
||||
'字' => 'zi',
|
||||
'书' => 'shu',
|
||||
'读' => 'du',
|
||||
'写' => 'xie',
|
||||
'画' => 'hua',
|
||||
'吃' => 'chi',
|
||||
'喝' => 'he',
|
||||
'睡' => 'shui',
|
||||
'觉' => 'jue',
|
||||
'走' => 'zou',
|
||||
'跑' => 'pao',
|
||||
'飞' => 'fei',
|
||||
'坐' => 'zuo',
|
||||
'站' => 'zhan',
|
||||
'躺' => 'tang',
|
||||
'笑' => 'xiao',
|
||||
'哭' => 'ku',
|
||||
'高' => 'gao',
|
||||
'兴' => 'xing',
|
||||
'快' => 'kuai',
|
||||
'乐' => 'le',
|
||||
'难' => 'nan',
|
||||
'过' => 'guo',
|
||||
'新' => 'xin',
|
||||
'旧' => 'jiu',
|
||||
'长' => 'chang',
|
||||
'短' => 'duan',
|
||||
'宽' => 'kuan',
|
||||
'窄' => 'zhai',
|
||||
'厚' => 'hou',
|
||||
'薄' => 'bao',
|
||||
'深' => 'shen',
|
||||
'浅' => 'qian',
|
||||
'远' => 'yuan',
|
||||
'近' => 'jin',
|
||||
'美' => 'mei',
|
||||
'丽' => 'li',
|
||||
'漂' => 'piao',
|
||||
'亮' => 'liang',
|
||||
'帅' => 'shuai',
|
||||
'聪' => 'cong',
|
||||
'明' => 'ming',
|
||||
'笨' => 'ben',
|
||||
'懒' => 'lan',
|
||||
'勤' => 'qin',
|
||||
'忙' => 'mang',
|
||||
'闲' => 'xian',
|
||||
'累' => 'lei',
|
||||
'轻' => 'qing',
|
||||
'重' => 'zhong',
|
||||
'松' => 'song',
|
||||
'紧' => 'jin',
|
||||
'开' => 'kai',
|
||||
'关' => 'guan',
|
||||
'门' => 'men',
|
||||
'窗' => 'chuang',
|
||||
'户' => 'hu',
|
||||
'房' => 'fang',
|
||||
'子' => 'zi',
|
||||
'屋' => 'wu',
|
||||
'楼' => 'lou',
|
||||
'层' => 'ceng',
|
||||
'街' => 'jie',
|
||||
'路' => 'lu',
|
||||
'桥' => 'qiao',
|
||||
'车' => 'che',
|
||||
'船' => 'chuan',
|
||||
'机' => 'ji',
|
||||
'电' => 'dian',
|
||||
'话' => 'hua',
|
||||
'视' => 'shi',
|
||||
'脑' => 'nao',
|
||||
'手' => 'shou',
|
||||
'网' => 'wang',
|
||||
'络' => 'luo',
|
||||
'游' => 'you',
|
||||
'戏' => 'xi',
|
||||
'音' => 'yin',
|
||||
'影' => 'ying',
|
||||
'唱' => 'chang',
|
||||
'歌' => 'ge',
|
||||
'跳' => 'tiao',
|
||||
'舞' => 'wu',
|
||||
'购' => 'gou',
|
||||
'物' => 'wu',
|
||||
'买' => 'mai',
|
||||
'卖' => 'mai',
|
||||
'钱' => 'qian',
|
||||
'价' => 'jia',
|
||||
'格' => 'ge',
|
||||
'便' => 'bian',
|
||||
'宜' => 'yi',
|
||||
'贵' => 'gui',
|
||||
'医' => 'yi',
|
||||
'院' => 'yuan',
|
||||
'病' => 'bing',
|
||||
'痛' => 'tong',
|
||||
'健' => 'jian',
|
||||
'康' => 'kang',
|
||||
'运' => 'yun',
|
||||
'动' => 'dong',
|
||||
'锻' => 'duan',
|
||||
'炼' => 'lian',
|
||||
'一' => 'yi', '二' => 'er', '三' => 'san', '四' => 'si', '五' => 'wu',
|
||||
'六' => 'liu', '七' => 'qi', '八' => 'ba', '九' => 'jiu', '十' => 'shi',
|
||||
'的' => 'de', '了' => 'le', '是' => 'shi', '我' => 'wo', '你' => 'ni',
|
||||
'他' => 'ta', '她' => 'ta', '它' => 'ta', '好' => 'hao', '很' => 'hen',
|
||||
'都' => 'dou', '会' => 'hui', '个' => 'ge', '这' => 'zhe', '那' => 'na',
|
||||
'中' => 'zhong', '国' => 'guo', '人' => 'ren', '有' => 'you', '来' => 'lai',
|
||||
'可' => 'ke', '以' => 'yi', '上' => 'shang', '下' => 'xia', '大' => 'da',
|
||||
'小' => 'xiao', '多' => 'duo', '少' => 'shao', '什' => 'shen', '么' => 'me',
|
||||
'时' => 'shi', '间' => 'jian', '地' => 'di', '方' => 'fang', '年' => 'nian',
|
||||
'月' => 'yue', '日' => 'ri', '天' => 'tian', '水' => 'shui', '火' => 'huo',
|
||||
'木' => 'mu', '金' => 'jin', '土' => 'tu', '山' => 'shan', '海' => 'hai',
|
||||
'河' => 'he', '学' => 'xue', '校' => 'xiao', '老' => 'lao', '师' => 'shi',
|
||||
'生' => 'sheng', '活' => 'huo', '工' => 'gong', '作' => 'zuo', '家' => 'jia',
|
||||
'庭' => 'ting', '朋' => 'peng', '友' => 'you', '爱' => 'ai', '情' => 'qing',
|
||||
'心' => 'xin', '想' => 'xiang', '知' => 'zhi', '道' => 'dao', '看' => 'kan',
|
||||
'见' => 'jian', '听' => 'ting', '说' => 'shuo', '话' => 'hua', '言' => 'yan',
|
||||
'文' => 'wen', '字' => 'zi', '书' => 'shu', '读' => 'du', '写' => 'xie',
|
||||
'画' => 'hua', '吃' => 'chi', '喝' => 'he', '睡' => 'shui', '觉' => 'jue',
|
||||
'走' => 'zou', '跑' => 'pao', '飞' => 'fei', '坐' => 'zuo', '站' => 'zhan',
|
||||
'躺' => 'tang', '笑' => 'xiao', '哭' => 'ku', '高' => 'gao', '兴' => 'xing',
|
||||
'快' => 'kuai', '乐' => 'le', '难' => 'nan', '过' => 'guo', '新' => 'xin',
|
||||
'旧' => 'jiu', '长' => 'chang', '短' => 'duan', '宽' => 'kuan', '窄' => 'zhai',
|
||||
'厚' => 'hou', '薄' => 'bao', '深' => 'shen', '浅' => 'qian', '远' => 'yuan',
|
||||
'近' => 'jin', '美' => 'mei', '丽' => 'li', '漂' => 'piao', '亮' => 'liang',
|
||||
'帅' => 'shuai', '聪' => 'cong', '明' => 'ming', '笨' => 'ben', '懒' => 'lan',
|
||||
'勤' => 'qin', '忙' => 'mang', '闲' => 'xian', '累' => 'lei', '轻' => 'qing',
|
||||
'重' => 'zhong', '松' => 'song', '紧' => 'jin', '开' => 'kai', '关' => 'guan',
|
||||
'门' => 'men', '窗' => 'chuang', '户' => 'hu', '房' => 'fang', '子' => 'zi',
|
||||
'屋' => 'wu', '楼' => 'lou', '层' => 'ceng', '街' => 'jie', '路' => 'lu',
|
||||
'桥' => 'qiao', '车' => 'che', '船' => 'chuan', '机' => 'ji', '电' => 'dian',
|
||||
'话' => 'hua', '视' => 'shi', '脑' => 'nao', '手' => 'shou', '网' => 'wang',
|
||||
'络' => 'luo', '游' => 'you', '戏' => 'xi', '音' => 'yin', '影' => 'ying',
|
||||
'唱' => 'chang', '歌' => 'ge', '跳' => 'tiao', '舞' => 'wu', '购' => 'gou',
|
||||
'物' => 'wu', '买' => 'mai', '卖' => 'mai', '钱' => 'qian', '价' => 'jia',
|
||||
'格' => 'ge', '便' => 'bian', '宜' => 'yi', '贵' => 'gui', '医' => 'yi',
|
||||
'院' => 'yuan', '病' => 'bing', '痛' => 'tong', '健' => 'jian', '康' => 'kang',
|
||||
'运' => 'yun', '动' => 'dong', '锻' => 'duan', '炼' => 'lian'
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -72,19 +72,16 @@ class WPSlug_Translator {
|
|||
'q' => $text,
|
||||
'source' => $source_lang,
|
||||
'target' => $target_lang,
|
||||
'format' => 'text',
|
||||
'format' => 'text'
|
||||
);
|
||||
|
||||
$response = wp_remote_post(
|
||||
$url,
|
||||
array(
|
||||
$response = wp_remote_post($url, array(
|
||||
'timeout' => 15,
|
||||
'body' => $params,
|
||||
'headers' => array(
|
||||
'User-Agent' => 'WPSlug/' . WPSLUG_VERSION,
|
||||
),
|
||||
'User-Agent' => 'WPSlug/' . WPSLUG_VERSION
|
||||
)
|
||||
);
|
||||
));
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
if (isset($options['debug_mode']) && $options['debug_mode']) {
|
||||
|
|
@ -143,19 +140,16 @@ class WPSlug_Translator {
|
|||
'to' => $target_lang,
|
||||
'appid' => $app_id,
|
||||
'salt' => $salt,
|
||||
'sign' => $sign,
|
||||
'sign' => $sign
|
||||
);
|
||||
|
||||
$response = wp_remote_post(
|
||||
$url,
|
||||
array(
|
||||
$response = wp_remote_post($url, array(
|
||||
'timeout' => 15,
|
||||
'body' => $params,
|
||||
'headers' => array(
|
||||
'User-Agent' => 'WPSlug/' . WPSLUG_VERSION,
|
||||
),
|
||||
'User-Agent' => 'WPSlug/' . WPSLUG_VERSION
|
||||
)
|
||||
);
|
||||
));
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
if (isset($options['debug_mode']) && $options['debug_mode']) {
|
||||
|
|
@ -247,18 +241,13 @@ class WPSlug_Translator {
|
|||
// 5. 调用 WPMind API(语言设置已在前面获取)
|
||||
$start_time = microtime(true);
|
||||
|
||||
$result = wpmind_translate(
|
||||
$text,
|
||||
$source_lang,
|
||||
$target_lang,
|
||||
array(
|
||||
$result = wpmind_translate($text, $source_lang, $target_lang, [
|
||||
'context' => 'wpslug_translation',
|
||||
'format' => 'slug',
|
||||
'cache_ttl' => 86400, // WPMind 内部缓存 1 天
|
||||
'max_tokens' => 100,
|
||||
'temperature' => 0.3,
|
||||
)
|
||||
);
|
||||
]);
|
||||
|
||||
$elapsed_time = round((microtime(true) - $start_time) * 1000);
|
||||
|
||||
|
|
@ -326,7 +315,7 @@ class WPSlug_Translator {
|
|||
$results[] = array(
|
||||
'original' => $item,
|
||||
'translated' => $translated,
|
||||
'service' => isset( $options['translation_service'] ) ? $options['translation_service'] : 'none',
|
||||
'service' => isset($options['translation_service']) ? $options['translation_service'] : 'none'
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
if (isset($options['debug_mode']) && $options['debug_mode']) {
|
||||
|
|
@ -335,7 +324,7 @@ class WPSlug_Translator {
|
|||
$results[] = array(
|
||||
'original' => $item,
|
||||
'translated' => sanitize_title($item),
|
||||
'service' => 'fallback',
|
||||
'service' => 'fallback'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,287 +106,73 @@ class WPSlug_Transliterator {
|
|||
|
||||
private function initCharMaps() {
|
||||
$this->char_maps = array(
|
||||
'А' => 'A',
|
||||
'а' => 'a',
|
||||
'Б' => 'B',
|
||||
'б' => 'b',
|
||||
'В' => 'V',
|
||||
'в' => 'v',
|
||||
'Г' => 'G',
|
||||
'г' => 'g',
|
||||
'Д' => 'D',
|
||||
'д' => 'd',
|
||||
'Е' => 'E',
|
||||
'е' => 'e',
|
||||
'Ё' => 'Yo',
|
||||
'ё' => 'yo',
|
||||
'Ж' => 'Zh',
|
||||
'ж' => 'zh',
|
||||
'З' => 'Z',
|
||||
'з' => 'z',
|
||||
'И' => 'I',
|
||||
'и' => 'i',
|
||||
'Й' => 'J',
|
||||
'й' => 'j',
|
||||
'К' => 'K',
|
||||
'к' => 'k',
|
||||
'Л' => 'L',
|
||||
'л' => 'l',
|
||||
'М' => 'M',
|
||||
'м' => 'm',
|
||||
'Н' => 'N',
|
||||
'н' => 'n',
|
||||
'О' => 'O',
|
||||
'о' => 'o',
|
||||
'П' => 'P',
|
||||
'п' => 'p',
|
||||
'Р' => 'R',
|
||||
'р' => 'r',
|
||||
'С' => 'S',
|
||||
'с' => 's',
|
||||
'Т' => 'T',
|
||||
'т' => 't',
|
||||
'У' => 'U',
|
||||
'у' => 'u',
|
||||
'Ф' => 'F',
|
||||
'ф' => 'f',
|
||||
'Х' => 'H',
|
||||
'х' => 'h',
|
||||
'Ц' => 'C',
|
||||
'ц' => 'c',
|
||||
'Ч' => 'Ch',
|
||||
'ч' => 'ch',
|
||||
'Ш' => 'Sh',
|
||||
'ш' => 'sh',
|
||||
'Щ' => 'Shh',
|
||||
'щ' => 'shh',
|
||||
'Ъ' => '',
|
||||
'ъ' => '',
|
||||
'Ы' => 'Y',
|
||||
'ы' => 'y',
|
||||
'Ь' => '',
|
||||
'ь' => '',
|
||||
'Э' => 'E',
|
||||
'э' => 'e',
|
||||
'Ю' => 'Yu',
|
||||
'ю' => 'yu',
|
||||
'Я' => 'Ya',
|
||||
'я' => 'ya',
|
||||
'А' => 'A', 'а' => 'a', 'Б' => 'B', 'б' => 'b', 'В' => 'V', 'в' => 'v',
|
||||
'Г' => 'G', 'г' => 'g', 'Д' => 'D', 'д' => 'd', 'Е' => 'E', 'е' => 'e',
|
||||
'Ё' => 'Yo', 'ё' => 'yo', 'Ж' => 'Zh', 'ж' => 'zh', 'З' => 'Z', 'з' => 'z',
|
||||
'И' => 'I', 'и' => 'i', 'Й' => 'J', 'й' => 'j', 'К' => 'K', 'к' => 'k',
|
||||
'Л' => 'L', 'л' => 'l', 'М' => 'M', 'м' => 'm', 'Н' => 'N', 'н' => 'n',
|
||||
'О' => 'O', 'о' => 'o', 'П' => 'P', 'п' => 'p', 'Р' => 'R', 'р' => 'r',
|
||||
'С' => 'S', 'с' => 's', 'Т' => 'T', 'т' => 't', 'У' => 'U', 'у' => 'u',
|
||||
'Ф' => 'F', 'ф' => 'f', 'Х' => 'H', 'х' => 'h', 'Ц' => 'C', 'ц' => 'c',
|
||||
'Ч' => 'Ch', 'ч' => 'ch', 'Ш' => 'Sh', 'ш' => 'sh', 'Щ' => 'Shh', 'щ' => 'shh',
|
||||
'Ъ' => '', 'ъ' => '', 'Ы' => 'Y', 'ы' => 'y', 'Ь' => '', 'ь' => '',
|
||||
'Э' => 'E', 'э' => 'e', 'Ю' => 'Yu', 'ю' => 'yu', 'Я' => 'Ya', 'я' => 'ya',
|
||||
|
||||
'ä' => 'ae',
|
||||
'ö' => 'oe',
|
||||
'ü' => 'ue',
|
||||
'ß' => 'ss',
|
||||
'Ä' => 'Ae',
|
||||
'Ö' => 'Oe',
|
||||
'Ü' => 'Ue',
|
||||
'ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'ß' => 'ss',
|
||||
'Ä' => 'Ae', 'Ö' => 'Oe', 'Ü' => 'Ue',
|
||||
|
||||
'à' => 'a',
|
||||
'á' => 'a',
|
||||
'â' => 'a',
|
||||
'ã' => 'a',
|
||||
'ä' => 'a',
|
||||
'å' => 'a',
|
||||
'À' => 'A',
|
||||
'Á' => 'A',
|
||||
'Â' => 'A',
|
||||
'Ã' => 'A',
|
||||
'Ä' => 'A',
|
||||
'Å' => 'A',
|
||||
'è' => 'e',
|
||||
'é' => 'e',
|
||||
'ê' => 'e',
|
||||
'ë' => 'e',
|
||||
'È' => 'E',
|
||||
'É' => 'E',
|
||||
'Ê' => 'E',
|
||||
'Ë' => 'E',
|
||||
'ì' => 'i',
|
||||
'í' => 'i',
|
||||
'î' => 'i',
|
||||
'ï' => 'i',
|
||||
'Ì' => 'I',
|
||||
'Í' => 'I',
|
||||
'Î' => 'I',
|
||||
'Ï' => 'I',
|
||||
'ò' => 'o',
|
||||
'ó' => 'o',
|
||||
'ô' => 'o',
|
||||
'õ' => 'o',
|
||||
'ö' => 'o',
|
||||
'ø' => 'o',
|
||||
'Ò' => 'O',
|
||||
'Ó' => 'O',
|
||||
'Ô' => 'O',
|
||||
'Õ' => 'O',
|
||||
'Ö' => 'O',
|
||||
'Ø' => 'O',
|
||||
'ù' => 'u',
|
||||
'ú' => 'u',
|
||||
'û' => 'u',
|
||||
'ü' => 'u',
|
||||
'Ù' => 'U',
|
||||
'Ú' => 'U',
|
||||
'Û' => 'U',
|
||||
'Ü' => 'U',
|
||||
'ý' => 'y',
|
||||
'ÿ' => 'y',
|
||||
'Ý' => 'Y',
|
||||
'Ÿ' => 'Y',
|
||||
'ñ' => 'n',
|
||||
'Ñ' => 'N',
|
||||
'ç' => 'c',
|
||||
'Ç' => 'C',
|
||||
'æ' => 'ae',
|
||||
'Æ' => 'AE',
|
||||
'œ' => 'oe',
|
||||
'Œ' => 'OE',
|
||||
'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a', 'å' => 'a',
|
||||
'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A',
|
||||
'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e',
|
||||
'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E',
|
||||
'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i',
|
||||
'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I',
|
||||
'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ø' => 'o',
|
||||
'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O', 'Ø' => 'O',
|
||||
'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u',
|
||||
'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U',
|
||||
'ý' => 'y', 'ÿ' => 'y', 'Ý' => 'Y', 'Ÿ' => 'Y',
|
||||
'ñ' => 'n', 'Ñ' => 'N',
|
||||
'ç' => 'c', 'Ç' => 'C',
|
||||
'æ' => 'ae', 'Æ' => 'AE',
|
||||
'œ' => 'oe', 'Œ' => 'OE',
|
||||
|
||||
'ā' => 'a',
|
||||
'ē' => 'e',
|
||||
'ī' => 'i',
|
||||
'ō' => 'o',
|
||||
'ū' => 'u',
|
||||
'Ā' => 'A',
|
||||
'Ē' => 'E',
|
||||
'Ī' => 'I',
|
||||
'Ō' => 'O',
|
||||
'Ū' => 'U',
|
||||
'ā' => 'a', 'ē' => 'e', 'ī' => 'i', 'ō' => 'o', 'ū' => 'u',
|
||||
'Ā' => 'A', 'Ē' => 'E', 'Ī' => 'I', 'Ō' => 'O', 'Ū' => 'U',
|
||||
|
||||
'ą' => 'a',
|
||||
'ć' => 'c',
|
||||
'ę' => 'e',
|
||||
'ł' => 'l',
|
||||
'ń' => 'n',
|
||||
'ó' => 'o',
|
||||
'ś' => 's',
|
||||
'ź' => 'z',
|
||||
'ż' => 'z',
|
||||
'Ą' => 'A',
|
||||
'Ć' => 'C',
|
||||
'Ę' => 'E',
|
||||
'Ł' => 'L',
|
||||
'Ń' => 'N',
|
||||
'Ó' => 'O',
|
||||
'Ś' => 'S',
|
||||
'Ź' => 'Z',
|
||||
'Ż' => 'Z',
|
||||
'ą' => 'a', 'ć' => 'c', 'ę' => 'e', 'ł' => 'l', 'ń' => 'n',
|
||||
'ó' => 'o', 'ś' => 's', 'ź' => 'z', 'ż' => 'z',
|
||||
'Ą' => 'A', 'Ć' => 'C', 'Ę' => 'E', 'Ł' => 'L', 'Ń' => 'N',
|
||||
'Ó' => 'O', 'Ś' => 'S', 'Ź' => 'Z', 'Ż' => 'Z',
|
||||
|
||||
'ă' => 'a',
|
||||
'î' => 'i',
|
||||
'ș' => 's',
|
||||
'ț' => 't',
|
||||
'Ă' => 'A',
|
||||
'Î' => 'I',
|
||||
'Ș' => 'S',
|
||||
'Ț' => 'T',
|
||||
'ă' => 'a', 'î' => 'i', 'ș' => 's', 'ț' => 't',
|
||||
'Ă' => 'A', 'Î' => 'I', 'Ș' => 'S', 'Ț' => 'T',
|
||||
|
||||
'α' => 'a',
|
||||
'β' => 'b',
|
||||
'γ' => 'g',
|
||||
'δ' => 'd',
|
||||
'ε' => 'e',
|
||||
'ζ' => 'z',
|
||||
'η' => 'i',
|
||||
'θ' => 'th',
|
||||
'ι' => 'i',
|
||||
'κ' => 'k',
|
||||
'λ' => 'l',
|
||||
'μ' => 'm',
|
||||
'ν' => 'n',
|
||||
'ξ' => 'x',
|
||||
'ο' => 'o',
|
||||
'π' => 'p',
|
||||
'ρ' => 'r',
|
||||
'σ' => 's',
|
||||
'τ' => 't',
|
||||
'υ' => 'y',
|
||||
'φ' => 'f',
|
||||
'χ' => 'ch',
|
||||
'ψ' => 'ps',
|
||||
'ω' => 'o',
|
||||
'Α' => 'A',
|
||||
'Β' => 'B',
|
||||
'Γ' => 'G',
|
||||
'Δ' => 'D',
|
||||
'Ε' => 'E',
|
||||
'Ζ' => 'Z',
|
||||
'Η' => 'I',
|
||||
'Θ' => 'TH',
|
||||
'Ι' => 'I',
|
||||
'Κ' => 'K',
|
||||
'Λ' => 'L',
|
||||
'Μ' => 'M',
|
||||
'Ν' => 'N',
|
||||
'Ξ' => 'X',
|
||||
'Ο' => 'O',
|
||||
'Π' => 'P',
|
||||
'Ρ' => 'R',
|
||||
'Σ' => 'S',
|
||||
'Τ' => 'T',
|
||||
'Υ' => 'Y',
|
||||
'Φ' => 'F',
|
||||
'Χ' => 'CH',
|
||||
'Ψ' => 'PS',
|
||||
'Ω' => 'O',
|
||||
'α' => 'a', 'β' => 'b', 'γ' => 'g', 'δ' => 'd', 'ε' => 'e',
|
||||
'ζ' => 'z', 'η' => 'i', 'θ' => 'th', 'ι' => 'i', 'κ' => 'k',
|
||||
'λ' => 'l', 'μ' => 'm', 'ν' => 'n', 'ξ' => 'x', 'ο' => 'o',
|
||||
'π' => 'p', 'ρ' => 'r', 'σ' => 's', 'τ' => 't', 'υ' => 'y',
|
||||
'φ' => 'f', 'χ' => 'ch', 'ψ' => 'ps', 'ω' => 'o',
|
||||
'Α' => 'A', 'Β' => 'B', 'Γ' => 'G', 'Δ' => 'D', 'Ε' => 'E',
|
||||
'Ζ' => 'Z', 'Η' => 'I', 'Θ' => 'TH', 'Ι' => 'I', 'Κ' => 'K',
|
||||
'Λ' => 'L', 'Μ' => 'M', 'Ν' => 'N', 'Ξ' => 'X', 'Ο' => 'O',
|
||||
'Π' => 'P', 'Ρ' => 'R', 'Σ' => 'S', 'Τ' => 'T', 'Υ' => 'Y',
|
||||
'Φ' => 'F', 'Χ' => 'CH', 'Ψ' => 'PS', 'Ω' => 'O',
|
||||
|
||||
'ã' => 'a',
|
||||
'õ' => 'o',
|
||||
'ç' => 'c',
|
||||
'Ã' => 'A',
|
||||
'Õ' => 'O',
|
||||
'Ç' => 'C',
|
||||
'ã' => 'a', 'õ' => 'o', 'ç' => 'c',
|
||||
'Ã' => 'A', 'Õ' => 'O', 'Ç' => 'C',
|
||||
|
||||
'ğ' => 'g',
|
||||
'ı' => 'i',
|
||||
'ş' => 's',
|
||||
'ü' => 'u',
|
||||
'ö' => 'o',
|
||||
'ç' => 'c',
|
||||
'Ğ' => 'G',
|
||||
'İ' => 'I',
|
||||
'Ş' => 'S',
|
||||
'Ü' => 'U',
|
||||
'Ö' => 'O',
|
||||
'Ç' => 'C',
|
||||
'ğ' => 'g', 'ı' => 'i', 'ş' => 's', 'ü' => 'u', 'ö' => 'o', 'ç' => 'c',
|
||||
'Ğ' => 'G', 'İ' => 'I', 'Ş' => 'S', 'Ü' => 'U', 'Ö' => 'O', 'Ç' => 'C',
|
||||
|
||||
'ک' => 'k',
|
||||
'گ' => 'g',
|
||||
'چ' => 'ch',
|
||||
'پ' => 'p',
|
||||
'ژ' => 'zh',
|
||||
'ی' => 'y',
|
||||
'ء' => 'a',
|
||||
'ؤ' => 'w',
|
||||
'ئ' => 'y',
|
||||
'ة' => 'h',
|
||||
'ا' => 'a',
|
||||
'ب' => 'b',
|
||||
'ت' => 't',
|
||||
'ث' => 'th',
|
||||
'ج' => 'j',
|
||||
'ح' => 'h',
|
||||
'خ' => 'kh',
|
||||
'د' => 'd',
|
||||
'ذ' => 'dh',
|
||||
'ر' => 'r',
|
||||
'ز' => 'z',
|
||||
'س' => 's',
|
||||
'ش' => 'sh',
|
||||
'ص' => 's',
|
||||
'ض' => 'd',
|
||||
'ط' => 't',
|
||||
'ظ' => 'dh',
|
||||
'ع' => 'a',
|
||||
'غ' => 'gh',
|
||||
'ف' => 'f',
|
||||
'ق' => 'q',
|
||||
'ل' => 'l',
|
||||
'م' => 'm',
|
||||
'ن' => 'n',
|
||||
'ه' => 'h',
|
||||
'و' => 'w',
|
||||
'ي' => 'y',
|
||||
'ک' => 'k', 'گ' => 'g', 'چ' => 'ch', 'پ' => 'p', 'ژ' => 'zh',
|
||||
'ی' => 'y', 'ء' => 'a', 'ؤ' => 'w', 'ئ' => 'y', 'ة' => 'h',
|
||||
'ا' => 'a', 'ب' => 'b', 'ت' => 't', 'ث' => 'th', 'ج' => 'j',
|
||||
'ح' => 'h', 'خ' => 'kh', 'د' => 'd', 'ذ' => 'dh', 'ر' => 'r',
|
||||
'ز' => 'z', 'س' => 's', 'ش' => 'sh', 'ص' => 's', 'ض' => 'd',
|
||||
'ط' => 't', 'ظ' => 'dh', 'ع' => 'a', 'غ' => 'gh', 'ف' => 'f',
|
||||
'ق' => 'q', 'ل' => 'l', 'م' => 'm', 'ن' => 'n', 'ه' => 'h',
|
||||
'و' => 'w', 'ي' => 'y'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,39 +87,9 @@ class WPSlug_Validator {
|
|||
|
||||
public static function validateLanguageCode($code) {
|
||||
$valid_codes = array(
|
||||
'auto',
|
||||
'zh',
|
||||
'zh-TW',
|
||||
'en',
|
||||
'es',
|
||||
'fr',
|
||||
'de',
|
||||
'ja',
|
||||
'ko',
|
||||
'ru',
|
||||
'ar',
|
||||
'it',
|
||||
'pt',
|
||||
'nl',
|
||||
'pl',
|
||||
'tr',
|
||||
'sv',
|
||||
'da',
|
||||
'no',
|
||||
'fi',
|
||||
'cs',
|
||||
'hu',
|
||||
'ro',
|
||||
'bg',
|
||||
'hr',
|
||||
'sk',
|
||||
'sl',
|
||||
'et',
|
||||
'lv',
|
||||
'lt',
|
||||
'mt',
|
||||
'el',
|
||||
'cy',
|
||||
'auto', 'zh', 'zh-TW', 'en', 'es', 'fr', 'de', 'ja', 'ko', 'ru',
|
||||
'ar', 'it', 'pt', 'nl', 'pl', 'tr', 'sv', 'da', 'no', 'fi', 'cs',
|
||||
'hu', 'ro', 'bg', 'hr', 'sk', 'sl', 'et', 'lv', 'lt', 'mt', 'el', 'cy'
|
||||
);
|
||||
|
||||
return in_array($code, $valid_codes, true) ? $code : 'auto';
|
||||
|
|
@ -207,7 +177,7 @@ class WPSlug_Validator {
|
|||
'php_version' => version_compare(PHP_VERSION, '7.0', '>='),
|
||||
'wordpress_version' => version_compare(get_bloginfo('version'), '5.0', '>='),
|
||||
'mbstring_extension' => extension_loaded('mbstring'),
|
||||
'json_extension' => extension_loaded( 'json' ),
|
||||
'json_extension' => extension_loaded('json')
|
||||
);
|
||||
|
||||
$errors = array();
|
||||
|
|
|
|||
39
phpcs.xml
39
phpcs.xml
|
|
@ -1,39 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<ruleset name="WPSlug">
|
||||
<description>WPSlug 项目代码规范</description>
|
||||
|
||||
<file>.</file>
|
||||
<arg name="extensions" value="php"/>
|
||||
<arg name="warning-severity" value="0"/>
|
||||
<exclude-pattern>vendor/*</exclude-pattern>
|
||||
<exclude-pattern>node_modules/*</exclude-pattern>
|
||||
<exclude-pattern>tests/*</exclude-pattern>
|
||||
<exclude-pattern>lib/*</exclude-pattern>
|
||||
<exclude-pattern>languages/*</exclude-pattern>
|
||||
|
||||
<rule ref="WordPress-Extra">
|
||||
<!-- 项目使用 camelCase 方法名 -->
|
||||
<exclude name="WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid"/>
|
||||
<!-- 不强制 Yoda 条件 -->
|
||||
<exclude name="WordPress.PHP.YodaConditions.NotYoda"/>
|
||||
<!-- 允许 error_log -->
|
||||
<exclude name="WordPress.PHP.DevelopmentFunctions.error_log_error_log"/>
|
||||
<!-- 允许短三元运算符 -->
|
||||
<exclude name="Universal.Operators.DisallowShortTernary.Found"/>
|
||||
<!-- _e() 是 WordPress 标准翻译函数 -->
|
||||
<exclude name="WordPress.Security.EscapeOutput.UnsafePrintingFunction"/>
|
||||
<!-- 拼音字典允许重复键 -->
|
||||
<exclude name="Universal.Arrays.DuplicateArrayKey.Found"/>
|
||||
<!-- Nonce 验证由其他机制处理 -->
|
||||
<exclude name="WordPress.Security.NonceVerification.Recommended"/>
|
||||
<exclude name="WordPress.Security.NonceVerification.Missing"/>
|
||||
<!-- 主文件结构是 WordPress 插件标准格式 -->
|
||||
<exclude name="WordPress.Files.FileName.InvalidClassFileName"/>
|
||||
<exclude name="Universal.Files.SeparateFunctionsFromOO.Mixed"/>
|
||||
</rule>
|
||||
|
||||
<!-- 输出转义降为 warning,不阻断 CI -->
|
||||
<rule ref="WordPress.Security.EscapeOutput.OutputNotEscaped">
|
||||
<type>warning</type>
|
||||
</rule>
|
||||
</ruleset>
|
||||
124
wpslug.php
124
wpslug.php
|
|
@ -15,37 +15,40 @@ Requires at least: 6.0
|
|||
Requires PHP: 7.4
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
if (!defined("ABSPATH")) {
|
||||
exit();
|
||||
}
|
||||
|
||||
define( 'WPSLUG_VERSION', '1.1.0' );
|
||||
define( 'WPSLUG_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||
define( 'WPSLUG_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
||||
define( 'WPSLUG_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
|
||||
|
||||
class WPSlug {
|
||||
define("WPSLUG_VERSION", "1.1.0");
|
||||
define("WPSLUG_PLUGIN_DIR", plugin_dir_path(__FILE__));
|
||||
define("WPSLUG_PLUGIN_URL", plugin_dir_url(__FILE__));
|
||||
define("WPSLUG_PLUGIN_BASENAME", plugin_basename(__FILE__));
|
||||
|
||||
class WPSlug
|
||||
{
|
||||
private static $instance = null;
|
||||
private $core = null;
|
||||
|
||||
public static function getInstance() {
|
||||
public static function getInstance()
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function __construct() {
|
||||
add_action( 'plugins_loaded', array( $this, 'loadPlugin' ) );
|
||||
register_activation_hook( __FILE__, array( $this, 'activate' ) );
|
||||
register_deactivation_hook( __FILE__, array( $this, 'deactivate' ) );
|
||||
register_uninstall_hook( __FILE__, array( 'WPSlug', 'uninstall' ) );
|
||||
private function __construct()
|
||||
{
|
||||
add_action("plugins_loaded", [$this, "loadPlugin"]);
|
||||
register_activation_hook(__FILE__, [$this, "activate"]);
|
||||
register_deactivation_hook(__FILE__, [$this, "deactivate"]);
|
||||
register_uninstall_hook(__FILE__, ["WPSlug", "uninstall"]);
|
||||
|
||||
add_action( 'init', array( $this, 'initLanguages' ) );
|
||||
add_action('init', [$this, 'initLanguages']);
|
||||
}
|
||||
|
||||
public function loadPlugin() {
|
||||
public function loadPlugin()
|
||||
{
|
||||
if (!$this->checkRequirements()) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -56,51 +59,48 @@ class WPSlug {
|
|||
new WenPai_Updater( WPSLUG_PLUGIN_BASENAME, WPSLUG_VERSION );
|
||||
}
|
||||
|
||||
private function checkRequirements() {
|
||||
if ( version_compare( PHP_VERSION, '7.0', '<' ) ) {
|
||||
add_action(
|
||||
'admin_notices',
|
||||
function () {
|
||||
private function checkRequirements()
|
||||
{
|
||||
if (version_compare(PHP_VERSION, "7.0", "<")) {
|
||||
add_action('admin_notices', function() {
|
||||
echo '<div class="notice notice-error"><p>';
|
||||
echo esc_html__('WP Slug requires PHP 7.0 or higher. Please upgrade your PHP version.', 'wpslug');
|
||||
echo '</p></div>';
|
||||
}
|
||||
);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( version_compare( get_bloginfo( 'version' ), '5.0', '<' ) ) {
|
||||
add_action(
|
||||
'admin_notices',
|
||||
function () {
|
||||
if (version_compare(get_bloginfo("version"), "5.0", "<")) {
|
||||
add_action('admin_notices', function() {
|
||||
echo '<div class="notice notice-error"><p>';
|
||||
echo esc_html__('WP Slug requires WordPress 5.0 or higher. Please upgrade your WordPress version.', 'wpslug');
|
||||
echo '</p></div>';
|
||||
}
|
||||
);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function loadDependencies() {
|
||||
require_once WPSLUG_PLUGIN_DIR . 'includes/class-wpslug-validator.php';
|
||||
require_once WPSLUG_PLUGIN_DIR . 'includes/class-wpslug-settings.php';
|
||||
require_once WPSLUG_PLUGIN_DIR . 'includes/class-wpslug-pinyin.php';
|
||||
require_once WPSLUG_PLUGIN_DIR . 'includes/class-wpslug-optimizer.php';
|
||||
require_once WPSLUG_PLUGIN_DIR . 'includes/class-wpslug-transliterator.php';
|
||||
require_once WPSLUG_PLUGIN_DIR . 'includes/class-wpslug-translator.php';
|
||||
require_once WPSLUG_PLUGIN_DIR . 'includes/class-wpslug-converter.php';
|
||||
require_once WPSLUG_PLUGIN_DIR . 'includes/class-wpslug-core.php';
|
||||
require_once WPSLUG_PLUGIN_DIR . 'includes/class-wenpai-updater.php';
|
||||
private function loadDependencies()
|
||||
{
|
||||
require_once WPSLUG_PLUGIN_DIR . "includes/class-wpslug-validator.php";
|
||||
require_once WPSLUG_PLUGIN_DIR . "includes/class-wpslug-settings.php";
|
||||
require_once WPSLUG_PLUGIN_DIR . "includes/class-wpslug-pinyin.php";
|
||||
require_once WPSLUG_PLUGIN_DIR . "includes/class-wpslug-optimizer.php";
|
||||
require_once WPSLUG_PLUGIN_DIR . "includes/class-wpslug-transliterator.php";
|
||||
require_once WPSLUG_PLUGIN_DIR . "includes/class-wpslug-translator.php";
|
||||
require_once WPSLUG_PLUGIN_DIR . "includes/class-wpslug-converter.php";
|
||||
require_once WPSLUG_PLUGIN_DIR . "includes/class-wpslug-core.php";
|
||||
require_once WPSLUG_PLUGIN_DIR . "includes/class-wenpai-updater.php";
|
||||
|
||||
if (is_admin()) {
|
||||
require_once WPSLUG_PLUGIN_DIR . 'includes/class-wpslug-admin.php';
|
||||
require_once WPSLUG_PLUGIN_DIR . "includes/class-wpslug-admin.php";
|
||||
}
|
||||
}
|
||||
|
||||
public function initLanguages() {
|
||||
public function initLanguages()
|
||||
{
|
||||
$locale = apply_filters('plugin_locale', get_locale(), 'wpslug');
|
||||
$mo_file = WPSLUG_PLUGIN_DIR . "languages/wpslug-{$locale}.mo";
|
||||
|
||||
|
|
@ -109,17 +109,19 @@ class WPSlug {
|
|||
}
|
||||
}
|
||||
|
||||
public function loadTextdomain() {
|
||||
public function loadTextdomain()
|
||||
{
|
||||
load_plugin_textdomain(
|
||||
'wpslug',
|
||||
"wpslug",
|
||||
false,
|
||||
dirname( plugin_basename( __FILE__ ) ) . '/languages/'
|
||||
dirname(plugin_basename(__FILE__)) . "/languages/"
|
||||
);
|
||||
}
|
||||
|
||||
public function activate() {
|
||||
if ( ! function_exists( 'is_plugin_active' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
public function activate()
|
||||
{
|
||||
if (!function_exists("is_plugin_active")) {
|
||||
require_once ABSPATH . "wp-admin/includes/plugin.php";
|
||||
}
|
||||
|
||||
if (!$this->checkRequirements()) {
|
||||
|
|
@ -152,7 +154,8 @@ class WPSlug {
|
|||
}
|
||||
}
|
||||
|
||||
public function deactivate() {
|
||||
public function deactivate()
|
||||
{
|
||||
try {
|
||||
if ($this->core) {
|
||||
$this->core->deactivate();
|
||||
|
|
@ -165,9 +168,10 @@ class WPSlug {
|
|||
}
|
||||
}
|
||||
|
||||
public static function uninstall() {
|
||||
public static function uninstall()
|
||||
{
|
||||
try {
|
||||
if ( class_exists( 'WPSlug_Settings' ) ) {
|
||||
if (class_exists("WPSlug_Settings")) {
|
||||
$settings = new WPSlug_Settings();
|
||||
$settings->uninstall();
|
||||
}
|
||||
|
|
@ -179,25 +183,29 @@ class WPSlug {
|
|||
}
|
||||
}
|
||||
|
||||
public function getCore() {
|
||||
public function getCore()
|
||||
{
|
||||
return $this->core;
|
||||
}
|
||||
|
||||
public function getSettings() {
|
||||
public function getSettings()
|
||||
{
|
||||
if ($this->core) {
|
||||
return $this->core->getSettings();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getConverter() {
|
||||
public function getConverter()
|
||||
{
|
||||
if ($this->core) {
|
||||
return $this->core->getConverter();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getOptimizer() {
|
||||
public function getOptimizer()
|
||||
{
|
||||
if ($this->core) {
|
||||
return $this->core->getOptimizer();
|
||||
}
|
||||
|
|
@ -205,14 +213,13 @@ class WPSlug {
|
|||
}
|
||||
}
|
||||
|
||||
function wpslug() {
|
||||
function wpslug()
|
||||
{
|
||||
return WPSlug::getInstance();
|
||||
}
|
||||
|
||||
if (is_admin()) {
|
||||
add_action(
|
||||
'admin_init',
|
||||
function () {
|
||||
add_action('admin_init', function() {
|
||||
if (get_option('wpslug_activation_redirect', false)) {
|
||||
delete_option('wpslug_activation_redirect');
|
||||
if (!isset($_GET['activate-multi'])) {
|
||||
|
|
@ -220,8 +227,7 @@ if ( is_admin() ) {
|
|||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
wpslug();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue