Compare commits

...

No commits in common. "dd8fa5f82e2c966498fc08f3c2ed4fc8c2eff9c8" and "bd3a4379bd79c45308cccac13260f3f6329c910a" have entirely different histories.

22 changed files with 4878 additions and 6286 deletions

View file

@ -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 "标签添加完成"

View file

@ -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 扫描通过,未发现密钥泄露。"

View file

@ -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"

View file

@ -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 "容器镜像安全扫描通过"

View file

@ -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 "清理完成"

View file

@ -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 扫描通过,未发现高危漏洞。

View file

@ -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 扫描通过"

View file

@ -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
View file

@ -0,0 +1,5 @@
vendor/
node_modules/
.env
*.log
.DS_Store

View file

@ -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

View file

@ -1,6 +1,6 @@
<?php

if ( ! defined( 'ABSPATH' ) ) {
if (!defined('ABSPATH')) {
exit;
}

@ -17,63 +17,60 @@ class WPSlug_Converter {
$this->settings = new WPSlug_Settings();
}

public function convert( $text, $options = array() ) {
if ( empty( $text ) ) {
public function convert($text, $options = array()) {
if (empty($text)) {
return '';
}

$mode = isset( $options['conversion_mode'] ) ? $options['conversion_mode'] : 'pinyin';
$start_time = microtime( true );
$mode = isset($options['conversion_mode']) ? $options['conversion_mode'] : 'pinyin';
$start_time = microtime(true);
try {
switch ( $mode ) {
switch ($mode) {
case 'pinyin':
$result = $this->convertPinyin( $text, $options );
$result = $this->convertPinyin($text, $options);
break;
case 'semantic_pinyin':
$result = $this->convertSemanticPinyin( $text, $options );
$result = $this->convertSemanticPinyin($text, $options);
break;
case 'transliteration':
$result = $this->convertTransliteration( $text, $options );
$result = $this->convertTransliteration($text, $options);
break;
case 'translation':
$result = $this->convertTranslation( $text, $options );
$result = $this->convertTranslation($text, $options);
break;
default:
$result = $this->convertPinyin( $text, $options );
$result = $this->convertPinyin($text, $options);
}
$execution_time = microtime( true ) - $start_time;
$this->settings->updateConversionStats( $mode, true, $execution_time );
$execution_time = microtime(true) - $start_time;
$this->settings->updateConversionStats($mode, true, $execution_time);
return $result;
} 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(
} 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(
'text' => $text,
'mode' => $mode,
'options' => $options,
)
);
'options' => $options
));
return $this->cleanBasicSlug( $text, $options );
return $this->cleanBasicSlug($text, $options);
}
}

