diff --git a/assets/admin/src/constants/sections/permissions.js b/assets/admin/src/constants/sections/permissions.js index 839cc175..d4fc1e77 100644 --- a/assets/admin/src/constants/sections/permissions.js +++ b/assets/admin/src/constants/sections/permissions.js @@ -47,15 +47,5 @@ export const permissionsSection = { canonicalDomain: 'permissions', section: 'attachment', }), - schemaPage({ - key: 'permissions-download', - name: 'PermissionsDownload', - label: '下载配置', - route: '/permissions/download', - title: '下载配置', - desc: '下载链接别名、默认使用次数与有效期限规则。', - canonicalDomain: 'permissions', - section: 'download', - }), ], } diff --git a/assets/admin/src/schema/sections/permissions/index.js b/assets/admin/src/schema/sections/permissions/index.js index af0893b0..2826560c 100644 --- a/assets/admin/src/schema/sections/permissions/index.js +++ b/assets/admin/src/schema/sections/permissions/index.js @@ -338,41 +338,4 @@ export const permissionsSchemas = { ], }, - /** - * Download link configuration - */ - download: { - key: 'permissions.download', - title: '下载配置', - icon: 'fa fas fa-download', - description: '下载总开关与链接别名、默认使用次数、有效期限规则。', - defaults: { - enabled: true, - url_alias: 'download', - default_max_uses: 1, - ttl_minutes: 10, - }, - groups: [ - { - key: 'basic', - title: '下载总开关', - icon: 'fa fas fa-power-off', - fields: [ - { key: 'enabled', label: '启用本站下载链接', type: 'switch', help: '总开关:开启后下载通过本站中转链接;关闭后下载按钮直接使用原始下载地址(直连),下方链接规则全部不生效,旧的中转链接同步失效。' }, - ], - }, - { - key: 'rules', - title: '链接规则', - icon: 'fa fas fa-link', - description: '文件完整送达才计次(HEAD 探测、传输中断不计次);到期或耗尽后访问旧地址返回 410,需重新获取,不会自动续签。', - fields: [ - { key: 'url_alias', label: '下载链接 URL 别名', type: 'input', placeholder: 'download', required: true, dependsOn: { key: 'enabled', value: true }, help: '仅允许英文字母、数字、连字符和下划线;禁止中文、空格和 / \\ ? # & 等特殊符号。修改后旧别名路由立即失效。' }, - { key: 'default_max_uses', label: '下载链接默认使用次数', type: 'number', min: 1, max: 100, dependsOn: { key: 'enabled', value: true }, help: '单个链接可完成下载的次数;耗尽后链接返回 410,需重新获取。' }, - { key: 'ttl_minutes', label: '下载链接默认有效期限(分钟)', type: 'number', min: 1, max: 1440, dependsOn: { key: 'enabled', value: true }, help: '链接签发后超过该时长即失效并返回 410,不自动续期。' }, - ], - }, - ], - }, - }; diff --git a/src/Admin/Schema/SchemaPermissions.php b/src/Admin/Schema/SchemaPermissions.php index e66cccff..c8b53857 100644 --- a/src/Admin/Schema/SchemaPermissions.php +++ b/src/Admin/Schema/SchemaPermissions.php @@ -140,22 +140,6 @@ class SchemaPermissions { ]], ], ], - 'download' => [ - 'key' => 'download', - 'title' => '下载配置', - 'version' => '1.1.0', - 'option_key' => 'aksrt_download_config', - 'groups' => [ - ['key' => 'basic', 'title' => '下载总开关', 'fields' => [ - ['key' => 'enabled', 'label' => '启用本站下载链接', 'type' => 'switch', 'default' => true, 'help' => '总开关:开启后下载通过本站中转链接(别名、次数、有效期生效);关闭后下载按钮直接使用原始下载地址(直连),下方链接规则全部不生效,旧的中转链接同步失效。'], - ]], - ['key' => 'rules', 'title' => '链接规则', 'fields' => [ - ['key' => 'url_alias', 'label' => '下载链接 URL 别名', 'type' => 'input', 'default' => 'download', 'required' => true, 'dependsOn' => ['key' => 'enabled', 'value' => true], 'help' => '仅允许英文字母、数字、连字符和下划线;禁止中文、空格和 / \\ ? # & 等特殊符号。修改后旧别名路由立即失效,需重新获取下载地址。'], - ['key' => 'default_max_uses', 'label' => '下载链接默认使用次数', 'type' => 'number', 'default' => 1, 'min' => 1, 'max' => 100, 'dependsOn' => ['key' => 'enabled', 'value' => true], 'help' => '单个链接可完成下载的次数;文件完整送达后才计次(HEAD 探测、传输中断不计次)。耗尽后链接返回 410,需重新获取。'], - ['key' => 'ttl_minutes', 'label' => '下载链接默认有效期限(分钟)', 'type' => 'number', 'default' => 10, 'min' => 1, 'max' => 1440, 'dependsOn' => ['key' => 'enabled', 'value' => true], 'help' => '链接签发后超过该时长即失效并返回 410,不自动续期,需重新获取。'], - ]], - ], - ], 'moderation' => [ 'key' => 'moderation', 'title' => '评论审核', diff --git a/src/Admin/SchemaProvider.php b/src/Admin/SchemaProvider.php index 35c6b65b..10e78979 100644 --- a/src/Admin/SchemaProvider.php +++ b/src/Admin/SchemaProvider.php @@ -301,19 +301,6 @@ class SchemaProvider { } } - if ($domain === 'permissions' && $section === 'download') { - $alias = trim((string) ($data['url_alias'] ?? '')); - if ($alias === '') { - $errors[] = __('下载链接 URL 别名不能为空', 'aksrt'); - } elseif (strlen($alias) > 48) { - $errors[] = __('下载链接 URL 别名长度不能超过 48 个字符', 'aksrt'); - } elseif (!preg_match('/^[A-Za-z0-9_-]+$/', $alias)) { - $errors[] = __('下载链接 URL 别名只能包含英文字母、数字、连字符和下划线,禁止中文、空格和 / \\ ? # & 等特殊符号', 'aksrt'); - } elseif (in_array(strtolower($alias), $this->reserved_download_aliases(), true)) { - $errors[] = __('该别名与系统保留路径冲突,请更换其他别名以避免路由 404', 'aksrt'); - } - } - return ['valid' => empty($errors), 'errors' => $errors]; } @@ -443,18 +430,6 @@ class SchemaProvider { && empty($parts['fragment']); } - /** - * Top-level slugs that must never be shadowed by the download route. - */ - private function reserved_download_aliases(): array { - return [ - 'wp-json', 'wp-admin', 'wp-content', 'wp-includes', - 'admin', 'login', 'logout', 'register', 'lostpassword', - 'feed', 'search', 'author', 'page', 'comments', - 'sitemap', 'sign', 'cgi-bin', - ]; - } - public function read_settings(string $domain, string $section = ''): array { if ($section !== '') { $schema = $this->get_section_schema($domain, $section); diff --git a/src/Settings/Settings.php b/src/Settings/Settings.php index bbdbecfb..b651fdd1 100644 --- a/src/Settings/Settings.php +++ b/src/Settings/Settings.php @@ -36,7 +36,6 @@ class Settings { public const STANDALONE_OPTION_KEYS = [ 'aksrt_captcha_config', 'aksrt_moderation_config', - 'aksrt_download_config', 'aksrt_footer_tabbar', 'aksrt_floating_buttons', 'aksrt_credit_settings', diff --git a/tests/widget-system/download-config-contract.test.cjs b/tests/widget-system/download-config-contract.test.cjs index a7feb105..21b4c37a 100644 --- a/tests/widget-system/download-config-contract.test.cjs +++ b/tests/widget-system/download-config-contract.test.cjs @@ -8,60 +8,6 @@ const test = require('node:test'); const root = path.resolve(__dirname, '..', '..'); const read = (file) => fs.readFileSync(path.join(root, file), 'utf8'); -test('download settings section exists on both schema sides with matching defaults', () => { - const php = read('src/Admin/Schema/SchemaPermissions.php'); - const vue = read('assets/admin/src/schema/sections/permissions/index.js'); - - // PHP side is the authoritative contract. - assert.match(php, /'download' => \[/); - assert.match(php, /'key'\s+=> 'download'/); - assert.match(php, /'option_key' => 'aksrt_download_config'/); - - // Field parity: same four keys with the same defaults on both ends. - const fields = [ - ['enabled', 'true', 'enabled: true'], - ['url_alias', "'download'", "url_alias: 'download'"], - ['default_max_uses', '1', 'default_max_uses: 1'], - ['ttl_minutes', '10', 'ttl_minutes: 10'], - ]; - for (const [key, phpDefault, vueDefault] of fields) { - const phpField = new RegExp(`'key' => '${key}'[^\\]]*'default' => ${phpDefault}`); - assert.match(php, phpField, `PHP default mismatch for ${key}`); - assert.match(vue, new RegExp(`${vueDefault}`), `Vue default mismatch for ${key}`); - } - - // The link rules depend on the master switch on both ends. - assert.match(php, /'title' => '下载总开关'/); - assert.match(php, /'dependsOn' => \['key' => 'enabled', 'value' => true\]/); - assert.match(vue, /dependsOn: \{ key: 'enabled', value: true \}/); - - // Vue mirrors labels so admins see the same wording as validation errors. - assert.match(vue, /download: \{/); - assert.match(vue, /下载链接 URL 别名/); - assert.match(vue, /下载链接默认使用次数/); - assert.match(vue, /下载链接默认有效期限(分钟)/); -}); - -test('the download settings page is registered under the permissions menu', () => { - const menu = read('assets/admin/src/constants/sections/permissions.js'); - assert.match(menu, /schemaPage\(\{\s*key: 'permissions-download'/); - assert.match(menu, /route: '\/permissions\/download'/); - assert.match(menu, /canonicalDomain: 'permissions',\s*section: 'download',/); - - const settings = read('src/Settings/Settings.php'); - assert.match(settings, /'aksrt_download_config'/); -}); - -test('invalid url aliases are rejected server-side with admin-facing messages', () => { - const provider = read('src/Admin/SchemaProvider.php'); - assert.match(provider, /\$domain === 'permissions' && \$section === 'download'/); - assert.match(provider, /URL 别名不能为空/); - assert.match(provider, /\/\^\[A-Za-z0-9_-\]\+\$\//); - assert.match(provider, /禁止中文、空格和 \/ \\\\ \? # & 等特殊符号/); - assert.match(provider, /reserved_download_aliases/); - assert.match(provider, /'wp-json', 'wp-admin', 'wp-content', 'wp-includes'/); -}); - test('Download.php derives endpoint, ttl and max uses from configuration', () => { const dl = read('src/Content/Download.php');