private function convertPinyin( $text, $options ) {
private function convertPinyin($text, $options) {
try {
if ( $this->detectLanguage( $text ) === 'zh' ) {
return $this->pinyin->convertToPinyin( $text, $options );
if ($this->detectLanguage($text) === 'zh') {
return $this->pinyin->convertToPinyin($text, $options);
} else {
return $this->cleanBasicSlug( $text, $options );
return $this->cleanBasicSlug($text, $options);
}
} catch ( Exception $e ) {
$this->settings->logError( 'Pinyin conversion error: ' . $e->getMessage() );
return $this->cleanBasicSlug( $text, $options );
} catch (Exception $e) {
$this->settings->logError('Pinyin conversion error: ' . $e->getMessage());
return $this->cleanBasicSlug($text, $options);
}
}

@ -83,171 +80,168 @@ class WPSlug_Converter {
* 与普通拼音不同,这个方法会按词语分隔而非按字分隔
* 例如 "你好世界" → "nihao-shijie" 而非 "ni-hao-shi-jie"
*/
private function convertSemanticPinyin( $text, $options ) {
$debug_mode = isset( $options['debug_mode'] ) && $options['debug_mode'];
private function convertSemanticPinyin($text, $options) {
$debug_mode = isset($options['debug_mode']) && $options['debug_mode'];
try {
// 1. 检查是否是中文
if ( $this->detectLanguage( $text ) !== 'zh' ) {
return $this->cleanBasicSlug( $text, $options );
if ($this->detectLanguage($text) !== 'zh') {
return $this->cleanBasicSlug($text, $options);
}

// 2. 检查 WPMind 是否可用
if ( ! function_exists( 'wpmind_pinyin' ) || ! function_exists( 'wpmind_is_available' ) || ! wpmind_is_available() ) {
if ( $debug_mode ) {
error_log( '[WPSlug] WPMind not available for semantic pinyin, falling back to regular pinyin' );
if (!function_exists('wpmind_pinyin') || !function_exists('wpmind_is_available') || !wpmind_is_available()) {
if ($debug_mode) {
error_log('[WPSlug] WPMind not available for semantic pinyin, falling back to regular pinyin');
}
return $this->convertPinyin( $text, $options );
return $this->convertPinyin($text, $options);
}

// 3. 文本长度限制(避免超时)
if ( mb_strlen( $text ) > 200 ) {
if ( $debug_mode ) {
error_log( '[WPSlug] Text too long for semantic pinyin, using regular pinyin' );
if (mb_strlen($text) > 200) {
if ($debug_mode) {
error_log('[WPSlug] Text too long for semantic pinyin, using regular pinyin');
}
return $this->convertPinyin( $text, $options );
return $this->convertPinyin($text, $options);
}

// 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 ) ) {
if ( $debug_mode ) {
error_log( '[WPSlug] WPMind pinyin error: ' . $result->get_error_message() );
if (is_wp_error($result)) {
if ($debug_mode) {
error_log('[WPSlug] WPMind pinyin error: ' . $result->get_error_message());
}
return $this->convertPinyin( $text, $options );
return $this->convertPinyin($text, $options);
}

// 6. 清理结果
$slug = $this->cleanSemanticPinyinResult( $result, $options );
$slug = $this->cleanSemanticPinyinResult($result, $options);

if ( empty( $slug ) ) {
if ( $debug_mode ) {
error_log( '[WPSlug] WPMind returned empty pinyin, using regular pinyin' );
if (empty($slug)) {
if ($debug_mode) {
error_log('[WPSlug] WPMind returned empty pinyin, using regular pinyin');
}
return $this->convertPinyin( $text, $options );
return $this->convertPinyin($text, $options);
}

if ( $debug_mode ) {
error_log( '[WPSlug] Semantic pinyin: "' . $text . '" → "' . $slug . '"' );
if ($debug_mode) {
error_log('[WPSlug] Semantic pinyin: "' . $text . '" → "' . $slug . '"');
}

return $slug;

} catch ( Exception $e ) {
$this->settings->logError( 'Semantic pinyin error: ' . $e->getMessage() );
return $this->convertPinyin( $text, $options );
} catch (Exception $e) {
$this->settings->logError('Semantic pinyin error: ' . $e->getMessage());
return $this->convertPinyin($text, $options);
}
}

/**
* 清理语义化拼音结果
*/
private function cleanSemanticPinyinResult( $text, $options ) {
private function cleanSemanticPinyinResult($text, $options) {
// 移除可能的引号和空格
$text = trim( $text, " \t\n\r\0\x0B\"'" );
$text = trim($text, " \t\n\r\0\x0B\"'");
// 确保只有小写字母、数字和连字符
$text = preg_replace( '/[^a-zA-Z0-9\-]/', '-', $text );
$text = strtolower( $text );
$text = preg_replace( '/-+/', '-', $text );
$text = trim( $text, '-' );
$text = preg_replace('/[^a-zA-Z0-9\-]/', '-', $text);
$text = strtolower($text);
$text = preg_replace('/-+/', '-', $text);
$text = trim($text, '-');
return $text;
}

private function convertTransliteration( $text, $options ) {
private function convertTransliteration($text, $options) {
try {
$detected_lang = $this->detectLanguage( $text );
$detected_lang = $this->detectLanguage($text);
if ( in_array( $detected_lang, array( 'ru', 'ar', 'el', 'he' ) ) ) {
return $this->transliterator->transliterate( $text, $options );
if (in_array($detected_lang, array('ru', 'ar', 'el', 'he'))) {
return $this->transliterator->transliterate($text, $options);
} else {
return $this->cleanBasicSlug( $text, $options );
return $this->cleanBasicSlug($text, $options);
}
} catch ( Exception $e ) {
$this->settings->logError( 'Transliteration error: ' . $e->getMessage() );
return $this->cleanBasicSlug( $text, $options );
} catch (Exception $e) {
$this->settings->logError('Transliteration error: ' . $e->getMessage());
return $this->cleanBasicSlug($text, $options);
}
}

private function convertTranslation( $text, $options ) {
private function convertTranslation($text, $options) {
try {
$service = isset( $options['translation_service'] ) ? $options['translation_service'] : 'none';
$service = isset($options['translation_service']) ? $options['translation_service'] : 'none';
if ( $service === 'none' ) {
return $this->convertPinyin( $text, $options );
if ($service === 'none') {
return $this->convertPinyin($text, $options);
}
$detected_lang = $this->detectLanguage( $text );
$target_lang = isset( $options['translation_target_lang'] ) ? $options['translation_target_lang'] : 'en';
$detected_lang = $this->detectLanguage($text);
$target_lang = isset($options['translation_target_lang']) ? $options['translation_target_lang'] : 'en';
if ( $detected_lang === $target_lang ) {
return $this->cleanBasicSlug( $text, $options );
if ($detected_lang === $target_lang) {
return $this->cleanBasicSlug($text, $options);
}
return $this->translator->translate( $text, $options );
} catch ( Exception $e ) {
$this->settings->logError( 'Translation error: ' . $e->getMessage() );
return $this->convertPinyin( $text, $options );
return $this->translator->translate($text, $options);
} catch (Exception $e) {
$this->settings->logError('Translation error: ' . $e->getMessage());
return $this->convertPinyin($text, $options);
}
}

private function cleanBasicSlug( $text, $options ) {
$text = strip_tags( $text );
$text = html_entity_decode( $text, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
$text = preg_replace( '/&.+?;/', '', $text );
$text = preg_replace( '/[^a-zA-Z0-9\s\-_\p{L}\p{N}]/u', '', $text );
$text = preg_replace( '/\s+/', '-', $text );
$text = preg_replace( '/\-+/', '-', $text );
$text = trim( $text, '-_' );
private function cleanBasicSlug($text, $options) {
$text = strip_tags($text);
$text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$text = preg_replace('/&.+?;/', '', $text);
$text = preg_replace('/[^a-zA-Z0-9\s\-_\p{L}\p{N}]/u', '', $text);
$text = preg_replace('/\s+/', '-', $text);
$text = preg_replace('/\-+/', '-', $text);
$text = trim($text, '-_');
if ( isset( $options['force_lowercase'] ) && $options['force_lowercase'] ) {
$text = strtolower( $text );
if (isset($options['force_lowercase']) && $options['force_lowercase']) {
$text = strtolower($text);
}
return $text;
}

public function detectLanguage( $text ) {
if ( preg_match( '/[\x{4e00}-\x{9fff}]/u', $text ) ) {
public function detectLanguage($text) {
if (preg_match('/[\x{4e00}-\x{9fff}]/u', $text)) {
return 'zh';
}
if ( preg_match( '/[\x{0400}-\x{04ff}]/u', $text ) ) {
if (preg_match('/[\x{0400}-\x{04ff}]/u', $text)) {
return 'ru';
}
if ( preg_match( '/[\x{0590}-\x{05ff}]/u', $text ) ) {
if (preg_match('/[\x{0590}-\x{05ff}]/u', $text)) {
return 'he';
}
if ( preg_match( '/[\x{0600}-\x{06ff}]/u', $text ) ) {
if (preg_match('/[\x{0600}-\x{06ff}]/u', $text)) {
return 'ar';
}
if ( preg_match( '/[\x{3040}-\x{309f}]/u', $text ) ) {
if (preg_match('/[\x{3040}-\x{309f}]/u', $text)) {
return 'ja';
}
if ( preg_match( '/[\x{30a0}-\x{30ff}]/u', $text ) ) {
if (preg_match('/[\x{30a0}-\x{30ff}]/u', $text)) {
return 'ja';
}
if ( preg_match( '/[\x{ac00}-\x{d7af}]/u', $text ) ) {
if (preg_match('/[\x{ac00}-\x{d7af}]/u', $text)) {
return 'ko';
}
if ( preg_match( '/[\x{0370}-\x{03ff}]/u', $text ) ) {
if (preg_match('/[\x{0370}-\x{03ff}]/u', $text)) {
return 'el';
}
if ( preg_match( '/[\x{0100}-\x{017f}]/u', $text ) ) {
if (preg_match('/[\x{0100}-\x{017f}]/u', $text)) {
return 'latin-ext';
}
if ( preg_match( '/[\x{0080}-\x{00ff}]/u', $text ) ) {
if (preg_match('/[\x{0080}-\x{00ff}]/u', $text)) {
return 'latin-ext';
}
if ( preg_match( '/[a-zA-Z]/', $text ) ) {
if (preg_match('/[a-zA-Z]/', $text)) {
return 'en';
}
@ -255,36 +249,36 @@ class WPSlug_Converter {
}

public function getSupportedModes() {
return array( 'pinyin', 'semantic_pinyin', 'transliteration', 'translation' );
return array('pinyin', 'semantic_pinyin', 'transliteration', 'translation');
}

public function isModeSupported( $mode ) {
return in_array( $mode, $this->getSupportedModes() );
public function isModeSupported($mode) {
return in_array($mode, $this->getSupportedModes());
}

public function batchConvert( $items, $options = array() ) {
public function batchConvert($items, $options = array()) {
$results = array();
if ( ! is_array( $items ) ) {
if (!is_array($items)) {
return $results;
}
foreach ( $items as $item ) {
foreach ($items as $item) {
try {
$converted = $this->convert( $item, $options );
$converted = $this->convert($item, $options);
$results[] = array(
'original' => $item,
'converted' => $converted,
'mode' => isset( $options['conversion_mode'] ) ? $options['conversion_mode'] : 'pinyin',
'detected_language' => $this->detectLanguage( $item ),
'mode' => isset($options['conversion_mode']) ? $options['conversion_mode'] : 'pinyin',
'detected_language' => $this->detectLanguage($item)
);
} catch ( Exception $e ) {
$this->settings->logError( 'Batch convert error: ' . $e->getMessage() );
} catch (Exception $e) {
$this->settings->logError('Batch convert error: ' . $e->getMessage());
$results[] = array(
'original' => $item,
'converted' => sanitize_title( $item ),
'converted' => sanitize_title($item),
'mode' => 'fallback',
'detected_language' => 'unknown',
'detected_language' => 'unknown'
);
}
}
@ -292,13 +286,13 @@ class WPSlug_Converter {
return $results;
}

public function testConversion( $text, $options = array() ) {
$start_time = microtime( true );
public function testConversion($text, $options = array()) {
$start_time = microtime(true);
try {
$detected_lang = $this->detectLanguage( $text );
$converted = $this->convert( $text, $options );
$execution_time = microtime( true ) - $start_time;
$detected_lang = $this->detectLanguage($text);
$converted = $this->convert($text, $options);
$execution_time = microtime(true) - $start_time;
return array(
'success' => true,
@ -306,80 +300,80 @@ 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 ) {
$execution_time = microtime( true ) - $start_time;
} catch (Exception $e) {
$execution_time = microtime(true) - $start_time;
return array(
'success' => false,
'original' => $text,
'converted' => sanitize_title( $text ),
'converted' => sanitize_title($text),
'detected_language' => 'unknown',
'execution_time' => $execution_time,
'mode' => 'fallback',
'error' => $e->getMessage(),
'error' => $e->getMessage()
);
}
}

public function getLanguageInfo( $text ) {
$detected_lang = $this->detectLanguage( $text );
$char_count = mb_strlen( $text, 'UTF-8' );
$word_count = str_word_count( $text, 0, 'àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ' );
public function getLanguageInfo($text) {
$detected_lang = $this->detectLanguage($text);
$char_count = mb_strlen($text, 'UTF-8');
$word_count = str_word_count($text, 0, 'àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ');
$info = array(
'detected_language' => $detected_lang,
'character_count' => $char_count,
'word_count' => $word_count,
'has_chinese' => preg_match( '/[\x{4e00}-\x{9fff}]/u', $text ) ? true : false,
'has_cyrillic' => preg_match( '/[\x{0400}-\x{04ff}]/u', $text ) ? true : false,
'has_arabic' => preg_match( '/[\x{0600}-\x{06ff}]/u', $text ) ? true : false,
'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_chinese' => preg_match('/[\x{4e00}-\x{9fff}]/u', $text) ? true : false,
'has_cyrillic' => preg_match('/[\x{0400}-\x{04ff}]/u', $text) ? true : false,
'has_arabic' => preg_match('/[\x{0600}-\x{06ff}]/u', $text) ? true : false,
'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
);
return $info;
}

public function recommendMode( $text ) {
$lang_info = $this->getLanguageInfo( $text );
public function recommendMode($text) {
$lang_info = $this->getLanguageInfo($text);
$detected_lang = $lang_info['detected_language'];
if ( $detected_lang === 'zh' ) {
if ($detected_lang === 'zh') {
return 'pinyin';
} elseif ( in_array( $detected_lang, array( 'ru', 'ar', 'el', 'he' ) ) ) {
} elseif (in_array($detected_lang, array('ru', 'ar', 'el', 'he'))) {
return 'transliteration';
} elseif ( $detected_lang !== 'en' && $detected_lang !== 'unknown' ) {
} elseif ($detected_lang !== 'en' && $detected_lang !== 'unknown') {
return 'translation';
} else {
return 'pinyin';
}
}

public function validateInput( $text, $options = array() ) {
$max_length = isset( $options['max_length'] ) ? intval( $options['max_length'] ) : 1000;
public function validateInput($text, $options = array()) {
$max_length = isset($options['max_length']) ? intval($options['max_length']) : 1000;
if ( empty( $text ) ) {
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 ) {
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'
);
}
}

View file

@ -1,6 +1,6 @@
<?php

if ( ! defined( 'ABSPATH' ) ) {
if (!defined('ABSPATH')) {
exit;
}

@ -15,7 +15,7 @@ class WPSlug_Core {
$this->converter = new WPSlug_Converter();
$this->optimizer = new WPSlug_Optimizer();

if ( is_admin() ) {
if (is_admin()) {
$this->admin = new WPSlug_Admin();
}

@ -24,171 +24,172 @@ class WPSlug_Core {
}

private function initHooks() {
add_filter( 'sanitize_title', array( $this, 'processSanitizeTitle' ), 9, 3 );
add_filter( 'wp_insert_post_data', array( $this, 'processPostData' ), 10, 2 );
add_filter( 'wp_insert_term_data', array( $this, 'processTermData' ), 10, 3 );
add_filter( 'wp_update_term_data', array( $this, 'processTermDataUpdate' ), 10, 4 );
add_filter( 'sanitize_file_name', array( $this, 'processFileName' ), 10, 2 );
add_filter( 'wp_unique_post_slug', array( $this, 'processUniquePostSlug' ), 10, 6 );
add_filter( 'pre_category_nicename', array( $this, 'preCategoryNicename' ), 10, 2 );
add_filter('sanitize_title', array($this, 'processSanitizeTitle'), 9, 3);
add_filter('wp_insert_post_data', array($this, 'processPostData'), 10, 2);
add_filter('wp_insert_term_data', array($this, 'processTermData'), 10, 3);
add_filter('wp_update_term_data', array($this, 'processTermDataUpdate'), 10, 4);
add_filter('sanitize_file_name', array($this, 'processFileName'), 10, 2);
add_filter('wp_unique_post_slug', array($this, 'processUniquePostSlug'), 10, 6);
add_filter('pre_category_nicename', array($this, 'preCategoryNicename'), 10, 2);
add_action( 'transition_post_status', array( $this, 'handlePostStatusTransition' ), 10, 3 );
add_action('transition_post_status', array($this, 'handlePostStatusTransition'), 10, 3);

if ( is_admin() ) {
add_filter( 'manage_posts_columns', array( $this, 'addSlugColumn' ) );
add_action( 'manage_posts_custom_column', array( $this, 'displaySlugColumn' ), 10, 2 );
add_filter( 'manage_pages_columns', array( $this, 'addSlugColumn' ) );
add_action( 'manage_pages_custom_column', array( $this, 'displaySlugColumn' ), 10, 2 );
if (is_admin()) {
add_filter('manage_posts_columns', array($this, 'addSlugColumn'));
add_action('manage_posts_custom_column', array($this, 'displaySlugColumn'), 10, 2);
add_filter('manage_pages_columns', array($this, 'addSlugColumn'));
add_action('manage_pages_custom_column', array($this, 'displaySlugColumn'), 10, 2);
}
}
/**
* Disabled: migrated to WPSlug_Updater (Update URI)
*/
private function init_update_checker() {
private function init_update_checker()
{
return;
}

public function processSanitizeTitle( $title, $raw_title = '', $context = 'display' ) {
if ( $context !== 'save' || empty( $title ) ) {
public function processSanitizeTitle($title, $raw_title = '', $context = 'display') {
if ($context !== 'save' || empty($title)) {
return $title;
}

try {
$options = $this->settings->getOptions();

if ( ! $options['enable_conversion'] ) {
if (!$options['enable_conversion']) {
return $title;
}

$processed_title = $this->converter->convert( $title, $options );
$processed_title = $this->optimizer->optimize( $processed_title, $options );
$processed_title = $this->converter->convert($title, $options);
$processed_title = $this->optimizer->optimize($processed_title, $options);

return $processed_title;
} catch ( Exception $e ) {
$this->settings->logError( 'processSanitizeTitle error: ' . $e->getMessage() );
} catch (Exception $e) {
$this->settings->logError('processSanitizeTitle error: ' . $e->getMessage());
return $title;
}
}

public function processPostData( $data, $postarr ) {
if ( empty( $data['post_title'] ) ) {
public function processPostData($data, $postarr) {
if (empty($data['post_title'])) {
return $data;
}

try {
$options = $this->settings->getOptions();

if ( ! $options['enable_conversion'] || ! $options['auto_convert'] ) {
if (!$options['enable_conversion'] || !$options['auto_convert']) {
return $data;
}

$post_type = $data['post_type'];
if ( ! $this->settings->isPostTypeEnabled( $post_type ) ) {
if (!$this->settings->isPostTypeEnabled($post_type)) {
return $data;
}

if ( isset( $postarr['wpslug_disable_conversion'] ) && $postarr['wpslug_disable_conversion'] ) {
if (isset($postarr['wpslug_disable_conversion']) && $postarr['wpslug_disable_conversion']) {
return $data;
}

if ( ! empty( $data['post_name'] ) && ! $this->shouldUpdateSlug( $postarr ) ) {
if (!empty($data['post_name']) && !$this->shouldUpdateSlug($postarr)) {
return $data;
}

$slug = $this->converter->convert( $data['post_title'], $options );
$slug = $this->optimizer->optimize( $slug, $options );
$slug = $this->converter->convert($data['post_title'], $options);
$slug = $this->optimizer->optimize($slug, $options);

if ( ! empty( $slug ) && $slug !== $data['post_name'] ) {
if (!empty($slug) && $slug !== $data['post_name']) {
$data['post_name'] = $slug;
}

return $data;
} catch ( Exception $e ) {
$this->settings->logError( 'processPostData error: ' . $e->getMessage() );
} catch (Exception $e) {
$this->settings->logError('processPostData error: ' . $e->getMessage());
return $data;
}
}

public function processTermData( $data, $taxonomy, $args ) {
public function processTermData($data, $taxonomy, $args) {
try {
$options = $this->settings->getOptions();

if ( ! $options['enable_conversion'] || ! $options['auto_convert'] ) {
if (!$options['enable_conversion'] || !$options['auto_convert']) {
return $data;
}

if ( ! $this->settings->isTaxonomyEnabled( $taxonomy ) ) {
if (!$this->settings->isTaxonomyEnabled($taxonomy)) {
return $data;
}

if ( ! empty( $data['name'] ) && empty( $args['slug'] ) ) {
$slug = $this->converter->convert( $data['name'], $options );
$slug = $this->optimizer->optimize( $slug, $options );
if (!empty($data['name']) && empty($args['slug'])) {
$slug = $this->converter->convert($data['name'], $options);
$slug = $this->optimizer->optimize($slug, $options);

if ( ! empty( $slug ) ) {
if (!empty($slug)) {
$data['slug'] = $slug;
}
}

return $data;
} catch ( Exception $e ) {
$this->settings->logError( 'processTermData error: ' . $e->getMessage() );
} catch (Exception $e) {
$this->settings->logError('processTermData error: ' . $e->getMessage());
return $data;
}
}

public function processTermDataUpdate( $data, $term_id, $taxonomy, $args ) {
public function processTermDataUpdate($data, $term_id, $taxonomy, $args) {
try {
$options = $this->settings->getOptions();

if ( ! $options['enable_conversion'] || ! $options['auto_convert'] ) {
if (!$options['enable_conversion'] || !$options['auto_convert']) {
return $data;
}

if ( ! $this->settings->isTaxonomyEnabled( $taxonomy ) ) {
if (!$this->settings->isTaxonomyEnabled($taxonomy)) {
return $data;
}

if ( ! empty( $data['name'] ) && empty( $args['slug'] ) ) {
$slug = $this->converter->convert( $data['name'], $options );
$slug = $this->optimizer->optimize( $slug, $options );
if (!empty($data['name']) && empty($args['slug'])) {
$slug = $this->converter->convert($data['name'], $options);
$slug = $this->optimizer->optimize($slug, $options);

if ( ! empty( $slug ) ) {
$data['slug'] = wp_unique_term_slug( $slug, (object) $args );
if (!empty($slug)) {
$data['slug'] = wp_unique_term_slug($slug, (object) $args);
}
}

return $data;
} catch ( Exception $e ) {
$this->settings->logError( 'processTermDataUpdate error: ' . $e->getMessage() );
} catch (Exception $e) {
$this->settings->logError('processTermDataUpdate error: ' . $e->getMessage());
return $data;
}
}

public function processFileName( $filename, $filename_raw = '' ) {
public function processFileName($filename, $filename_raw = '') {
try {
$options = $this->settings->getOptions();

if ( ! $options['enable_conversion'] || $options['disable_file_convert'] ) {
if (!$options['enable_conversion'] || $options['disable_file_convert']) {
return $filename;
}

$media_mode = $this->settings->getOption( 'media_conversion_mode', 'normal' );
$media_prefix = $this->settings->getOption( 'media_file_prefix', '' );
$media_mode = $this->settings->getOption('media_conversion_mode', 'normal');
$media_prefix = $this->settings->getOption('media_file_prefix', '');

$parts = explode( '.', $filename );
$parts = explode('.', $filename);
$extension = '';
if ( count( $parts ) > 1 ) {
$extension = array_pop( $parts );
$name = implode( '.', $parts );
if (count($parts) > 1) {
$extension = array_pop($parts);
$name = implode('.', $parts);
} else {
$name = $filename;
}

switch ( $media_mode ) {
switch ($media_mode) {
case 'md5':
$converted_name = md5( $name . time() );
$converted_name = md5($name . time());
break;
case 'none':
@ -197,173 +198,171 @@ class WPSlug_Core {
case 'normal':
default:
if ( $this->needsConversion( $name ) ) {
$converted_name = $this->converter->convert( $name, $options );
$converted_name = $this->optimizer->optimize( $converted_name, $options );
if ($this->needsConversion($name)) {
$converted_name = $this->converter->convert($name, $options);
$converted_name = $this->optimizer->optimize($converted_name, $options);
} else {
$converted_name = $name;
}
break;
}

if ( ! empty( $media_prefix ) ) {
if (!empty($media_prefix)) {
$converted_name = $media_prefix . $converted_name;
}

return $extension ? $converted_name . '.' . $extension : $converted_name;
} catch ( Exception $e ) {
$this->settings->logError( 'processFileName error: ' . $e->getMessage() );
} catch (Exception $e) {
$this->settings->logError('processFileName error: ' . $e->getMessage());
return $filename;
}
}

public function processUniquePostSlug( $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug ) {
public function processUniquePostSlug($slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug) {
try {
$options = $this->settings->getOptions();

if ( ! $options['enable_conversion'] || ! $options['auto_convert'] ) {
if (!$options['enable_conversion'] || !$options['auto_convert']) {
return $slug;
}

if ( $post_type === 'attachment' ) {
if ($post_type === 'attachment') {
return $slug;
}

if ( ! $this->settings->isPostTypeEnabled( $post_type ) ) {
if (!$this->settings->isPostTypeEnabled($post_type)) {
return $slug;
}

$old_status = get_post_field( 'post_status', $post_ID, 'edit' );
$old_status = get_post_field('post_status', $post_ID, 'edit');

if ( $old_status !== 'publish' && $post_status === 'publish' ) {
$converted_slug = $this->converter->convert( $slug, $options );
$converted_slug = $this->optimizer->optimize( $converted_slug, $options );
if ( ! empty( $converted_slug ) ) {
if ($old_status !== 'publish' && $post_status === 'publish') {
$converted_slug = $this->converter->convert($slug, $options);
$converted_slug = $this->optimizer->optimize($converted_slug, $options);
if (!empty($converted_slug)) {
return $converted_slug;
}
}

return $slug;
} catch ( Exception $e ) {
$this->settings->logError( 'processUniquePostSlug error: ' . $e->getMessage() );
} catch (Exception $e) {
$this->settings->logError('processUniquePostSlug error: ' . $e->getMessage());
return $slug;
}
}

public function preCategoryNicename( $slug, $name = '' ) {
public function preCategoryNicename($slug, $name = '') {
try {
$options = $this->settings->getOptions();

if ( ! $options['enable_conversion'] || ! $options['auto_convert'] ) {
if (!$options['enable_conversion'] || !$options['auto_convert']) {
return $slug;
}

if ( $slug ) {
if ($slug) {
return $slug;
}

$tag_name = isset( $_POST['tag-name'] ) ? sanitize_text_field( $_POST['tag-name'] ) : $name;
$tag_name = isset($_POST['tag-name']) ? sanitize_text_field($_POST['tag-name']) : $name;

if ( $tag_name ) {
$converted_slug = $this->converter->convert( $tag_name, $options );
$converted_slug = $this->optimizer->optimize( $converted_slug, $options );
return sanitize_title( $converted_slug );
if ($tag_name) {
$converted_slug = $this->converter->convert($tag_name, $options);
$converted_slug = $this->optimizer->optimize($converted_slug, $options);
return sanitize_title($converted_slug);
}

return $slug;
} catch ( Exception $e ) {
$this->settings->logError( 'preCategoryNicename error: ' . $e->getMessage() );
} catch (Exception $e) {
$this->settings->logError('preCategoryNicename error: ' . $e->getMessage());
return $slug;
}
}

public function handlePostStatusTransition( $new_status, $old_status, $post ) {
if ( $new_status !== 'publish' || $old_status === 'publish' ) {
public function handlePostStatusTransition($new_status, $old_status, $post) {
if ($new_status !== 'publish' || $old_status === 'publish') {
return;
}

try {
$options = $this->settings->getOptions();

if ( ! $options['enable_conversion'] || ! $options['auto_convert'] ) {
if (!$options['enable_conversion'] || !$options['auto_convert']) {
return;
}

if ( ! $this->settings->isPostTypeEnabled( $post->post_type ) ) {
if (!$this->settings->isPostTypeEnabled($post->post_type)) {
return;
}

$new_slug = $this->converter->convert( $post->post_title, $options );
$new_slug = $this->optimizer->optimize( $new_slug, $options );
$new_slug = $this->converter->convert($post->post_title, $options);
$new_slug = $this->optimizer->optimize($new_slug, $options);

if ( ! empty( $new_slug ) && $new_slug !== $post->post_name ) {
$unique_slug = $this->optimizer->generateUniqueSlug( $new_slug, $post->ID, $post->post_type );
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() );
} catch (Exception $e) {
$this->settings->logError('handlePostStatusTransition error: ' . $e->getMessage());
}
}

public function addSlugColumn( $columns ) {
public function addSlugColumn($columns) {
try {
$options = $this->settings->getOptions();

if ( isset( $options['show_slug_column'] ) && $options['show_slug_column'] ) {
$columns['wpslug_slug'] = __( 'Slug', 'wpslug' );
if (isset($options['show_slug_column']) && $options['show_slug_column']) {
$columns['wpslug_slug'] = __('Slug', 'wpslug');
}

return $columns;
} catch ( Exception $e ) {
} catch (Exception $e) {
return $columns;
}
}

public function displaySlugColumn( $column_name, $post_id ) {
if ( $column_name !== 'wpslug_slug' ) {
public function displaySlugColumn($column_name, $post_id) {
if ($column_name !== 'wpslug_slug') {
return;
}

try {
$post = get_post( $post_id );
if ( ! $post || $post->post_status === 'trash' ) {
echo '<span class="wpslug-na">' . esc_html__( 'N/A', 'wpslug' ) . '</span>';
$post = get_post($post_id);
if (!$post || $post->post_status === 'trash') {
echo '<span class="wpslug-na">' . esc_html__('N/A', 'wpslug') . '</span>';
return;
}

$permalink = get_permalink( $post_id );
$home_url = trailingslashit( home_url() );
$permalink = get_permalink($post_id);
$home_url = trailingslashit(home_url());

if ( $permalink && strpos( $permalink, $home_url ) === 0 ) {
$slug = str_replace( $home_url, '/', $permalink );
if ($permalink && strpos($permalink, $home_url) === 0) {
$slug = str_replace($home_url, '/', $permalink);
} else {
$slug = '/' . $post->post_name;
}

echo '<code class="wpslug-slug">' . esc_html( $slug ) . '</code>';
} catch ( Exception $e ) {
echo '<span class="wpslug-error">' . esc_html__( 'Error', 'wpslug' ) . '</span>';
echo '<code class="wpslug-slug">' . esc_html($slug) . '</code>';
} catch (Exception $e) {
echo '<span class="wpslug-error">' . esc_html__('Error', 'wpslug') . '</span>';
}
}

private function needsConversion( $text ) {
return preg_match( '/[^\x00-\x7F]/', $text );
private function needsConversion($text) {
return preg_match('/[^\x00-\x7F]/', $text);
}

private function shouldUpdateSlug( $postarr ) {
if ( isset( $postarr['post_status'] ) && $postarr['post_status'] === 'auto-draft' ) {
private function shouldUpdateSlug($postarr) {
if (isset($postarr['post_status']) && $postarr['post_status'] === 'auto-draft') {
return true;
}

if ( isset( $postarr['ID'] ) && $postarr['ID'] > 0 ) {
$existing_post = get_post( $postarr['ID'] );
if ( $existing_post && $existing_post->post_status === 'auto-draft' ) {
if (isset($postarr['ID']) && $postarr['ID'] > 0) {
$existing_post = get_post($postarr['ID']);
if ($existing_post && $existing_post->post_status === 'auto-draft') {
return true;
}
}
@ -375,8 +374,8 @@ class WPSlug_Core {
try {
$this->settings->createDefaultOptions();
flush_rewrite_rules();
} catch ( Exception $e ) {
$this->settings->logError( 'activate error: ' . $e->getMessage() );
} catch (Exception $e) {
$this->settings->logError('activate error: ' . $e->getMessage());
}
}


View file

@ -1,207 +1,199 @@
<?php

if ( ! defined( 'ABSPATH' ) ) {
if (!defined('ABSPATH')) {
exit;
}

class WPSlug_Optimizer {

public function __construct() {
}

public function optimize( $text, $options = array() ) {
if ( empty( $text ) ) {
public function optimize($text, $options = array()) {
if (empty($text)) {
return '';
}

$force_lowercase = isset( $options['force_lowercase'] ) ? $options['force_lowercase'] : true;
$max_length = isset( $options['max_length'] ) ? (int) $options['max_length'] : 50;
$conversion_mode = isset( $options['conversion_mode'] ) ? $options['conversion_mode'] : 'pinyin';
$pinyin_format = isset( $options['pinyin_format'] ) ? $options['pinyin_format'] : 'full';
$force_lowercase = isset($options['force_lowercase']) ? $options['force_lowercase'] : true;
$max_length = isset($options['max_length']) ? (int)$options['max_length'] : 50;
$conversion_mode = isset($options['conversion_mode']) ? $options['conversion_mode'] : 'pinyin';
$pinyin_format = isset($options['pinyin_format']) ? $options['pinyin_format'] : 'full';

$is_first_letter_mode = ( $conversion_mode === 'pinyin' && $pinyin_format === 'first' );
$should_apply_seo = isset( $options['enable_seo_optimization'] ) ? $options['enable_seo_optimization'] : true;
$is_first_letter_mode = ($conversion_mode === 'pinyin' && $pinyin_format === 'first');
$should_apply_seo = isset($options['enable_seo_optimization']) ? $options['enable_seo_optimization'] : true;
if ( $should_apply_seo && ! $is_first_letter_mode ) {
$text = $this->applySEOOptimizations( $text, $options );
if ($should_apply_seo && !$is_first_letter_mode) {
$text = $this->applySEOOptimizations($text, $options);
}

if ( $max_length > 0 && strlen( $text ) > $max_length ) {
$text = $this->truncateSlug( $text, $max_length );
if ($max_length > 0 && strlen($text) > $max_length) {
$text = $this->truncateSlug($text, $max_length);
}

$text = $this->finalCleanup( $text );
$text = $this->finalCleanup($text);

if ( $force_lowercase ) {
$text = strtolower( $text );
if ($force_lowercase) {
$text = strtolower($text);
}

return $text;
}

private function applySEOOptimizations( $text, $options ) {
if ( isset( $options['smart_punctuation'] ) && $options['smart_punctuation'] ) {
$text = $this->handleSmartPunctuation( $text );
private function applySEOOptimizations($text, $options) {
if (isset($options['smart_punctuation']) && $options['smart_punctuation']) {
$text = $this->handleSmartPunctuation($text);
}

if ( isset( $options['mixed_content_optimization'] ) && $options['mixed_content_optimization'] ) {
$text = $this->optimizeMixedContent( $text );
if (isset($options['mixed_content_optimization']) && $options['mixed_content_optimization']) {
$text = $this->optimizeMixedContent($text);
}

if ( isset( $options['remove_stop_words'] ) && $options['remove_stop_words'] ) {
$text = $this->removeStopWords( $text, $options );
if (isset($options['remove_stop_words']) && $options['remove_stop_words']) {
$text = $this->removeStopWords($text, $options);
}

if ( isset( $options['seo_max_words'] ) && $options['seo_max_words'] > 0 ) {
$text = $this->limitWords( $text, $options['seo_max_words'] );
if (isset($options['seo_max_words']) && $options['seo_max_words'] > 0) {
$text = $this->limitWords($text, $options['seo_max_words']);
}

return $text;
}

private function handleSmartPunctuation( $text ) {
$text = str_replace( array( ':', ';', ',' ), '-', $text );
$text = str_replace( array( '.', '?', '!', '(', ')', '[', ']', '<', '>' ), '', $text );
$text = str_replace( array( '|', '/', '\\' ), '-', $text );
$text = str_replace( '&', 'and', $text );
$text = str_replace( '+', 'plus', $text );
$text = str_replace( '=', 'equal', $text );
$text = str_replace( '%', 'percent', $text );
$text = str_replace( '#', 'hash', $text );
$text = str_replace( '@', 'at', $text );
$text = str_replace( '$', 'dollar', $text );
private function handleSmartPunctuation($text) {
$text = str_replace(array(':', ';', ','), '-', $text);
$text = str_replace(array('.', '?', '!', '(', ')', '[', ']', '<', '>'), '', $text);
$text = str_replace(array('|', '/', '\\'), '-', $text);
$text = str_replace('&', 'and', $text);
$text = str_replace('+', 'plus', $text);
$text = str_replace('=', 'equal', $text);
$text = str_replace('%', 'percent', $text);
$text = str_replace('#', 'hash', $text);
$text = str_replace('@', 'at', $text);
$text = str_replace('$', 'dollar', $text);
return $text;
}

private function optimizeMixedContent( $text ) {
$text = preg_replace( '/(\d+)(\p{Han})/u', '$1-$2', $text );
$text = preg_replace( '/(\p{Han})(\d+)/u', '$1-$2', $text );
private function optimizeMixedContent($text) {
$text = preg_replace('/(\d+)(\p{Han})/u', '$1-$2', $text);
$text = preg_replace('/(\p{Han})(\d+)/u', '$1-$2', $text);
$text = preg_replace( '/([a-zA-Z])(\p{Han})/u', '$1-$2', $text );
$text = preg_replace( '/(\p{Han})([a-zA-Z])/u', '$1-$2', $text );
$text = preg_replace('/([a-zA-Z])(\p{Han})/u', '$1-$2', $text);
$text = preg_replace('/(\p{Han})([a-zA-Z])/u', '$1-$2', $text);
$text = preg_replace( '/([a-zA-Z])(\d+)/u', '$1$2', $text );
$text = preg_replace( '/(\d+)([a-zA-Z])/u', '$1$2', $text );
$text = preg_replace('/([a-zA-Z])(\d+)/u', '$1$2', $text);
$text = preg_replace('/(\d+)([a-zA-Z])/u', '$1$2', $text);
$text = preg_replace( '/\s*-\s*/', '-', $text );
$text = preg_replace('/\s*-\s*/', '-', $text);
return $text;
}

private function removeStopWords( $text, $options ) {
if ( ! isset( $options['stop_words_list'] ) ) {
private function removeStopWords($text, $options) {
if (!isset($options['stop_words_list'])) {
return $text;
}

$stop_words_string = $options['stop_words_list'];
if ( empty( $stop_words_string ) ) {
if (empty($stop_words_string)) {
return $text;
}

$stop_words = explode( ',', $stop_words_string );
$stop_words = array_map( 'trim', $stop_words );
$stop_words = array_filter( $stop_words );
$stop_words = explode(',', $stop_words_string);
$stop_words = array_map('trim', $stop_words);
$stop_words = array_filter($stop_words);

if ( empty( $stop_words ) ) {
if (empty($stop_words)) {
return $text;
}

$words = preg_split( '/[-\s_]+/', $text );
$words = preg_split('/[-\s_]+/', $text);
$filtered_words = array();

foreach ( $words as $word ) {
$word = trim( $word );
if ( empty( $word ) ) {
foreach ($words as $word) {
$word = trim($word);
if (empty($word)) {
continue;
}

if ( ! in_array( strtolower( $word ), array_map( 'strtolower', $stop_words ) ) ) {
if (!in_array(strtolower($word), array_map('strtolower', $stop_words))) {
$filtered_words[] = $word;
}
}

return implode( '-', $filtered_words );
return implode('-', $filtered_words);
}

private function limitWords( $text, $max_words ) {
if ( $max_words <= 0 ) {
private function limitWords($text, $max_words) {
if ($max_words <= 0) {
return $text;
}

$words = preg_split( '/[-\s_]+/', $text );
$words = array_filter(
$words,
function ( $word ) {
return ! empty( trim( $word ) );
}
);
$words = preg_split('/[-\s_]+/', $text);
$words = array_filter($words, function($word) {
return !empty(trim($word));
});

if ( count( $words ) <= $max_words ) {
if (count($words) <= $max_words) {
return $text;
}

$limited_words = array_slice( $words, 0, $max_words );
return implode( '-', $limited_words );
$limited_words = array_slice($words, 0, $max_words);
return implode('-', $limited_words);
}

private function finalCleanup( $slug ) {
$slug = preg_replace( '/[^a-zA-Z0-9\-_\p{Han}]/u', '', $slug );
$slug = preg_replace( '/\-+/', '-', $slug );
$slug = preg_replace( '/_+/', '_', $slug );
$slug = trim( $slug, '-_' );
private function finalCleanup($slug) {
$slug = preg_replace('/[^a-zA-Z0-9\-_\p{Han}]/u', '', $slug);
$slug = preg_replace('/\-+/', '-', $slug);
$slug = preg_replace('/_+/', '_', $slug);
$slug = trim($slug, '-_');
return $slug;
}

public function truncateSlug( $slug, $max_length ) {
if ( strlen( $slug ) <= $max_length ) {
public function truncateSlug($slug, $max_length) {
if (strlen($slug) <= $max_length) {
return $slug;
}

$parts = explode( '-', $slug );
$parts = explode('-', $slug);
$result = '';
foreach ( $parts as $part ) {
if ( strlen( $result . '-' . $part ) <= $max_length ) {
$result .= ( $result ? '-' : '' ) . $part;
foreach ($parts as $part) {
if (strlen($result . '-' . $part) <= $max_length) {
$result .= ($result ? '-' : '') . $part;
} else {
break;
}
}
return $result ?: substr( $slug, 0, $max_length );
return $result ?: substr($slug, 0, $max_length);
}

public function generateUniqueSlug( $slug, $post_id = 0, $post_type = 'post' ) {
public function generateUniqueSlug($slug, $post_id = 0, $post_type = 'post') {
global $wpdb;
$original_slug = $slug;
$suffix = 1;
while ( true ) {
$query = $wpdb->prepare(
"
while (true) {
$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 ) ) {
if (!$wpdb->get_var($query)) {
break;
}
$slug = $original_slug . '-' . $suffix;
++$suffix;
$suffix++;
if ( $suffix > 100 ) {
if ($suffix > 100) {
break;
}
}
@ -209,102 +201,102 @@ class WPSlug_Optimizer {
return $slug;
}

public function validateSlug( $slug ) {
if ( empty( $slug ) ) {
public function validateSlug($slug) {
if (empty($slug)) {
return false;
}
if ( strlen( $slug ) > 200 ) {
if (strlen($slug) > 200) {
return false;
}
if ( preg_match( '/[^a-zA-Z0-9\-_\p{Han}]/u', $slug ) ) {
if (preg_match('/[^a-zA-Z0-9\-_\p{Han}]/u', $slug)) {
return false;
}
return true;
}

public function detectLanguage( $text ) {
if ( preg_match( '/[\x{4e00}-\x{9fff}]/u', $text ) ) {
public function detectLanguage($text) {
if (preg_match('/[\x{4e00}-\x{9fff}]/u', $text)) {
return 'zh';
}
return 'en';
}

public function getSEOScore( $original_text, $optimized_slug ) {
public function getSEOScore($original_text, $optimized_slug) {
$score = 0;
$max_score = 100;
if ( strlen( $optimized_slug ) > 0 ) {
if (strlen($optimized_slug) > 0) {
$score += 20;
}
if ( strlen( $optimized_slug ) <= 60 ) {
if (strlen($optimized_slug) <= 60) {
$score += 15;
}
if ( preg_match( '/^[a-zA-Z0-9\-_\p{Han}]+$/u', $optimized_slug ) ) {
if (preg_match('/^[a-zA-Z0-9\-_\p{Han}]+$/u', $optimized_slug)) {
$score += 15;
}
if ( substr_count( $optimized_slug, '-' ) <= 5 ) {
if (substr_count($optimized_slug, '-') <= 5) {
$score += 10;
}
if ( ! preg_match( '/^-|-$/', $optimized_slug ) ) {
if (!preg_match('/^-|-$/', $optimized_slug)) {
$score += 10;
}
$word_count = count( explode( '-', $optimized_slug ) );
if ( $word_count >= 2 && $word_count <= 5 ) {
$word_count = count(explode('-', $optimized_slug));
if ($word_count >= 2 && $word_count <= 5) {
$score += 15;
}
if ( strtolower( $optimized_slug ) === $optimized_slug ) {
if (strtolower($optimized_slug) === $optimized_slug) {
$score += 5;
}
if ( ! preg_match( '/\d{4,}/', $optimized_slug ) ) {
if (!preg_match('/\d{4,}/', $optimized_slug)) {
$score += 5;
}
if ( strlen( $optimized_slug ) >= 10 ) {
if (strlen($optimized_slug) >= 10) {
$score += 5;
}
return min( $score, $max_score );
return min($score, $max_score);
}

public function suggestAlternatives( $text, $options = array() ) {
public function suggestAlternatives($text, $options = array()) {
$suggestions = array();
$base_slug = $this->optimize( $text, $options );
$base_slug = $this->optimize($text, $options);
$suggestions[] = $base_slug;
$short_options = $options;
$short_options['seo_max_words'] = 3;
$short_slug = $this->optimize( $text, $short_options );
if ( $short_slug !== $base_slug ) {
$short_slug = $this->optimize($text, $short_options);
if ($short_slug !== $base_slug) {
$suggestions[] = $short_slug;
}
$no_stop_words_options = $options;
$no_stop_words_options['remove_stop_words'] = true;
$no_stop_words_slug = $this->optimize( $text, $no_stop_words_options );
if ( $no_stop_words_slug !== $base_slug ) {
$no_stop_words_slug = $this->optimize($text, $no_stop_words_options);
if ($no_stop_words_slug !== $base_slug) {
$suggestions[] = $no_stop_words_slug;
}
$minimal_options = $options;
$minimal_options['seo_max_words'] = 2;
$minimal_options['remove_stop_words'] = true;
$minimal_slug = $this->optimize( $text, $minimal_options );
if ( $minimal_slug !== $base_slug && ! in_array( $minimal_slug, $suggestions ) ) {
$minimal_slug = $this->optimize($text, $minimal_options);
if ($minimal_slug !== $base_slug && !in_array($minimal_slug, $suggestions)) {
$suggestions[] = $minimal_slug;
}
return array_unique( $suggestions );
return array_unique($suggestions);
}
}

View file

@ -1,6 +1,6 @@
<?php

if ( ! defined( 'ABSPATH' ) ) {
if (!defined('ABSPATH')) {
exit;
}

@ -9,7 +9,7 @@ class WPSlug_Pinyin {
private static $instance = null;

public static function getInstance() {
if ( self::$instance === null ) {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
@ -19,79 +19,81 @@ class WPSlug_Pinyin {
$this->initPinyinDict();
}

public function convertToPinyin( $text, $options = array() ) {
if ( empty( $text ) ) {
public function convertToPinyin($text, $options = array()) {
if (empty($text)) {
return '';
}

$separator = isset( $options['pinyin_separator'] ) ? $options['pinyin_separator'] : '-';
$format = isset( $options['pinyin_format'] ) ? $options['pinyin_format'] : 'full';
$max_length = isset( $options['max_length'] ) ? (int) $options['max_length'] : 100;
$preserve_english = isset( $options['preserve_english'] ) ? $options['preserve_english'] : true;
$preserve_numbers = isset( $options['preserve_numbers'] ) ? $options['preserve_numbers'] : true;
$separator = isset($options['pinyin_separator']) ? $options['pinyin_separator'] : '-';
$format = isset($options['pinyin_format']) ? $options['pinyin_format'] : 'full';
$max_length = isset($options['max_length']) ? (int)$options['max_length'] : 100;
$preserve_english = isset($options['preserve_english']) ? $options['preserve_english'] : true;
$preserve_numbers = isset($options['preserve_numbers']) ? $options['preserve_numbers'] : true;

if ( $max_length > 0 && mb_strlen( $text, 'UTF-8' ) > $max_length ) {
$text = mb_substr( $text, 0, $max_length, 'UTF-8' );
if ($max_length > 0 && mb_strlen($text, 'UTF-8') > $max_length) {
$text = mb_substr($text, 0, $max_length, 'UTF-8');
}

$result = '';
$text_array = $this->mbStrSplit( $text );
$text_array = $this->mbStrSplit($text);

foreach ( $text_array as $char ) {
if ( $this->isChinese( $char ) ) {
$pinyin = $this->getPinyinForChar( $char );
if ( ! empty( $pinyin ) ) {
if ( $format === 'first' ) {
$first_letter = mb_substr( $pinyin, 0, 1, 'UTF-8' );
$result .= $separator . strtolower( $first_letter ) . $separator;
foreach ($text_array as $char) {
if ($this->isChinese($char)) {
$pinyin = $this->getPinyinForChar($char);
if (!empty($pinyin)) {
if ($format === 'first') {
$first_letter = mb_substr($pinyin, 0, 1, 'UTF-8');
$result .= $separator . strtolower($first_letter) . $separator;
} else {
$result .= $separator . strtolower( $pinyin ) . $separator;
$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 ) ) {
} elseif ($preserve_numbers && preg_match('/[0-9]/', $char)) {
$result .= $char;
} elseif ( preg_match( '/\s/', $char ) ) {
} elseif (preg_match('/\s/', $char)) {
$result .= $separator;
}
}
}

if ( ! empty( $separator ) ) {
$result = trim( preg_replace( '/' . preg_quote( $separator, '/' ) . '+/', $separator, $result ), $separator );
if (!empty($separator)) {
$result = trim(preg_replace('/' . preg_quote($separator, '/') . '+/', $separator, $result), $separator);
}

return $result;
}

public function isChinese( $char ) {
return preg_match( '/[\x{4e00}-\x{9fff}]/u', $char );
public function isChinese($char) {
return preg_match('/[\x{4e00}-\x{9fff}]/u', $char);
}

public function detectLanguage( $text ) {
if ( preg_match( '/[\x{4e00}-\x{9fff}]/u', $text ) ) {
public function detectLanguage($text) {
if (preg_match('/[\x{4e00}-\x{9fff}]/u', $text)) {
return 'zh';
}
return 'en';
}

private function getPinyinForChar( $char ) {
if ( isset( $this->pinyin_dict[ $char ] ) ) {
return $this->pinyin_dict[ $char ];
private function getPinyinForChar($char) {
if (isset($this->pinyin_dict[$char])) {
return $this->pinyin_dict[$char];
}
return '';
}

private function mbStrSplit( $string, $length = 1 ) {
if ( function_exists( 'mb_str_split' ) ) {
return mb_str_split( $string, $length, 'UTF-8' );
private function mbStrSplit($string, $length = 1) {
if (function_exists('mb_str_split')) {
return mb_str_split($string, $length, 'UTF-8');
}

$result = array();
$string_length = mb_strlen( $string, 'UTF-8' );
$string_length = mb_strlen($string, 'UTF-8');
for ( $i = 0; $i < $string_length; $i += $length ) {
$result[] = mb_substr( $string, $i, $length, 'UTF-8' );
for ($i = 0; $i < $string_length; $i += $length) {
$result[] = mb_substr($string, $i, $length, 'UTF-8');
}
return $result;
@ -100,17 +102,17 @@ class WPSlug_Pinyin {
private function initPinyinDict() {
$dictionary_file = WPSLUG_PLUGIN_DIR . 'includes/dictionary.php';
if ( file_exists( $dictionary_file ) ) {
if (file_exists($dictionary_file)) {
$dictionary = include $dictionary_file;
if ( is_array( $dictionary ) ) {
foreach ( $dictionary as $pinyin => $chars ) {
$pinyin_lower = strtolower( $pinyin );
$char_array = $this->mbStrSplit( $chars );
if (is_array($dictionary)) {
foreach ($dictionary as $pinyin => $chars) {
$pinyin_lower = strtolower($pinyin);
$char_array = $this->mbStrSplit($chars);
foreach ( $char_array as $char ) {
if ( ! empty( $char ) ) {
$this->pinyin_dict[ $char ] = $pinyin_lower;
foreach ($char_array as $char) {
if (!empty($char)) {
$this->pinyin_dict[$char] = $pinyin_lower;
}
}
}
@ -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

View file

@ -1,6 +1,6 @@
<?php

if ( ! defined( 'ABSPATH' ) ) {
if (!defined('ABSPATH')) {
exit;
}

@ -20,49 +20,49 @@ class WPSlug_Translator {
$this->converter = null;
}

public function translate( $text, $options = array() ) {
if ( empty( $text ) ) {
public function translate($text, $options = array()) {
if (empty($text)) {
return '';
}

// 防止循环调用:如果正在翻译中,直接回退到拼音
if ( self::$is_translating ) {
return $this->fallbackTranslate( $text, $options );
if (self::$is_translating) {
return $this->fallbackTranslate($text, $options);
}

$service = isset( $options['translation_service'] ) ? $options['translation_service'] : 'none';
$service = isset($options['translation_service']) ? $options['translation_service'] : 'none';
switch ( $service ) {
switch ($service) {
case 'wpmind':
// WPMind 服务需要循环保护
self::$is_translating = true;
try {
$result = $this->translateWPMind( $text, $options );
$result = $this->translateWPMind($text, $options);
} finally {
self::$is_translating = false;
}
return $result;
case 'google':
return $this->translateGoogle( $text, $options );
return $this->translateGoogle($text, $options);
case 'baidu':
return $this->translateBaidu( $text, $options );
return $this->translateBaidu($text, $options);
case 'none':
default:
return $this->fallbackTranslate( $text, $options );
return $this->fallbackTranslate($text, $options);
}
}

private function translateGoogle( $text, $options ) {
$api_key = isset( $options['google_api_key'] ) ? trim( $options['google_api_key'] ) : '';
$source_lang = isset( $options['translation_source_lang'] ) ? $options['translation_source_lang'] : 'auto';
$target_lang = isset( $options['translation_target_lang'] ) ? $options['translation_target_lang'] : 'en';
private function translateGoogle($text, $options) {
$api_key = isset($options['google_api_key']) ? trim($options['google_api_key']) : '';
$source_lang = isset($options['translation_source_lang']) ? $options['translation_source_lang'] : 'auto';
$target_lang = isset($options['translation_target_lang']) ? $options['translation_target_lang'] : 'en';

if ( empty( $api_key ) ) {
return $this->fallbackTranslate( $text, $options );
if (empty($api_key)) {
return $this->fallbackTranslate($text, $options);
}

if ( strlen( $text ) > 5000 ) {
return $this->fallbackTranslate( $text, $options );
if (strlen($text) > 5000) {
return $this->fallbackTranslate($text, $options);
}

try {
@ -72,69 +72,66 @@ 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'] ) {
error_log( 'WPSlug Google Translate Error: ' . $response->get_error_message() );
if (is_wp_error($response)) {
if (isset($options['debug_mode']) && $options['debug_mode']) {
error_log('WPSlug Google Translate Error: ' . $response->get_error_message());
}
return $this->fallbackTranslate( $text, $options );
return $this->fallbackTranslate($text, $options);
}

$response_code = wp_remote_retrieve_response_code( $response );
if ( $response_code !== 200 ) {
if ( isset( $options['debug_mode'] ) && $options['debug_mode'] ) {
error_log( 'WPSlug Google Translate HTTP Error: ' . $response_code );
$response_code = wp_remote_retrieve_response_code($response);
if ($response_code !== 200) {
if (isset($options['debug_mode']) && $options['debug_mode']) {
error_log('WPSlug Google Translate HTTP Error: ' . $response_code);
}
return $this->fallbackTranslate( $text, $options );
return $this->fallbackTranslate($text, $options);
}

$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body, true );
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);

if ( isset( $data['data']['translations'][0]['translatedText'] ) ) {
if (isset($data['data']['translations'][0]['translatedText'])) {
$translated = $data['data']['translations'][0]['translatedText'];
return $this->cleanTranslatedText( $translated );
return $this->cleanTranslatedText($translated);
}

return $this->fallbackTranslate( $text, $options );
} catch ( Exception $e ) {
if ( isset( $options['debug_mode'] ) && $options['debug_mode'] ) {
error_log( 'WPSlug translateGoogle error: ' . $e->getMessage() );
return $this->fallbackTranslate($text, $options);
} catch (Exception $e) {
if (isset($options['debug_mode']) && $options['debug_mode']) {
error_log('WPSlug translateGoogle error: ' . $e->getMessage());
}
return $this->fallbackTranslate( $text, $options );
return $this->fallbackTranslate($text, $options);
}
}

private function translateBaidu( $text, $options ) {
$app_id = isset( $options['baidu_app_id'] ) ? trim( $options['baidu_app_id'] ) : '';
$secret_key = isset( $options['baidu_secret_key'] ) ? trim( $options['baidu_secret_key'] ) : '';
$source_lang = isset( $options['translation_source_lang'] ) ? $options['translation_source_lang'] : 'auto';
$target_lang = isset( $options['translation_target_lang'] ) ? $options['translation_target_lang'] : 'en';
private function translateBaidu($text, $options) {
$app_id = isset($options['baidu_app_id']) ? trim($options['baidu_app_id']) : '';
$secret_key = isset($options['baidu_secret_key']) ? trim($options['baidu_secret_key']) : '';
$source_lang = isset($options['translation_source_lang']) ? $options['translation_source_lang'] : 'auto';
$target_lang = isset($options['translation_target_lang']) ? $options['translation_target_lang'] : 'en';

if ( empty( $app_id ) || empty( $secret_key ) ) {
return $this->fallbackTranslate( $text, $options );
if (empty($app_id) || empty($secret_key)) {
return $this->fallbackTranslate($text, $options);
}

if ( strlen( $text ) > 6000 ) {
return $this->fallbackTranslate( $text, $options );
if (strlen($text) > 6000) {
return $this->fallbackTranslate($text, $options);
}

try {
$salt = wp_rand( 10000, 99999 );
$sign = md5( $app_id . $text . $salt . $secret_key );
$salt = wp_rand(10000, 99999);
$sign = md5($app_id . $text . $salt . $secret_key);

$url = 'https://fanyi-api.baidu.com/api/trans/vip/translate';
$params = array(
@ -143,55 +140,52 @@ 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'] ) {
error_log( 'WPSlug Baidu Translate Error: ' . $response->get_error_message() );
if (is_wp_error($response)) {
if (isset($options['debug_mode']) && $options['debug_mode']) {
error_log('WPSlug Baidu Translate Error: ' . $response->get_error_message());
}
return $this->fallbackTranslate( $text, $options );
return $this->fallbackTranslate($text, $options);
}

$response_code = wp_remote_retrieve_response_code( $response );
if ( $response_code !== 200 ) {
if ( isset( $options['debug_mode'] ) && $options['debug_mode'] ) {
error_log( 'WPSlug Baidu Translate HTTP Error: ' . $response_code );
$response_code = wp_remote_retrieve_response_code($response);
if ($response_code !== 200) {
if (isset($options['debug_mode']) && $options['debug_mode']) {
error_log('WPSlug Baidu Translate HTTP Error: ' . $response_code);
}
return $this->fallbackTranslate( $text, $options );
return $this->fallbackTranslate($text, $options);
}

$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body, true );
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);

if ( isset( $data['trans_result'][0]['dst'] ) ) {
if (isset($data['trans_result'][0]['dst'])) {
$translated = $data['trans_result'][0]['dst'];
return $this->cleanTranslatedText( $translated );
return $this->cleanTranslatedText($translated);
}

if ( isset( $data['error_code'] ) ) {
if ( isset( $options['debug_mode'] ) && $options['debug_mode'] ) {
error_log( 'WPSlug Baidu Translate API Error: ' . $data['error_code'] );
if (isset($data['error_code'])) {
if (isset($options['debug_mode']) && $options['debug_mode']) {
error_log('WPSlug Baidu Translate API Error: ' . $data['error_code']);
}
}

return $this->fallbackTranslate( $text, $options );
} catch ( Exception $e ) {
if ( isset( $options['debug_mode'] ) && $options['debug_mode'] ) {
error_log( 'WPSlug translateBaidu error: ' . $e->getMessage() );
return $this->fallbackTranslate($text, $options);
} catch (Exception $e) {
if (isset($options['debug_mode']) && $options['debug_mode']) {
error_log('WPSlug translateBaidu error: ' . $e->getMessage());
}
return $this->fallbackTranslate( $text, $options );
return $this->fallbackTranslate($text, $options);
}
}

@ -202,140 +196,135 @@ class WPSlug_Translator {
* @param array $options 选项
* @return string 翻译后的文本slug 格式)
*/
private function translateWPMind( $text, $options ) {
$debug_mode = isset( $options['debug_mode'] ) && $options['debug_mode'];
private function translateWPMind($text, $options) {
$debug_mode = isset($options['debug_mode']) && $options['debug_mode'];
// 获取语言设置(提前获取以便用于缓存键)
$source_lang = isset( $options['translation_source_lang'] ) ? $options['translation_source_lang'] : 'zh';
$target_lang = isset( $options['translation_target_lang'] ) ? $options['translation_target_lang'] : 'en';
$source_lang = isset($options['translation_source_lang']) ? $options['translation_source_lang'] : 'zh';
$target_lang = isset($options['translation_target_lang']) ? $options['translation_target_lang'] : 'en';
// 1. 先检查本地缓存(缓存键包含语言设置)
$cache_key = 'wpslug_wpmind_' . md5( $text . '_' . $source_lang . '_' . $target_lang );
$cached = get_transient( $cache_key );
if ( $cached !== false ) {
if ( $debug_mode ) {
error_log( '[WPSlug] WPMind cache hit for: ' . $text . ' (' . $source_lang . ' -> ' . $target_lang . ')' );
$cache_key = 'wpslug_wpmind_' . md5($text . '_' . $source_lang . '_' . $target_lang);
$cached = get_transient($cache_key);
if ($cached !== false) {
if ($debug_mode) {
error_log('[WPSlug] WPMind cache hit for: ' . $text . ' (' . $source_lang . ' -> ' . $target_lang . ')');
}
return $cached;
}

// 2. 检查 WPMind 是否可用
if ( ! function_exists( 'wpmind_is_available' ) || ! wpmind_is_available() ) {
if ( $debug_mode ) {
error_log( '[WPSlug] WPMind not available, falling back to pinyin' );
if (!function_exists('wpmind_is_available') || !wpmind_is_available()) {
if ($debug_mode) {
error_log('[WPSlug] WPMind not available, falling back to pinyin');
}
return $this->fallbackTranslate( $text, $options );
return $this->fallbackTranslate($text, $options);
}

// 3. 文本长度限制(避免超时)
if ( mb_strlen( $text ) > 200 ) {
if ( $debug_mode ) {
error_log( '[WPSlug] Text too long (' . mb_strlen( $text ) . ' chars), using pinyin' );
if (mb_strlen($text) > 200) {
if ($debug_mode) {
error_log('[WPSlug] Text too long (' . mb_strlen($text) . ' chars), using pinyin');
}
return $this->fallbackTranslate( $text, $options );
return $this->fallbackTranslate($text, $options);
}

// 4. 中文字符数限制
$chinese_count = preg_match_all( '/[\x{4e00}-\x{9fff}]/u', $text );
if ( $chinese_count > 50 ) {
if ( $debug_mode ) {
error_log( '[WPSlug] Too many Chinese characters (' . $chinese_count . '), using pinyin' );
$chinese_count = preg_match_all('/[\x{4e00}-\x{9fff}]/u', $text);
if ($chinese_count > 50) {
if ($debug_mode) {
error_log('[WPSlug] Too many Chinese characters (' . $chinese_count . '), using pinyin');
}
return $this->fallbackTranslate( $text, $options );
return $this->fallbackTranslate($text, $options);
}

// 5. 调用 WPMind API语言设置已在前面获取
$start_time = microtime( true );
$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 );
$elapsed_time = round((microtime(true) - $start_time) * 1000);

// 7. 处理结果
if ( is_wp_error( $result ) ) {
if ( $debug_mode ) {
error_log( '[WPSlug] WPMind error: ' . $result->get_error_message() . ' (took ' . $elapsed_time . 'ms)' );
if (is_wp_error($result)) {
if ($debug_mode) {
error_log('[WPSlug] WPMind error: ' . $result->get_error_message() . ' (took ' . $elapsed_time . 'ms)');
}
return $this->fallbackTranslate( $text, $options );
return $this->fallbackTranslate($text, $options);
}

$slug = $this->cleanTranslatedText( $result );
$slug = $this->cleanTranslatedText($result);

// 8. 验证结果有效性
if ( empty( $slug ) ) {
if ( $debug_mode ) {
error_log( '[WPSlug] WPMind returned empty result, using pinyin' );
if (empty($slug)) {
if ($debug_mode) {
error_log('[WPSlug] WPMind returned empty result, using pinyin');
}
return $this->fallbackTranslate( $text, $options );
return $this->fallbackTranslate($text, $options);
}

// 9. 缓存结果7 天)
set_transient( $cache_key, $slug, 7 * DAY_IN_SECONDS );
set_transient($cache_key, $slug, 7 * DAY_IN_SECONDS);

if ( $debug_mode ) {
error_log( '[WPSlug] WPMind translated "' . $text . '" to "' . $slug . '" in ' . $elapsed_time . 'ms' );
if ($debug_mode) {
error_log('[WPSlug] WPMind translated "' . $text . '" to "' . $slug . '" in ' . $elapsed_time . 'ms');
}

return $slug;
}

private function fallbackTranslate( $text, $options ) {
if ( $this->converter === null ) {
private function fallbackTranslate($text, $options) {
if ($this->converter === null) {
$this->converter = new WPSlug_Converter();
}
$fallback_options = $options;
$fallback_options['conversion_mode'] = 'pinyin';
return $this->converter->convert( $text, $fallback_options );
return $this->converter->convert($text, $fallback_options);
}

private function cleanTranslatedText( $text ) {
$text = strip_tags( $text );
$text = html_entity_decode( $text, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
$text = preg_replace( '/&.+?;/', '', $text );
$text = preg_replace( '/[^a-zA-Z0-9\s\-_]/', '', $text );
$text = preg_replace( '/\s+/', '-', $text );
$text = preg_replace( '/\-+/', '-', $text );
$text = trim( $text, '-_' );
private function cleanTranslatedText($text) {
$text = strip_tags($text);
$text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$text = preg_replace('/&.+?;/', '', $text);
$text = preg_replace('/[^a-zA-Z0-9\s\-_]/', '', $text);
$text = preg_replace('/\s+/', '-', $text);
$text = preg_replace('/\-+/', '-', $text);
$text = trim($text, '-_');
return $text;
}

public function batchTranslate( $items, $options = array() ) {
public function batchTranslate($items, $options = array()) {
$results = array();

if ( ! is_array( $items ) ) {
if (!is_array($items)) {
return $results;
}

foreach ( $items as $item ) {
foreach ($items as $item) {
try {
$translated = $this->translate( $item, $options );
$translated = $this->translate($item, $options);
$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'] ) {
error_log( 'WPSlug batchTranslate error: ' . $e->getMessage() );
} catch (Exception $e) {
if (isset($options['debug_mode']) && $options['debug_mode']) {
error_log('WPSlug batchTranslate error: ' . $e->getMessage());
}
$results[] = array(
'original' => $item,
'translated' => sanitize_title( $item ),
'service' => 'fallback',
'translated' => sanitize_title($item),
'service' => 'fallback'
);
}
}
@ -344,45 +333,45 @@ class WPSlug_Translator {
}

public function getSupportedServices() {
return array( 'none', 'google', 'baidu' );
return array('none', 'google', 'baidu');
}

public function isServiceSupported( $service ) {
return in_array( $service, $this->getSupportedServices() );
public function isServiceSupported($service) {
return in_array($service, $this->getSupportedServices());
}

public function isServiceConfigured( $service, $options ) {
switch ( $service ) {
public function isServiceConfigured($service, $options) {
switch ($service) {
case 'google':
return ! empty( $options['google_api_key'] );
return !empty($options['google_api_key']);
case 'baidu':
return ! empty( $options['baidu_app_id'] ) && ! empty( $options['baidu_secret_key'] );
return !empty($options['baidu_app_id']) && !empty($options['baidu_secret_key']);
case 'none':
default:
return true;
}
}

public function detectLanguage( $text ) {
if ( preg_match( '/[\x{4e00}-\x{9fff}]/u', $text ) ) {
public function detectLanguage($text) {
if (preg_match('/[\x{4e00}-\x{9fff}]/u', $text)) {
return 'zh';
}
if ( preg_match( '/[\x{0400}-\x{04ff}]/u', $text ) ) {
if (preg_match('/[\x{0400}-\x{04ff}]/u', $text)) {
return 'ru';
}
if ( preg_match( '/[\x{0590}-\x{05ff}]/u', $text ) ) {
if (preg_match('/[\x{0590}-\x{05ff}]/u', $text)) {
return 'he';
}
if ( preg_match( '/[\x{0600}-\x{06ff}]/u', $text ) ) {
if (preg_match('/[\x{0600}-\x{06ff}]/u', $text)) {
return 'ar';
}
if ( preg_match( '/[\x{3040}-\x{309f}]/u', $text ) ) {
if (preg_match('/[\x{3040}-\x{309f}]/u', $text)) {
return 'ja';
}
if ( preg_match( '/[\x{30a0}-\x{30ff}]/u', $text ) ) {
if (preg_match('/[\x{30a0}-\x{30ff}]/u', $text)) {
return 'ja';
}
if ( preg_match( '/[\x{ac00}-\x{d7af}]/u', $text ) ) {
if (preg_match('/[\x{ac00}-\x{d7af}]/u', $text)) {
return 'ko';
}

View file

@ -1,6 +1,6 @@
<?php

if ( ! defined( 'ABSPATH' ) ) {
if (!defined('ABSPATH')) {
exit;
}

@ -11,401 +11,187 @@ class WPSlug_Transliterator {
$this->initCharMaps();
}

public function transliterate( $text, $options = array() ) {
if ( empty( $text ) ) {
public function transliterate($text, $options = array()) {
if (empty($text)) {
return '';
}

$method = isset( $options['transliteration_method'] ) ? $options['transliteration_method'] : 'basic';
$method = isset($options['transliteration_method']) ? $options['transliteration_method'] : 'basic';
switch ( $method ) {
switch ($method) {
case 'iconv':
return $this->transliterateIconv( $text, $options );
return $this->transliterateIconv($text, $options);
case 'intl':
return $this->transliterateIntl( $text, $options );
return $this->transliterateIntl($text, $options);
case 'basic':
default:
return $this->transliterateBasic( $text, $options );
return $this->transliterateBasic($text, $options);
}
}

private function transliterateBasic( $text, $options ) {
$text = $this->applyCharMaps( $text );
$text = $this->cleanSlug( $text );
private function transliterateBasic($text, $options) {
$text = $this->applyCharMaps($text);
$text = $this->cleanSlug($text);
if ( isset( $options['force_lowercase'] ) && $options['force_lowercase'] ) {
$text = strtolower( $text );
if (isset($options['force_lowercase']) && $options['force_lowercase']) {
$text = strtolower($text);
}
return $text;
}

private function transliterateIconv( $text, $options ) {
if ( ! function_exists( 'iconv' ) ) {
return $this->transliterateBasic( $text, $options );
private function transliterateIconv($text, $options) {
if (!function_exists('iconv')) {
return $this->transliterateBasic($text, $options);
}
try {
$text = iconv( 'UTF-8', 'ASCII//TRANSLIT//IGNORE', $text );
$text = $this->cleanSlug( $text );
$text = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $text);
$text = $this->cleanSlug($text);
if ( isset( $options['force_lowercase'] ) && $options['force_lowercase'] ) {
$text = strtolower( $text );
if (isset($options['force_lowercase']) && $options['force_lowercase']) {
$text = strtolower($text);
}
return $text;
} catch ( Exception $e ) {
return $this->transliterateBasic( $text, $options );
} catch (Exception $e) {
return $this->transliterateBasic($text, $options);
}
}

private function transliterateIntl( $text, $options ) {
if ( ! class_exists( 'Transliterator' ) ) {
return $this->transliterateIconv( $text, $options );
private function transliterateIntl($text, $options) {
if (!class_exists('Transliterator')) {
return $this->transliterateIconv($text, $options);
}
try {
$transliterator = Transliterator::create( 'Any-Latin; Latin-ASCII' );
if ( $transliterator ) {
$text = $transliterator->transliterate( $text );
$text = $this->cleanSlug( $text );
$transliterator = Transliterator::create('Any-Latin; Latin-ASCII');
if ($transliterator) {
$text = $transliterator->transliterate($text);
$text = $this->cleanSlug($text);
if ( isset( $options['force_lowercase'] ) && $options['force_lowercase'] ) {
$text = strtolower( $text );
if (isset($options['force_lowercase']) && $options['force_lowercase']) {
$text = strtolower($text);
}
return $text;
}
} catch ( Exception $e ) {
if ( isset( $options['debug_mode'] ) && $options['debug_mode'] ) {
error_log( 'WPSlug transliterateIntl error: ' . $e->getMessage() );
} catch (Exception $e) {
if (isset($options['debug_mode']) && $options['debug_mode']) {
error_log('WPSlug transliterateIntl error: ' . $e->getMessage());
}
}
return $this->transliterateIconv( $text, $options );
return $this->transliterateIconv($text, $options);
}

private function applyCharMaps( $text ) {
foreach ( $this->char_maps as $from => $to ) {
$text = str_replace( $from, $to, $text );
private function applyCharMaps($text) {
foreach ($this->char_maps as $from => $to) {
$text = str_replace($from, $to, $text);
}
return $text;
}

private function cleanSlug( $text ) {
$text = strip_tags( $text );
$text = html_entity_decode( $text, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
$text = preg_replace( '/&.+?;/', '', $text );
$text = preg_replace( '/[^a-zA-Z0-9\s\-_]/', '', $text );
$text = preg_replace( '/\s+/', '-', $text );
$text = preg_replace( '/\-+/', '-', $text );
$text = trim( $text, '-_' );
private function cleanSlug($text) {
$text = strip_tags($text);
$text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$text = preg_replace('/&.+?;/', '', $text);
$text = preg_replace('/[^a-zA-Z0-9\s\-_]/', '', $text);
$text = preg_replace('/\s+/', '-', $text);
$text = preg_replace('/\-+/', '-', $text);
$text = trim($text, '-_');
return $text;
}

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'
);
}

public function getSupportedMethods() {
return array( 'basic', 'iconv', 'intl' );
return array('basic', 'iconv', 'intl');
}

public function isMethodSupported( $method ) {
return in_array( $method, $this->getSupportedMethods() );
public function isMethodSupported($method) {
return in_array($method, $this->getSupportedMethods());
}

public function getAvailableMethods() {
$methods = array( 'basic' );
$methods = array('basic');
if ( function_exists( 'iconv' ) ) {
if (function_exists('iconv')) {
$methods[] = 'iconv';
}
if ( class_exists( 'Transliterator' ) ) {
if (class_exists('Transliterator')) {
$methods[] = 'intl';
}

View file

@ -1,233 +1,203 @@
<?php

if ( ! defined( 'ABSPATH' ) ) {
if (!defined('ABSPATH')) {
exit;
}

class WPSlug_Validator {
public static function validateBoolean( $value ) {
if ( is_bool( $value ) ) {
public static function validateBoolean($value) {
if (is_bool($value)) {
return $value;
}
if ( is_string( $value ) ) {
return ( $value === '1' || strtolower( $value ) === 'true' );
if (is_string($value)) {
return ($value === '1' || strtolower($value) === 'true');
}
if ( is_numeric( $value ) ) {
return (int) $value === 1;
if (is_numeric($value)) {
return (int)$value === 1;
}
return false;
}
public static function validateInteger( $value, $min = null, $max = null ) {
$int_value = intval( $value );
public static function validateInteger($value, $min = null, $max = null) {
$int_value = intval($value);
if ( $min !== null && $int_value < $min ) {
if ($min !== null && $int_value < $min) {
return $min;
}
if ( $max !== null && $int_value > $max ) {
if ($max !== null && $int_value > $max) {
return $max;
}
return $int_value;
}
public static function validateString( $value, $max_length = 200 ) {
$string_value = sanitize_text_field( $value );
public static function validateString($value, $max_length = 200) {
$string_value = sanitize_text_field($value);
if ( strlen( $string_value ) > $max_length ) {
return substr( $string_value, 0, $max_length );
if (strlen($string_value) > $max_length) {
return substr($string_value, 0, $max_length);
}
return $string_value;
}
public static function validateTextarea( $value ) {
return sanitize_textarea_field( $value );
public static function validateTextarea($value) {
return sanitize_textarea_field($value);
}
public static function validateArray( $value, $default = array() ) {
if ( ! is_array( $value ) ) {
public static function validateArray($value, $default = array()) {
if (!is_array($value)) {
return $default;
}
$sanitized = array_map( 'sanitize_text_field', $value );
$filtered = array_filter( $sanitized );
$sanitized = array_map('sanitize_text_field', $value);
$filtered = array_filter($sanitized);
return array_slice( $filtered, 0, 50 );
return array_slice($filtered, 0, 50);
}
public static function validateSelect( $value, $valid_options, $default ) {
return in_array( $value, $valid_options, true ) ? $value : $default;
public static function validateSelect($value, $valid_options, $default) {
return in_array($value, $valid_options, true) ? $value : $default;
}
public static function validateApiKey( $value ) {
return self::validateString( $value, 200 );
public static function validateApiKey($value) {
return self::validateString($value, 200);
}
public static function validateSlug( $slug ) {
if ( empty( $slug ) ) {
public static function validateSlug($slug) {
if (empty($slug)) {
return false;
}
if ( strlen( $slug ) > 200 ) {
if (strlen($slug) > 200) {
return false;
}
if ( preg_match( '/[^a-zA-Z0-9\-_\p{Han}]/u', $slug ) ) {
if (preg_match('/[^a-zA-Z0-9\-_\p{Han}]/u', $slug)) {
return false;
}
return true;
}
public static function validateLanguageCode( $code ) {
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';
return in_array($code, $valid_codes, true) ? $code : 'auto';
}
public static function validatePostTypes( $post_types ) {
if ( ! is_array( $post_types ) ) {
return array( 'post', 'page' );
public static function validatePostTypes($post_types) {
if (!is_array($post_types)) {
return array('post', 'page');
}
$all_post_types = get_post_types( array( 'public' => true ) );
$all_post_types = get_post_types(array('public' => true));
$validated = array();
foreach ( $post_types as $post_type ) {
if ( in_array( $post_type, $all_post_types ) ) {
foreach ($post_types as $post_type) {
if (in_array($post_type, $all_post_types)) {
$validated[] = $post_type;
}
}
return empty( $validated ) ? array( 'post', 'page' ) : $validated;
return empty($validated) ? array('post', 'page') : $validated;
}
public static function validateTaxonomies( $taxonomies ) {
if ( ! is_array( $taxonomies ) ) {
return array( 'category', 'post_tag' );
public static function validateTaxonomies($taxonomies) {
if (!is_array($taxonomies)) {
return array('category', 'post_tag');
}
$all_taxonomies = get_taxonomies( array( 'public' => true ) );
$all_taxonomies = get_taxonomies(array('public' => true));
$validated = array();
foreach ( $taxonomies as $taxonomy ) {
if ( in_array( $taxonomy, $all_taxonomies ) ) {
foreach ($taxonomies as $taxonomy) {
if (in_array($taxonomy, $all_taxonomies)) {
$validated[] = $taxonomy;
}
}
return empty( $validated ) ? array( 'category', 'post_tag' ) : $validated;
return empty($validated) ? array('category', 'post_tag') : $validated;
}
public static function validateConversionMode( $mode ) {
$valid_modes = array( 'pinyin', 'semantic_pinyin', 'transliteration', 'translation' );
return self::validateSelect( $mode, $valid_modes, 'pinyin' );
public static function validateConversionMode($mode) {
$valid_modes = array('pinyin', 'semantic_pinyin', 'transliteration', 'translation');
return self::validateSelect($mode, $valid_modes, 'pinyin');
}
public static function validateTranslationService( $service ) {
public static function validateTranslationService($service) {
// 注意wpmind 服务需要在此列表中才能保存
$valid_services = array( 'none', 'google', 'baidu', 'wpmind' );
return self::validateSelect( $service, $valid_services, 'none' );
$valid_services = array('none', 'google', 'baidu', 'wpmind');
return self::validateSelect($service, $valid_services, 'none');
}
public static function validateTransliterationMethod( $method ) {
$valid_methods = array( 'basic', 'iconv', 'intl' );
return self::validateSelect( $method, $valid_methods, 'basic' );
public static function validateTransliterationMethod($method) {
$valid_methods = array('basic', 'iconv', 'intl');
return self::validateSelect($method, $valid_methods, 'basic');
}
public static function hasRequiredApiCredentials( $options ) {
public static function hasRequiredApiCredentials($options) {
$errors = array();
if ( $options['conversion_mode'] === 'translation' ) {
if ($options['conversion_mode'] === 'translation') {
$service = $options['translation_service'];
if ( $service === 'google' && empty( $options['google_api_key'] ) ) {
$errors[] = __( 'Google API key is required for Google Translate service.', 'wpslug' );
if ($service === 'google' && empty($options['google_api_key'])) {
$errors[] = __('Google API key is required for Google Translate service.', 'wpslug');
}
if ( $service === 'baidu' && ( empty( $options['baidu_app_id'] ) || empty( $options['baidu_secret_key'] ) ) ) {
$errors[] = __( 'Baidu App ID and Secret Key are required for Baidu Translate service.', 'wpslug' );
if ($service === 'baidu' && (empty($options['baidu_app_id']) || empty($options['baidu_secret_key']))) {
$errors[] = __('Baidu App ID and Secret Key are required for Baidu Translate service.', 'wpslug');
}
// WPMind 需要已配置
if ( $service === 'wpmind' ) {
if ( ! function_exists( 'wpmind_is_available' ) ) {
$errors[] = __( 'WPMind plugin is not installed.', 'wpslug' );
} elseif ( ! wpmind_is_available() ) {
$errors[] = __( 'WPMind is not configured. Please configure it in WPMind settings.', 'wpslug' );
if ($service === 'wpmind') {
if (!function_exists('wpmind_is_available')) {
$errors[] = __('WPMind plugin is not installed.', 'wpslug');
} elseif (!wpmind_is_available()) {
$errors[] = __('WPMind is not configured. Please configure it in WPMind settings.', 'wpslug');
}
}
}
return empty( $errors ) ? true : $errors;
return empty($errors) ? true : $errors;
}
public static function validateSystemRequirements() {
$requirements = array(
'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' ),
'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')
);
$errors = array();
if ( ! $requirements['php_version'] ) {
$errors[] = __( 'PHP 7.0 or higher is required.', 'wpslug' );
if (!$requirements['php_version']) {
$errors[] = __('PHP 7.0 or higher is required.', 'wpslug');
}
if ( ! $requirements['wordpress_version'] ) {
$errors[] = __( 'WordPress 5.0 or higher is required.', 'wpslug' );
if (!$requirements['wordpress_version']) {
$errors[] = __('WordPress 5.0 or higher is required.', 'wpslug');
}
if ( ! $requirements['mbstring_extension'] ) {
$errors[] = __( 'PHP mbstring extension is required.', 'wpslug' );
if (!$requirements['mbstring_extension']) {
$errors[] = __('PHP mbstring extension is required.', 'wpslug');
}
if ( ! $requirements['json_extension'] ) {
$errors[] = __( 'PHP JSON extension is required.', 'wpslug' );
if (!$requirements['json_extension']) {
$errors[] = __('PHP JSON extension is required.', 'wpslug');
}
return empty( $errors ) ? true : $errors;
return empty($errors) ? true : $errors;
}
}

View file

@ -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>

View file

@ -15,38 +15,41 @@ 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() {
if ( self::$instance === null ) {
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() {
if ( ! $this->checkRequirements() ) {
public function loadPlugin()
{
if (!$this->checkRequirements()) {
return;
}

@ -56,78 +59,77 @@ 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 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 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';
if (is_admin()) {
require_once WPSLUG_PLUGIN_DIR . "includes/class-wpslug-admin.php";
}
}

public function initLanguages() {
$locale = apply_filters( 'plugin_locale', get_locale(), 'wpslug' );
public function initLanguages()
{
$locale = apply_filters('plugin_locale', get_locale(), 'wpslug');
$mo_file = WPSLUG_PLUGIN_DIR . "languages/wpslug-{$locale}.mo";
if ( file_exists( $mo_file ) ) {
load_textdomain( 'wpslug', $mo_file );
if (file_exists($mo_file)) {
load_textdomain('wpslug', $mo_file);
}
}

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() ) {
deactivate_plugins( plugin_basename( __FILE__ ) );
if (!$this->checkRequirements()) {
deactivate_plugins(plugin_basename(__FILE__));
wp_die(
esc_html__( 'WP Slug plugin requirements not met. Please check your PHP and WordPress versions.', 'wpslug' ),
esc_html__( 'Plugin Activation Error', 'wpslug' ),
array( 'back_link' => true )
esc_html__('WP Slug plugin requirements not met. Please check your PHP and WordPress versions.', 'wpslug'),
esc_html__('Plugin Activation Error', 'wpslug'),
array('back_link' => true)
);
}

@ -140,88 +142,92 @@ class WPSlug {
$core = new WPSlug_Core();
$core->activate();
add_option( 'wpslug_activation_redirect', true );
add_option('wpslug_activation_redirect', true);
} catch ( Exception $e ) {
error_log( 'WP Slug activation error: ' . $e->getMessage() );
} catch (Exception $e) {
error_log('WP Slug activation error: ' . $e->getMessage());
wp_die(
esc_html__( 'An error occurred during plugin activation. Please check your server logs.', 'wpslug' ),
esc_html__( 'Plugin Activation Error', 'wpslug' ),
array( 'back_link' => true )
esc_html__('An error occurred during plugin activation. Please check your server logs.', 'wpslug'),
esc_html__('Plugin Activation Error', 'wpslug'),
array('back_link' => true)
);
}
}

public function deactivate() {
public function deactivate()
{
try {
if ( $this->core ) {
if ($this->core) {
$this->core->deactivate();
}
delete_option( 'wpslug_activation_redirect' );
delete_option('wpslug_activation_redirect');
} catch ( Exception $e ) {
error_log( 'WP Slug deactivation error: ' . $e->getMessage() );
} catch (Exception $e) {
error_log('WP Slug deactivation error: ' . $e->getMessage());
}
}

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();
}
delete_option( 'wpslug_activation_redirect' );
delete_option('wpslug_activation_redirect');
} catch ( Exception $e ) {
error_log( 'WP Slug uninstall error: ' . $e->getMessage() );
} catch (Exception $e) {
error_log('WP Slug uninstall error: ' . $e->getMessage());
}
}

public function getCore() {
public function getCore()
{
return $this->core;
}

public function getSettings() {
if ( $this->core ) {
public function getSettings()
{
if ($this->core) {
return $this->core->getSettings();
}
return null;
}

public function getConverter() {
if ( $this->core ) {
public function getConverter()
{
if ($this->core) {
return $this->core->getConverter();
}
return null;
}

public function getOptimizer() {
if ( $this->core ) {
public function getOptimizer()
{
if ($this->core) {
return $this->core->getOptimizer();
}
return null;
}
}

function wpslug() {
function wpslug()
{
return WPSlug::getInstance();
}

if ( is_admin() ) {
add_action(
'admin_init',
function () {
if ( get_option( 'wpslug_activation_redirect', false ) ) {
delete_option( 'wpslug_activation_redirect' );
if ( ! isset( $_GET['activate-multi'] ) ) {
wp_safe_redirect( admin_url( 'options-general.php?page=wpslug' ) );
if (is_admin()) {
add_action('admin_init', function() {
if (get_option('wpslug_activation_redirect', false)) {
delete_option('wpslug_activation_redirect');
if (!isset($_GET['activate-multi'])) {
wp_safe_redirect(admin_url('options-general.php?page=wpslug'));
exit;
}
}
}
);
});
}

wpslug();