diff --git a/assets/admin/src/pages/content/FloatingButtonsSettings.vue b/assets/admin/src/pages/content/FloatingButtonsSettings.vue
index 09082ab9..87c22d7d 100644
--- a/assets/admin/src/pages/content/FloatingButtonsSettings.vue
+++ b/assets/admin/src/pages/content/FloatingButtonsSettings.vue
@@ -490,7 +490,7 @@ function applyAddPreset(key) {
}
const presetFactories = {
- post: () => ({ label: '写文章', icon: 'fas fa-pen-nib', url: `${adminUrl || siteUrl}/post-new.php` }),
+ post: () => ({ label: '写文章', icon: 'fas fa-pen-nib', url: `${siteUrl}/submit/` }),
submit: () => ({ label: '投稿页', icon: 'fas fa-paper-plane', url: `${siteUrl}/submit/` }),
profile: () => ({ label: '创作中心', icon: 'fas fa-user-edit', url: `${siteUrl}/user/` }),
}
@@ -518,7 +518,7 @@ function isAddPresetExists(key) {
function getAddPresetValue(key) {
const presetFactories = {
- post: () => ({ label: '写文章', icon: 'fas fa-pen-nib', url: `${adminUrl || siteUrl}/post-new.php` }),
+ post: () => ({ label: '写文章', icon: 'fas fa-pen-nib', url: `${siteUrl}/submit/` }),
submit: () => ({ label: '投稿页', icon: 'fas fa-paper-plane', url: `${siteUrl}/submit/` }),
profile: () => ({ label: '创作中心', icon: 'fas fa-user-edit', url: `${siteUrl}/user/` }),
}
diff --git a/assets/admin/src/pages/content/FooterTabbarSettings.vue b/assets/admin/src/pages/content/FooterTabbarSettings.vue
index 041ca205..0595b09e 100644
--- a/assets/admin/src/pages/content/FooterTabbarSettings.vue
+++ b/assets/admin/src/pages/content/FooterTabbarSettings.vue
@@ -365,7 +365,7 @@ function getItemSummary(item) {
}
const addPresets = [
- { label: '写文章', icon: 'fas fa-pen-fancy', url: '/post-new' },
+ { label: '写文章', icon: 'fas fa-pen-fancy', url: '/submit' },
{ label: '投稿页', icon: 'fas fa-file-alt', url: '/submit' },
{ label: '发布资源', icon: 'fas fa-cloud-upload-alt', url: '/resource-new' },
{ label: '创作中心', icon: 'fas fa-palette', url: '/creator' },
diff --git a/assets/admin/src/schema/sections/post-list/submit.js b/assets/admin/src/schema/sections/post-list/submit.js
index 940df576..a17d05c4 100644
--- a/assets/admin/src/schema/sections/post-list/submit.js
+++ b/assets/admin/src/schema/sections/post-list/submit.js
@@ -22,6 +22,8 @@ export const submitSchema = {
front_submit_editor_mode: 'rich',
front_submit_draft_save: true,
front_submit_leave_warning: true,
+ front_submit_guest_enabled: false,
+ front_submit_captcha: false,
post_article_btn_txte: '发布文章',
post_article_title_strlen_limit_min: 5,
post_article_title_strlen_limit_max: 30,
@@ -59,7 +61,9 @@ export const submitSchema = {
{ key: 'front_submit_cover', label: '封面图', type: 'switch', default: true, dependsOn: { key: 'front_submit_enabled', value: true } },
{ key: 'front_submit_category', label: '分类选择', type: 'switch', default: true, dependsOn: { key: 'front_submit_enabled', value: true } },
{ key: 'post_article_cat', label: '允许投稿分类', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [], help: '为空则允许全部分类', dependsOn: { key: 'front_submit_enabled', value: true } },
- { key: 'post_article_user', label: '免登录归属用户', type: 'user_select', default: 0, help: '免登录投稿时文章归属的用户,搜索选择', dependsOn: { key: 'front_submit_enabled', value: true } },
+ { key: 'front_submit_guest_enabled', label: '允许免登录投稿', type: 'switch', default: false, help: '开启后访客可投稿;文章归属到下方用户', dependsOn: { key: 'front_submit_enabled', value: true } },
+ { key: 'post_article_user', label: '免登录归属用户', type: 'user_select', default: 0, help: '免登录投稿时文章归属的用户,搜索选择', dependsOn: { key: 'front_submit_guest_enabled', value: true } },
+ { key: 'front_submit_captcha', label: '提交人机验证', type: 'switch', default: false, help: '提交正式投稿时校验人机验证(需在安全扩展启用 captcha)', dependsOn: { key: 'front_submit_enabled', value: true } },
{ key: 'front_submit_tags', label: '标签输入', type: 'switch', default: true, dependsOn: { key: 'front_submit_enabled', value: true } },
{ key: 'post_article_topics_s', label: '专题选择', type: 'switch', default: false, help: '投稿时可选择专题分类', dependsOn: { key: 'front_submit_enabled', value: true } },
{ key: 'post_article_custom_filter_s', label: '高级筛选选择', type: 'switch', default: true, help: '投稿时可选择自定义筛选维度值', dependsOn: { key: 'front_submit_enabled', value: true } },
diff --git a/assets/css/frontend/pages.css b/assets/css/frontend/pages.css
index c6342ebc..4c26339a 100644
--- a/assets/css/frontend/pages.css
+++ b/assets/css/frontend/pages.css
@@ -3,3 +3,4 @@
@import url("pages/topic-rows.css");
@import url("pages/sidebar.css");
@import url("pages/empty-state.css");
+@import url("pages/submit.css");
diff --git a/assets/css/frontend/pages/submit.css b/assets/css/frontend/pages/submit.css
new file mode 100644
index 00000000..fd816f49
--- /dev/null
+++ b/assets/css/frontend/pages/submit.css
@@ -0,0 +1,487 @@
+/* Article compose / front submit page */
+
+.aksrt-article-compose {
+ padding-top: 24px;
+ padding-bottom: 48px;
+}
+
+.aksrt-article-compose__layout {
+ display: grid;
+ grid-template-columns: 1fr 320px;
+ gap: 20px;
+ align-items: start;
+}
+
+.aksrt-submit-main {
+ min-width: 0;
+}
+
+.aksrt-submit-sidebar {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+}
+
+.aksrt-submit-panel {
+ background: var(--surface-1, #fff);
+ border-radius: var(--main-radius, 8px);
+ box-shadow: var(--shadow-sm, 0 1px 4px rgba(0, 0, 0, 0.06));
+}
+
+.aksrt-submit-panel--editor {
+ padding: 16px 18px 14px;
+ min-height: 60vh;
+}
+
+.aksrt-submit-panel__title {
+ margin: 0;
+ padding: 12px 16px;
+ font-size: 14px;
+ font-weight: 600;
+ border-bottom: 1px solid var(--color-line, #eceff4);
+}
+
+.aksrt-submit-panel__body {
+ padding: 14px 16px;
+}
+
+.aksrt-submit-title-wrap {
+ margin: 8px 0 12px;
+}
+
+.aksrt-submit-title {
+ width: 100%;
+ border: 0;
+ border-bottom: 1px solid var(--color-line, #e5e7eb);
+ font-size: 22px;
+ font-weight: 600;
+ line-height: 1.4;
+ resize: none;
+ outline: none;
+ background: transparent;
+ color: var(--color-ink, #1f2329);
+ padding: 8px 0;
+}
+
+.aksrt-submit-title:focus {
+ border-bottom-color: var(--color-primary, #3b82f6);
+}
+
+.aksrt-submit-editor-wrap .wp-editor-container {
+ border: none;
+}
+
+.aksrt-submit-editor-wrap .mce-toolbar-grp {
+ background: var(--surface-1, #fff);
+ border-bottom: 1px solid var(--color-line, #eceff4);
+}
+
+.aksrt-submit-shortcode-tools {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-top: 10px;
+}
+
+.aksrt-submit-status-row {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px;
+ margin-top: 12px;
+ font-size: 13px;
+}
+
+.aksrt-submit-nocan {
+ min-height: 40vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ color: var(--color-ink-3, #8a909c);
+ text-align: center;
+}
+
+.aksrt-submit-featured {
+ margin-bottom: 16px;
+ border: 1px dashed var(--color-line, #d9dee7);
+ border-radius: 8px;
+ overflow: hidden;
+ background: var(--surface-2, #f7f8fa);
+}
+
+.aksrt-submit-featured__preview {
+ position: relative;
+ aspect-ratio: 2 / 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--surface-2, #f3f4f6);
+}
+
+.aksrt-submit-featured__preview img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.aksrt-submit-featured__empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+ color: var(--color-ink-3, #8a909c);
+ font-size: 13px;
+}
+
+.aksrt-submit-featured__badge {
+ position: absolute;
+ right: 10px;
+ bottom: 10px;
+ background: rgba(0, 0, 0, 0.55);
+ color: #fff;
+ border-radius: 999px;
+ padding: 4px 10px;
+ font-size: 12px;
+}
+
+.aksrt-submit-featured__actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ padding: 10px 12px;
+ border-top: 1px solid var(--color-line, #eceff4);
+ background: #fff;
+}
+
+.aksrt-submit-drop {
+ border: 1px solid var(--color-line, #eceff4);
+ border-radius: 6px;
+ margin-bottom: 8px;
+ overflow: hidden;
+}
+
+.aksrt-submit-drop > summary {
+ list-style: none;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 10px 12px;
+ font-size: 13px;
+}
+
+.aksrt-submit-drop > summary::-webkit-details-marker {
+ display: none;
+}
+
+.aksrt-submit-drop[open] > summary {
+ border-bottom: 1px solid var(--color-line, #eceff4);
+ background: var(--surface-2, #fafbfc);
+}
+
+.aksrt-submit-drop__body {
+ padding: 10px 12px;
+ max-height: 220px;
+ overflow: auto;
+}
+
+.aksrt-submit-check {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 4px 0;
+ font-size: 13px;
+ cursor: pointer;
+ font-weight: 400;
+}
+
+.aksrt-submit-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+}
+
+.aksrt-submit-tag {
+ display: inline-flex;
+ cursor: pointer;
+ font-weight: 400;
+}
+
+.aksrt-submit-tag input {
+ display: none;
+}
+
+.aksrt-submit-tag span {
+ display: inline-block;
+ padding: 4px 10px;
+ border-radius: 999px;
+ background: var(--surface-2, #f3f4f6);
+ font-size: 12px;
+}
+
+.aksrt-submit-tag.is-active span,
+.aksrt-submit-tag:has(input:checked) span {
+ background: #dbeafe;
+ color: #2563eb;
+}
+
+.aksrt-submit-field {
+ display: block;
+ margin-top: 12px;
+}
+
+.aksrt-submit-field__label {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 13px;
+ font-weight: 600;
+ margin-bottom: 6px;
+}
+
+.aksrt-submit-hint {
+ margin: 0 0 8px;
+ font-size: 12px;
+ color: var(--color-ink-3, #8a909c);
+}
+
+.aksrt-form-input {
+ width: 100%;
+ border: 1px solid var(--color-line, #e5e7eb);
+ border-radius: 6px;
+ padding: 8px 10px;
+ font-size: 13px;
+ background: #fff;
+ color: inherit;
+ box-sizing: border-box;
+}
+
+.aksrt-form-input:focus {
+ outline: none;
+ border-color: var(--color-primary, #3b82f6);
+}
+
+.aksrt-submit-pay__row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ padding: 8px 0;
+}
+
+.aksrt-switch {
+ position: relative;
+ display: inline-flex;
+ cursor: pointer;
+}
+
+.aksrt-switch input {
+ position: absolute;
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+
+.aksrt-switch__ui {
+ width: 40px;
+ height: 22px;
+ border-radius: 999px;
+ background: #d1d5db;
+ position: relative;
+ transition: 0.2s;
+}
+
+.aksrt-switch__ui::after {
+ content: "";
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ background: #fff;
+ transition: 0.2s;
+}
+
+.aksrt-switch input:checked + .aksrt-switch__ui {
+ background: var(--color-primary, #3b82f6);
+}
+
+.aksrt-switch input:checked + .aksrt-switch__ui::after {
+ transform: translateX(18px);
+}
+
+.aksrt-submit-segmented {
+ display: inline-flex;
+ gap: 4px;
+}
+
+.aksrt-submit-segmented .but.active {
+ background: var(--color-primary, #3b82f6);
+ color: #fff;
+ border-color: var(--color-primary, #3b82f6);
+}
+
+.aksrt-submit-pay-downloads__item {
+ display: grid;
+ gap: 6px;
+ margin-bottom: 10px;
+ padding-bottom: 10px;
+ border-bottom: 1px dashed var(--color-line, #eceff4);
+}
+
+.aksrt-submit-actions {
+ text-align: center;
+}
+
+.aksrt-submit-ready {
+ color: var(--color-ink-3, #8a909c);
+ letter-spacing: 0.08em;
+ font-size: 12px;
+ margin: 0 0 12px;
+}
+
+.aksrt-submit-actions__btns {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ justify-content: center;
+}
+
+.aksrt-submit-actions__btns .but {
+ flex: 1 1 120px;
+ min-height: 40px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ border-radius: 6px;
+ border: 0;
+ cursor: pointer;
+ font-size: 14px;
+}
+
+.aksrt-submit-actions__btns .jb-green {
+ background: linear-gradient(135deg, #34d399, #10b981);
+ color: #fff;
+}
+
+.aksrt-submit-actions__btns .jb-blue {
+ background: linear-gradient(135deg, #60a5fa, #3b82f6);
+ color: #fff;
+}
+
+.aksrt-submit-actions__btns .but:disabled {
+ opacity: 0.65;
+ cursor: wait;
+}
+
+.aksrt-submit-drafts__item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ padding: 8px 0;
+ border-bottom: 1px solid var(--color-line, #f0f1f3);
+}
+
+.aksrt-submit-drafts__link {
+ flex: 1;
+ min-width: 0;
+ text-decoration: none;
+ color: inherit;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.aksrt-submit-drafts__name {
+ font-size: 13px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.aksrt-submit-drafts__link time {
+ font-size: 11px;
+ color: var(--color-ink-3, #8a909c);
+}
+
+.aksrt-submit-drafts__delete {
+ border: 0;
+ background: transparent;
+ color: #ef4444;
+ cursor: pointer;
+ padding: 6px;
+}
+
+.aksrt-article-compose .but {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ padding: 6px 12px;
+ border-radius: 6px;
+ border: 1px solid var(--color-line, #e5e7eb);
+ background: #fff;
+ color: var(--color-ink-2, #444);
+ font-size: 12px;
+ cursor: pointer;
+ text-decoration: none;
+}
+
+.aksrt-article-compose .but.hollow {
+ background: transparent;
+}
+
+.aksrt-article-compose .but.c-blue {
+ color: #2563eb;
+ border-color: #bfdbfe;
+}
+
+.aksrt-article-compose .but.c-yellow {
+ color: #b45309;
+ border-color: #fde68a;
+}
+
+.aksrt-article-compose .but.c-red {
+ color: #dc2626;
+ border-color: #fecaca;
+}
+
+.aksrt-article-compose .but.cir {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ justify-content: center;
+ padding: 0;
+}
+
+.aksrt-article-compose .badg {
+ display: inline-block;
+ padding: 3px 8px;
+ border-radius: 4px;
+ background: var(--surface-2, #f3f4f6);
+ color: var(--color-ink-3, #8a909c);
+ font-size: 12px;
+}
+
+body.aksrt-hide-float #float-right-btns,
+body.aksrt-hide-tabbar #footer-tabbar,
+body.aksrt-hide-tabbar .footer-tabbar-placeholder {
+ display: none !important;
+}
+
+@media (max-width: 991px) {
+ .aksrt-article-compose__layout {
+ grid-template-columns: 1fr;
+ }
+
+ .aksrt-submit-sidebar {
+ order: 2;
+ }
+
+ .aksrt-submit-main {
+ order: 1;
+ }
+}
diff --git a/assets/js/front-submit.js b/assets/js/front-submit.js
index c974ddf5..637bc9ef 100644
--- a/assets/js/front-submit.js
+++ b/assets/js/front-submit.js
@@ -1,471 +1,618 @@
/**
- * AKSRT Front Submit
- *
- * Handles front-end post submission, draft save/load,
- * editor toolbar, cover preview, and form protection.
+ * AKSRT front-end article submit (write/post page).
+ * Handles TinyMCE content, featured cover, pay box, draft/submit AJAX.
*
* @package AKSRT
*/
-
(function () {
'use strict';
- var i18n = (window.aksrtSubmitData && window.aksrtSubmitData.i18n) || {};
- var ajaxUrl = (window.aksrtSubmitData && window.aksrtSubmitData.ajaxUrl) || '/wp-admin/admin-ajax.php';
- var nonce = (window.aksrtSubmitData && window.aksrtSubmitData.nonce) || '';
- var restUrl = ((window.aksrtSubmitData && window.aksrtSubmitData.restUrl) || '/wp-json/aksrt/v1').replace(/\/$/, '');
- var restNonce = (window.aksrtSubmitData && window.aksrtSubmitData.restNonce) || '';
- var aiCfg = (window.aksrtSubmitData && window.aksrtSubmitData.ai) || {};
+ var cfg = window.aksrtSubmitData || {};
+ var i18n = cfg.i18n || {};
+ var ajaxUrl = cfg.ajaxUrl || '/wp-admin/admin-ajax.php';
+ var nonce = cfg.nonce || '';
- var FRONT_SUBMIT = {
+ function qs(sel, root) {
+ return (root || document).querySelector(sel);
+ }
+
+ function qsa(sel, root) {
+ return Array.prototype.slice.call((root || document).querySelectorAll(sel));
+ }
+
+ function getEditorContent() {
+ if (window.tinymce) {
+ var ed = window.tinymce.get('post_content');
+ if (ed) {
+ return ed.getContent();
+ }
+ }
+ var ta = qs('#post_content') || qs('[name="post_content"]');
+ return ta ? ta.value : '';
+ }
+
+ function insertEditorContent(html) {
+ if (window.tinymce) {
+ var ed = window.tinymce.get('post_content');
+ if (ed) {
+ ed.focus();
+ ed.insertContent(html);
+ return;
+ }
+ }
+ var ta = qs('#post_content') || qs('[name="post_content"]');
+ if (ta) {
+ ta.value += html;
+ }
+ }
+
+ function serializeForm(form) {
+ var data = {};
+ var fd = new FormData(form);
+ fd.forEach(function (value, key) {
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
+ if (!Array.isArray(data[key])) {
+ data[key] = [data[key]];
+ }
+ data[key].push(value);
+ } else {
+ data[key] = value;
+ }
+ });
+ return data;
+ }
+
+ function toFormBody(obj) {
+ var params = new URLSearchParams();
+ function append(key, val) {
+ if (val === undefined || val === null) {
+ return;
+ }
+ if (Array.isArray(val)) {
+ val.forEach(function (v) {
+ append(key, v);
+ });
+ return;
+ }
+ if (typeof val === 'object') {
+ Object.keys(val).forEach(function (k) {
+ append(key + '[' + k + ']', val[k]);
+ });
+ return;
+ }
+ params.append(key, String(val));
+ }
+ Object.keys(obj).forEach(function (k) {
+ append(k, obj[k]);
+ });
+ return params;
+ }
+
+ var App = {
form: null,
isDirty: false,
isSubmitting: false,
+ featured: { type: '', data: {} },
init: function () {
this.form = document.getElementById('aksrt-submit-form');
- if (!this.form) return;
-
- this.bindDirtyTracking();
- this.bindSubmit();
- this.bindDraftSave();
+ if (!this.form) {
+ return;
+ }
+ this.bindDirty();
+ this.bindAutoHeight();
+ this.bindSubmitButtons();
this.bindDraftDelete();
- this.bindEditorToolbar();
- this.bindCoverPreview();
- this.bindWordCount();
+ this.bindFeatured();
+ this.bindPay();
+ this.bindShortcodes();
this.bindLeaveWarning();
- this.bindAiAssist();
+ this.bindAi();
+ this.bindTagActive();
+ this.loadFeaturedFromDom();
},
- /**
- * Track form changes
- */
- bindDirtyTracking: function () {
+ bindDirty: function () {
var self = this;
this.form.addEventListener('input', function () {
self.isDirty = true;
self.form.setAttribute('data-dirty', 'true');
});
+ if (window.tinymce) {
+ var tryBind = function () {
+ var ed = window.tinymce.get('post_content');
+ if (ed) {
+ ed.on('change keyup', function () {
+ self.isDirty = true;
+ });
+ } else {
+ setTimeout(tryBind, 400);
+ }
+ };
+ tryBind();
+ }
},
- /**
- * Leave page warning
- */
bindLeaveWarning: function () {
var self = this;
+ if (cfg.leaveWarning === false) {
+ return;
+ }
window.addEventListener('beforeunload', function (e) {
- if (self.isDirty && !self.isSubmitting) {
+ if (self.isDirty && !self.isSubmitting && !window.post_is_save) {
e.preventDefault();
e.returnValue = i18n.confirmLeave || '';
}
});
},
- /**
- * Form submission
- */
- bindSubmit: function () {
+ bindAutoHeight: function () {
+ qsa('[data-auto-height]').forEach(function (el) {
+ var max = parseInt(el.getAttribute('data-max-height') || '78', 10);
+ var resize = function () {
+ el.style.height = 'auto';
+ el.style.height = Math.min(el.scrollHeight, max) + 'px';
+ };
+ el.addEventListener('input', resize);
+ resize();
+ });
+ },
+
+ bindTagActive: function () {
+ qsa('.aksrt-submit-tag input').forEach(function (input) {
+ var sync = function () {
+ var tag = input.closest('.aksrt-submit-tag');
+ if (tag) {
+ tag.classList.toggle('is-active', input.checked);
+ }
+ };
+ input.addEventListener('change', sync);
+ sync();
+ });
+ },
+
+ bindSubmitButtons: function () {
var self = this;
+ qsa('.aksrt-submit-btn', this.form).forEach(function (btn) {
+ btn.addEventListener('click', function (e) {
+ e.preventDefault();
+ if (self.isSubmitting) {
+ return;
+ }
+ self.submit(btn, btn.getAttribute('data-action') || 'posts_save');
+ });
+ });
this.form.addEventListener('submit', function (e) {
e.preventDefault();
- if (self.isSubmitting) return;
-
- var submitBtn = document.getElementById('aksrt-submit-btn');
- var statusInput = document.getElementById('submit-status');
- statusInput.value = 'pending';
-
- self.submitForm(submitBtn, 'pending');
});
},
- /**
- * Draft save
- */
- bindDraftSave: function () {
- var self = this;
- var draftBtn = document.getElementById('aksrt-save-draft');
- if (!draftBtn) return;
-
- draftBtn.addEventListener('click', function () {
- if (self.isSubmitting) return;
- var statusInput = document.getElementById('submit-status');
- statusInput.value = 'draft';
- self.submitForm(draftBtn, 'draft');
- });
+ collectPayload: function (action) {
+ var data = serializeForm(this.form);
+ data.action = action === 'posts_draft' ? 'aksrt_save_draft' : 'aksrt_submit_post';
+ data._nonce = nonce || data._nonce || '';
+ data.post_content = getEditorContent();
+ data.post_title = data.post_title || ((qs('#submit-title') && qs('#submit-title').value) || '');
+ data.status = action === 'posts_draft' ? 'draft' : 'pending';
+ data.submit_action = action;
+ var featuredJson = qs('[data-featured-json]', this.form);
+ if (featuredJson && featuredJson.value) {
+ try {
+ data.featured_data = JSON.parse(featuredJson.value);
+ } catch (err) {
+ data.featured_data = this.featured;
+ }
+ } else if (this.featured && this.featured.type) {
+ data.featured_data = this.featured;
+ }
+ return data;
},
- /**
- * Submit form via AJAX
- */
- submitForm: function (btn, status) {
+ submit: function (btn, action) {
var self = this;
- var formData = new FormData(this.form);
- formData.append('action', 'aksrt_submit_post');
- formData.append('_nonce', nonce);
-
- var originalText = btn.innerHTML;
+ this.isSubmitting = true;
+ var original = btn.innerHTML;
btn.disabled = true;
- btn.innerHTML = ' ' + (status === 'draft' ? (i18n.saving || '保存中...') : (i18n.submitting || '提交中...'));
- self.isSubmitting = true;
+ btn.innerHTML =
+ ' ' +
+ (action === 'posts_draft' ? i18n.saving || 'Saving...' : i18n.submitting || 'Submitting...');
+
+ var payload = this.collectPayload(action);
+ if (payload.featured_data && typeof payload.featured_data === 'object') {
+ payload.featured_data = JSON.stringify(payload.featured_data);
+ }
fetch(ajaxUrl, {
method: 'POST',
- body: formData,
credentials: 'same-origin',
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
+ body: toFormBody(payload),
})
- .then(function (res) { return res.json(); })
- .then(function (data) {
- if (data.success) {
- self.isDirty = false;
- self.form.setAttribute('data-dirty', 'false');
- btn.innerHTML = ' ' + (data.data && data.data.message ? data.data.message : (status === 'draft' ? (i18n.saved || '草稿已保存') : (i18n.submitted || '投稿已提交')));
- btn.classList.add('is-success');
-
- if (status === 'pending' && data.data && data.data.redirect) {
- setTimeout(function () { window.location.href = data.data.redirect; }, 1500);
- } else if (status === 'draft') {
- // Reload to update draft list
- setTimeout(function () { window.location.reload(); }, 1000);
- }
- } else {
- alert(data.data && data.data.message ? data.data.message : (i18n.error || '操作失败'));
- btn.disabled = false;
- btn.innerHTML = originalText;
+ .then(function (r) {
+ return r.json();
+ })
+ .then(function (res) {
+ var ok = !!(res && res.success);
+ var d = (res && (res.data || res)) || {};
+ if (!ok) {
+ throw new Error((d && d.message) || i18n.error || 'Error');
}
+ self.isDirty = false;
+ window.post_is_save = true;
+ if (d.posts_id || d.post_id) {
+ var hid = self.form.querySelector('input[name="posts_id"]');
+ if (hid) {
+ hid.value = String(d.posts_id || d.post_id);
+ }
+ }
+ if (d.time) {
+ var timeEl = qs('.aksrt-submit-modified-time');
+ if (timeEl) {
+ timeEl.innerHTML =
+ '' + (i18n.lastSaved || 'Last saved: ') + d.time + '';
+ }
+ }
+ if (d.url) {
+ var view = qs('.aksrt-submit-view-btn');
+ if (view) {
+ view.innerHTML =
+ ' ' +
+ (i18n.preview || 'Preview') +
+ '';
+ }
+ }
+ btn.innerHTML =
+ ' ' +
+ (d.message || (action === 'posts_draft' ? i18n.saved : i18n.submitted));
+ if (d.goto) {
+ setTimeout(function () {
+ window.location.href = d.goto;
+ }, 600);
+ return;
+ }
+ if (d.reload) {
+ setTimeout(function () {
+ window.location.reload();
+ }, 600);
+ return;
+ }
+ setTimeout(function () {
+ btn.disabled = false;
+ btn.innerHTML = original;
+ self.isSubmitting = false;
+ }, 1200);
})
- .catch(function () {
- alert(i18n.error || '操作失败,请重试');
+ .catch(function (err) {
+ alert(err.message || i18n.error || 'Error');
btn.disabled = false;
- btn.innerHTML = originalText;
- })
- .finally(function () {
+ btn.innerHTML = original;
self.isSubmitting = false;
});
},
- /**
- * Draft delete
- */
bindDraftDelete: function () {
document.addEventListener('click', function (e) {
var btn = e.target.closest('.aksrt-submit-drafts__delete');
- if (!btn) return;
-
- var draftId = btn.getAttribute('data-draft-id');
- if (!draftId) return;
- if (!confirm(i18n.confirmDelete || '确定要删除这个草稿吗?')) return;
-
- var formData = new FormData();
- formData.append('action', 'aksrt_delete_draft');
- formData.append('_nonce', nonce);
- formData.append('draft_id', draftId);
-
+ if (!btn) {
+ return;
+ }
+ e.preventDefault();
+ var id = btn.getAttribute('data-draft-id');
+ if (!id) {
+ return;
+ }
+ if (!window.confirm(i18n.confirmDelete || 'Delete?')) {
+ return;
+ }
fetch(ajaxUrl, {
method: 'POST',
- body: formData,
credentials: 'same-origin',
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
+ body: toFormBody({ action: 'aksrt_delete_draft', _nonce: nonce, draft_id: id }),
})
- .then(function (res) { return res.json(); })
- .then(function (data) {
- if (data.success) {
- var item = btn.closest('.aksrt-submit-drafts__item');
- if (item) item.remove();
- } else {
- alert(data.data && data.data.message ? data.data.message : '删除失败');
- }
+ .then(function (r) {
+ return r.json();
})
- .catch(function () { alert('删除失败'); });
- });
- },
-
- /**
- * Editor toolbar actions
- */
- bindEditorToolbar: function () {
- var textarea = document.getElementById('submit-content');
- if (!textarea) return;
-
- document.addEventListener('click', function (e) {
- var btn = e.target.closest('[data-action]');
- if (!btn) return;
-
- var action = btn.getAttribute('data-action');
- var form = btn.closest('.aksrt-submit-form');
- if (!form) return;
- var ta = form.querySelector('textarea');
- if (!ta) return;
-
- switch (action) {
- case 'bold':
- FRONT_SUBMIT.wrapSelection(ta, '**');
- break;
- case 'italic':
- FRONT_SUBMIT.wrapSelection(ta, '*');
- break;
- case 'code':
- var sel = ta.value.substring(ta.selectionStart, ta.selectionEnd);
- if (sel.indexOf('\n') >= 0) {
- FRONT_SUBMIT.insertAtCursor(ta, '```\n', '\n```');
+ .then(function (res) {
+ if (res && res.success) {
+ var item = btn.closest('.aksrt-submit-drafts__item');
+ if (item) {
+ item.remove();
+ }
} else {
- FRONT_SUBMIT.wrapSelection(ta, '`');
+ alert((res && res.data && res.data.message) || i18n.error || 'Error');
}
- break;
- case 'link':
- FRONT_SUBMIT.askUrl(btn, '链接地址', function (url) {
- if (url) {
- var linkText = ta.value.substring(ta.selectionStart, ta.selectionEnd) || '链接文字';
- FRONT_SUBMIT.insertAtCursor(ta, '[' + linkText + '](' + url + ')');
- }
- });
- break;
- case 'image':
- FRONT_SUBMIT.askUrl(btn, '图片地址', function (url) {
- if (url) {
- FRONT_SUBMIT.insertAtCursor(ta, '');
- }
- });
- break;
- case 'quote':
- FRONT_SUBMIT.insertAtCursor(ta, '> ', '\n');
- break;
- case 'heading':
- FRONT_SUBMIT.insertAtCursor(ta, '## ', '\n');
- break;
- case 'list':
- FRONT_SUBMIT.insertAtCursor(ta, '- ', '\n');
- break;
- }
- });
- },
-
- askUrl: function (anchor, placeholder, callback) {
- var existing = document.querySelector('.aksrt-inline-url-input');
- if (existing) existing.remove();
-
- var input = document.createElement('input');
- input.type = 'url';
- input.className = 'aksrt-inline-url-input';
- input.placeholder = placeholder || 'https://';
- input.value = 'https://';
- input.style.cssText = 'display:block;width:100%;max-width:320px;margin:4px 0;padding:6px 10px;border:1px solid #dce3ec;border-radius:8px;font-size:13px;';
-
- var parent = anchor.parentElement;
- parent.insertBefore(input, anchor.nextSibling);
- input.focus();
- input.select();
-
- function done() {
- var val = input.value.trim();
- input.remove();
- callback(val);
- }
-
- input.addEventListener('keydown', function (e) {
- if (e.key === 'Enter') { e.preventDefault(); done(); }
- if (e.key === 'Escape') { input.remove(); callback(''); }
- });
- input.addEventListener('blur', function () { done(); });
- },
-
- insertAtCursor: function (textarea, before, after) {
- var start = textarea.selectionStart;
- var end = textarea.selectionEnd;
- var text = textarea.value;
- var selected = text.substring(start, end);
- var replacement = before + (selected || '') + (after || '');
- textarea.value = text.substring(0, start) + replacement + text.substring(end);
- textarea.focus();
- var newPos = start + before.length + selected.length;
- textarea.setSelectionRange(newPos, newPos);
- textarea.dispatchEvent(new Event('input', { bubbles: true }));
- },
-
- wrapSelection: function (textarea, marker) {
- this.insertAtCursor(textarea, marker, marker);
- },
-
- /**
- * Cover image preview
- */
- bindCoverPreview: function () {
- var thumbInput = document.getElementById('submit-thumbnail');
- var previewWrap = document.getElementById('cover-preview');
- var previewImg = document.getElementById('cover-preview-img');
- var removeBtn = document.getElementById('cover-remove');
-
- if (!thumbInput || !previewWrap || !previewImg) return;
-
- thumbInput.addEventListener('change', function () {
- var url = thumbInput.value.trim();
- if (url && /^https?:\/\//.test(url)) {
- previewImg.src = url;
- previewWrap.style.display = 'flex';
- previewImg.onerror = function () { previewWrap.style.display = 'none'; };
- } else {
- previewWrap.style.display = 'none';
- }
- });
-
- if (removeBtn) {
- removeBtn.addEventListener('click', function () {
- thumbInput.value = '';
- previewWrap.style.display = 'none';
- thumbInput.dispatchEvent(new Event('input', { bubbles: true }));
- });
- }
-
- // Media library picker
- var pickBtn = document.getElementById('aksrt-pick-cover');
- if (pickBtn && window.wp && window.wp.media) {
- pickBtn.addEventListener('click', function (e) {
- e.preventDefault();
- var mediaFrame = wp.media({
- title: '选择封面图',
- button: { text: '使用此图片' },
- multiple: false,
- library: { type: 'image' },
});
- mediaFrame.on('select', function () {
- var attachment = mediaFrame.state().get('selection').first().toJSON();
- thumbInput.value = attachment.url;
- previewImg.src = attachment.url;
- previewWrap.style.display = 'flex';
- thumbInput.dispatchEvent(new Event('input', { bubbles: true }));
- });
- mediaFrame.open();
- });
+ });
+ },
+
+ loadFeaturedFromDom: function () {
+ var box = qs('.featured-edit, .aksrt-submit-featured');
+ if (!box) {
+ return;
+ }
+ var raw = box.getAttribute('data-featured-args');
+ if (!raw) {
+ return;
+ }
+ try {
+ var args = JSON.parse(raw);
+ if (args && args.type) {
+ this.featured = { type: args.type, data: args.data || {} };
+ }
+ } catch (e) {}
+ },
+
+ setFeatured: function (type, data) {
+ this.featured = { type: type || '', data: data || {} };
+ var typeInput = qs('[data-featured-type]');
+ var jsonInput = qs('[data-featured-json]');
+ if (typeInput) {
+ typeInput.value = type || '';
+ }
+ if (jsonInput) {
+ jsonInput.value = JSON.stringify(this.featured);
+ }
+ this.renderFeaturedPreview();
+ this.isDirty = true;
+ },
+
+ renderFeaturedPreview: function () {
+ var preview = qs('[data-featured-preview]');
+ if (!preview) {
+ return;
+ }
+ var t = this.featured.type;
+ var d = this.featured.data || {};
+ var html = '';
+ if (t === 'image' && d.url) {
+ html = '
';
+ } else if (t === 'slide' && Array.isArray(d) && d[0] && d[0].url) {
+ html =
+ '
';
+ } else if (t === 'video') {
+ html =
+ (d.pic
+ ? '
'
+ : '
') +
+ '';
+ } else {
+ html = 'Cover
';
+ }
+ preview.innerHTML = html;
+ var clearBtn = qs('[data-featured-action="close"]');
+ if (clearBtn) {
+ clearBtn.hidden = !t || t === 'close';
}
},
- /**
- * Word count
- */
- bindWordCount: function () {
- var contentEl = document.getElementById('submit-content');
- var countEl = document.getElementById('submit-word-count');
- if (!contentEl || !countEl) return;
-
- var updateCount = function () {
- var text = contentEl.value.replace(/<[^>]*>/g, '').trim();
- var chars = text.length;
- var words = text ? text.split(/\s+/).length : 0;
- countEl.textContent = chars + ' 字 · ~' + words + ' 词';
- };
-
- contentEl.addEventListener('input', updateCount);
- updateCount();
+ openMedia: function (opts, cb) {
+ opts = opts || {};
+ if (!window.wp || !wp.media) {
+ alert('Media library unavailable');
+ return;
+ }
+ var frame = wp.media({
+ title: opts.title || i18n.pickImage || 'Select',
+ button: { text: opts.button || 'OK' },
+ multiple: !!opts.multiple,
+ library: opts.library || { type: 'image' },
+ });
+ frame.on('select', function () {
+ var selection = frame.state().get('selection');
+ var files = selection.map(function (att) {
+ var a = att.toJSON();
+ return { id: a.id, url: a.url, title: a.title || '' };
+ });
+ cb(opts.multiple ? files : files[0]);
+ });
+ frame.open();
},
- /**
- * AI 摘要 / 标签辅助(仅建议回填,不自动提交)
- */
- bindAiAssist: function () {
+ bindFeatured: function () {
var self = this;
- if (!aiCfg.available) return;
-
- var buttons = this.form.querySelectorAll('.aksrt-ai-assist-btn');
- if (!buttons.length) return;
-
- buttons.forEach(function (btn) {
- btn.hidden = false;
- btn.addEventListener('click', function (e) {
- e.preventDefault();
- var action = btn.getAttribute('data-ai-action');
- if (action === 'summary') self.runAiSummary(btn);
- if (action === 'tags') self.runAiTags(btn);
- });
- });
- },
-
- getSubmitContent: function () {
- var titleEl = document.getElementById('submit-title');
- var contentEl = document.getElementById('submit-content');
- return {
- title: titleEl ? titleEl.value.trim() : '',
- content: contentEl ? contentEl.value.trim() : '',
- };
- },
-
- runAiSummary: function (btn) {
- var data = this.getSubmitContent();
- if (!data.content) {
- alert(i18n.needContent || '请先填写正文内容');
+ var box = qs('.featured-edit, .aksrt-submit-featured');
+ if (!box) {
return;
}
- this.aiRequest(btn, '/ai/summary-suggest', {
- title: data.title,
- content: data.content,
- max_length: 200,
- }, function (payload) {
- var summary = payload && payload.summary ? String(payload.summary) : '';
- var el = document.getElementById('submit-excerpt');
- if (el && summary) {
- el.value = summary;
- el.dispatchEvent(new Event('input', { bubbles: true }));
+ box.addEventListener('click', function (e) {
+ var btn = e.target.closest('[data-featured-action]');
+ if (!btn) {
+ return;
}
- });
- },
-
- runAiTags: function (btn) {
- var data = this.getSubmitContent();
- if (!data.content && !data.title) {
- alert(i18n.needContent || '请先填写正文内容');
- return;
- }
- this.aiRequest(btn, '/ai/tags-suggest', {
- title: data.title,
- content: data.content,
- }, function (payload) {
- var tags = payload && Array.isArray(payload.tags) ? payload.tags : [];
- var el = document.getElementById('submit-tags');
- if (el && tags.length) {
- el.value = tags.join(', ');
- el.dispatchEvent(new Event('input', { bubbles: true }));
+ e.preventDefault();
+ var act = btn.getAttribute('data-featured-action');
+ if (act === 'close') {
+ self.setFeatured('close', {});
+ return;
}
- });
- },
-
- aiRequest: function (btn, path, body, onSuccess) {
- if (btn.getAttribute('data-loading') === '1') return;
- var original = btn.textContent;
- btn.setAttribute('data-loading', '1');
- btn.textContent = i18n.aiGenerating || '生成中…';
-
- fetch(restUrl + path, {
- method: 'POST',
- credentials: 'same-origin',
- headers: {
- 'Content-Type': 'application/json',
- 'X-WP-Nonce': restNonce,
- },
- body: JSON.stringify(body || {}),
- })
- .then(function (r) {
- return r.json().then(function (json) {
- return { ok: r.ok, json: json };
+ if (act === 'image') {
+ self.openMedia({ title: i18n.pickImage, multiple: false }, function (file) {
+ if (file) {
+ self.setFeatured('image', { url: file.url, id: file.id });
+ }
});
- })
- .then(function (res) {
- btn.removeAttribute('data-loading');
- btn.textContent = original;
- if (!res.ok || !res.json || res.json.success === false) {
- var msg = (res.json && res.json.message) || i18n.aiFailed || 'AI 生成失败';
- alert(msg);
+ return;
+ }
+ if (act === 'slide') {
+ self.openMedia({ title: i18n.pickImage, multiple: true }, function (files) {
+ if (files && files.length) {
+ self.setFeatured(
+ 'slide',
+ files.map(function (f) {
+ return { url: f.url, id: f.id };
+ })
+ );
+ }
+ });
+ return;
+ }
+ if (act === 'video') {
+ var url = window.prompt(i18n.videoUrl || 'Video URL');
+ if (!url) {
return;
}
- onSuccess(res.json.data || {});
- })
- .catch(function () {
- btn.removeAttribute('data-loading');
- btn.textContent = original;
- alert(i18n.aiFailed || 'AI 生成失败');
+ self.openMedia({ title: i18n.pickVideo || 'Poster', multiple: false }, function (file) {
+ self.setFeatured('video', { url: url, pic: file ? file.url : '' });
+ });
+ }
+ });
+ },
+
+ bindPay: function () {
+ var box = qs('[data-pay-box]');
+ if (!box) {
+ return;
+ }
+ var toggle = qs('[data-pay-toggle]', box);
+ var fields = qs('[data-pay-fields]', box);
+ var offHint = qs('[data-pay-off-hint]', box);
+ var modoInput = qs('[data-pay-modo-input]', box);
+ var cash = qs('[data-pay-cash]', box);
+ var points = qs('[data-pay-points]', box);
+
+ function syncToggle() {
+ var on = toggle && toggle.checked;
+ if (fields) {
+ fields.hidden = !on;
+ }
+ if (offHint) {
+ offHint.hidden = !!on;
+ }
+ }
+ if (toggle) {
+ toggle.addEventListener('change', syncToggle);
+ }
+ syncToggle();
+
+ qsa('[data-pay-modo]', box).forEach(function (btn) {
+ btn.addEventListener('click', function () {
+ var v = btn.getAttribute('data-pay-modo') || '0';
+ if (modoInput) {
+ modoInput.value = v;
+ }
+ qsa('[data-pay-modo]', box).forEach(function (b) {
+ b.classList.toggle('active', b === btn);
+ });
+ if (cash) {
+ cash.hidden = v === 'points';
+ }
+ if (points) {
+ points.hidden = v !== 'points';
+ }
});
+ });
+
+ var list = qs('[data-pay-download-list]', box);
+ var tpl = qs('[data-pay-download-tpl]', box);
+ var addBtn = qs('[data-pay-download-add]', box);
+ if (addBtn && list && tpl) {
+ addBtn.addEventListener('click', function () {
+ var node = tpl.content.cloneNode(true);
+ var idx = list.querySelectorAll('[data-pay-download-item]').length;
+ qsa('[data-name]', node).forEach(function (input) {
+ input.name = 'aksrt_pay[downloads][' + idx + '][' + input.getAttribute('data-name') + ']';
+ });
+ list.appendChild(node);
+ });
+ }
+ if (list) {
+ list.addEventListener('click', function (e) {
+ var rm = e.target.closest('[data-pay-download-remove]');
+ if (!rm) {
+ return;
+ }
+ var item = rm.closest('[data-pay-download-item]');
+ if (item) {
+ item.remove();
+ }
+ });
+ }
+ },
+
+ bindShortcodes: function () {
+ document.addEventListener('click', function (e) {
+ var btn = e.target.closest('[data-insert-shortcode]');
+ if (!btn) {
+ return;
+ }
+ e.preventDefault();
+ var type = btn.getAttribute('data-insert-shortcode');
+ if (type === 'hide') {
+ insertEditorContent('[hidecontent type="logged"]hidden[/hidecontent]');
+ } else if (type === 'payshow') {
+ insertEditorContent('[hidecontent type="payshow"]paid content[/hidecontent]');
+ }
+ });
+ },
+
+ bindAi: function () {
+ var ai = cfg.ai || {};
+ if (!ai.available) {
+ return;
+ }
+ qsa('.aksrt-ai-assist-btn').forEach(function (btn) {
+ btn.hidden = false;
+ btn.addEventListener('click', function () {
+ var action = btn.getAttribute('data-ai-action');
+ var content = getEditorContent();
+ if (!content || content.replace(/<[^>]+>/g, '').trim().length < 10) {
+ alert(i18n.needContent || 'Need content');
+ return;
+ }
+ var old = btn.textContent;
+ btn.disabled = true;
+ btn.textContent = i18n.aiGenerating || '...';
+ fetch((cfg.restUrl || '').replace(/\/$/, '') + '/ai/' + action, {
+ method: 'POST',
+ credentials: 'same-origin',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-WP-Nonce': cfg.restNonce || '',
+ },
+ body: JSON.stringify({ content: content }),
+ })
+ .then(function (r) {
+ return r.json();
+ })
+ .then(function (res) {
+ var text = (res && (res.data || res).text) || (res && res.result) || '';
+ if (!text) {
+ throw new Error(i18n.aiFailed || 'AI failed');
+ }
+ if (action === 'summary') {
+ var ex = qs('[name="excerpt"]');
+ if (ex) {
+ ex.value = text;
+ }
+ } else if (action === 'tags') {
+ var tags = qs('[name="tags"]');
+ if (tags) {
+ tags.value = text;
+ }
+ }
+ })
+ .catch(function (err) {
+ alert(err.message || i18n.aiFailed || 'AI failed');
+ })
+ .finally(function () {
+ btn.disabled = false;
+ btn.textContent = old;
+ });
+ });
+ });
},
};
if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', function () { FRONT_SUBMIT.init(); });
+ document.addEventListener('DOMContentLoaded', function () {
+ App.init();
+ });
} else {
- FRONT_SUBMIT.init();
+ App.init();
}
})();
diff --git a/footer.php b/footer.php
index 5343b7a5..c04f81fc 100644
--- a/footer.php
+++ b/footer.php
@@ -326,10 +326,11 @@ if ($_footer_conter === '') {
-
+
@@ -462,7 +465,7 @@ if (!empty($aksrt_bg_styles)) {
-
+
diff --git a/inc/helpers/aksrt-single-render.php b/inc/helpers/aksrt-single-render.php
index 0718302b..7af2618e 100644
--- a/inc/helpers/aksrt-single-render.php
+++ b/inc/helpers/aksrt-single-render.php
@@ -436,9 +436,11 @@ if (!function_exists('aksrt_single_render_author_card')) :
0) {
+ $url = add_query_arg('edit', $edit_id, $url);
+ }
+ return $url;
+}
+endif;
+
if (!function_exists('aksrt_get_orders_page_url')) :
/**
* 订单中心页面 URL(模板 templates/pages/page-orders.php)。
diff --git a/src/AKSRT/Admin/Schema/SchemaPostList.php b/src/AKSRT/Admin/Schema/SchemaPostList.php
index e0fa09b9..c9fe0cda 100644
--- a/src/AKSRT/Admin/Schema/SchemaPostList.php
+++ b/src/AKSRT/Admin/Schema/SchemaPostList.php
@@ -343,7 +343,9 @@ class SchemaPostList {
['key' => 'front_submit_cover', 'label' => '封面图', 'type' => 'switch', 'default' => true],
['key' => 'front_submit_category', 'label' => '分类选择', 'type' => 'switch', 'default' => true],
['key' => 'post_article_cat', 'label' => '允许投稿分类', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => [], 'help' => '为空则允许全部分类'],
+ ['key' => 'front_submit_guest_enabled', 'label' => '允许免登录投稿', 'type' => 'switch', 'default' => false, 'help' => '开启后访客可投稿;文章归属到下方用户'],
['key' => 'post_article_user', 'label' => '免登录归属用户', 'type' => 'user_select', 'default' => 0, 'help' => '免登录投稿时文章归属的用户,搜索选择'],
+ ['key' => 'front_submit_captcha', 'label' => '提交人机验证', 'type' => 'switch', 'default' => false, 'help' => '提交正式投稿时校验人机验证'],
['key' => 'front_submit_tags', 'label' => '标签输入', 'type' => 'switch', 'default' => true],
['key' => 'post_article_topics_s', 'label' => '专题选择', 'type' => 'switch', 'default' => false, 'help' => '投稿时可选择专题分类'],
['key' => 'post_article_custom_filter_s', 'label' => '高级筛选选择', 'type' => 'switch', 'default' => true, 'help' => '投稿时可选择自定义筛选维度值'],
diff --git a/src/AKSRT/Core/FrontSubmit.php b/src/AKSRT/Core/FrontSubmit.php
index 6bb4b64b..1a561bba 100644
--- a/src/AKSRT/Core/FrontSubmit.php
+++ b/src/AKSRT/Core/FrontSubmit.php
@@ -7,12 +7,22 @@ namespace AKSRT\Core;
defined('ABSPATH') || exit;
+/**
+ * Front-end article submit (写文章 / 投稿).
+ *
+ * AJAX authority for create / draft / update / delete; capability-gated.
+ */
class FrontSubmit {
+ use FrontSubmitSave;
+ use FrontSubmitPay;
+ use FrontSubmitFeatured;
private static $instance = null;
public static function get_instance(): self {
- if (null === self::$instance) self::$instance = new self();
+ if (null === self::$instance) {
+ self::$instance = new self();
+ }
return self::$instance;
}
@@ -21,368 +31,336 @@ class FrontSubmit {
add_action('wp_ajax_aksrt_save_draft', [$this, 'ajax_save_draft']);
add_action('wp_ajax_aksrt_update_post', [$this, 'ajax_update_post']);
add_action('wp_ajax_aksrt_delete_draft', [$this, 'ajax_delete_draft']);
+ add_action('wp_ajax_aksrt_delete_post', [$this, 'ajax_delete_post']);
+
+ // Guest path (when enabled).
+ add_action('wp_ajax_nopriv_aksrt_submit_post', [$this, 'ajax_submit_post']);
+
+ add_filter('body_class', [$this, 'body_class']);
+ add_filter('wp_robots', [$this, 'robots_noindex']);
+ add_filter('aksrt_floating_buttons_enabled', [$this, 'disable_chrome_on_submit']);
+ add_filter('aksrt_footer_tabbar_enabled', [$this, 'disable_chrome_on_submit']);
}
/**
- * 检查用户是否可以投稿
- *
- * @param int|null $user_id User ID (null = current user).
- * @return bool
+ * @return array
+ */
+ public function settings(): array {
+ if (class_exists('\AKSRT\Settings\Settings')) {
+ return \AKSRT\Settings\Settings::get_submit_settings();
+ }
+ return [
+ 'enabled' => true,
+ 'title_min' => 5,
+ 'title_max' => 30,
+ 'allowed_cat_ids' => [],
+ 'default_user_id' => 0,
+ 'guest_enabled' => false,
+ 'allow_category' => true,
+ 'allow_tags' => true,
+ 'allow_cover' => true,
+ 'topics_s' => false,
+ 'custom_filter_s' => true,
+ 'review_mode' => 'pending',
+ 'pay_free_s' => true,
+ 'pay_price_min' => 0,
+ 'pay_price_max' => 100,
+ 'pay_vip_price_s' => true,
+ 'pay_vip_1_discount' => 80,
+ 'pay_vip_2_discount' => 50,
+ ];
+ }
+
+ /**
+ * Whether the user (or guest) may open / create a submission.
*/
public function can_submit(?int $user_id = null): bool {
- if ($user_id === null) {
- $user_id = get_current_user_id();
- }
- if ($user_id <= 0) return false;
-
- // Read submission settings
- $submit_settings = [];
- if (class_exists('\AKSRT\Settings\Settings')) {
- $submit_settings = \AKSRT\Settings\Settings::get_value('post_list.submit', []);
- }
-
- // Check if front submission is enabled (fix: was checking !empty && empty which is always false)
- if (isset($submit_settings['submit_enabled']) && !$submit_settings['submit_enabled']) {
+ $settings = $this->settings();
+ if (empty($settings['enabled'])) {
return false;
}
- // Role whitelist: if configured, user must have one of the allowed roles
- $allowed_roles = $submit_settings['submit_roles'] ?? [];
- if (!empty($allowed_roles)) {
- $user = get_userdata($user_id);
- if (!$user) return false;
- $user_roles = $user->roles;
- if (empty(array_intersect($user_roles, (array) $allowed_roles))) {
- // Also allow administrators
- if (!in_array('administrator', $user_roles, true)) {
- return false;
- }
- }
+ if ($user_id === null) {
+ $user_id = get_current_user_id();
}
- // Credits threshold: if configured, user must have enough credits
- $min_credits = (int) ($submit_settings['submit_min_credits'] ?? 0);
- if ($min_credits > 0 && class_exists('\AKSRT\Credits\Credits')) {
- $balance = \AKSRT\Credits\Credits::balance($user_id);
- if ($balance < $min_credits) {
- return false;
- }
- }
-
- // Default: allow users with edit_posts capability (or any logged-in user if submission is enabled)
- return user_can($user_id, 'edit_posts') || !empty($submit_settings['submit_enabled']);
- }
-
- /**
- * 提交文章
- *
- * @param array $data 投稿数据
- * @return array{success: bool, post_id?: int, message: string}
- */
- public function submit(array $data): array {
- $user_id = get_current_user_id();
- if (!$this->can_submit($user_id)) {
- return ['success' => false, 'message' => __('没有投稿权限', 'aksrt')];
- }
-
- $title = sanitize_text_field((string) ($data['title'] ?? ''));
- $content = wp_kses_post((string) ($data['content'] ?? ''));
- $category = (int) ($data['category'] ?? 0);
- $tags = sanitize_text_field((string) ($data['tags'] ?? ''));
- $status = sanitize_key((string) ($data['status'] ?? 'pending'));
-
- if ($title === '') {
- return ['success' => false, 'message' => __('标题不能为空', 'aksrt')];
- }
-
- $valid_statuses = ['pending', 'draft', 'publish'];
- if (!in_array($status, $valid_statuses, true)) {
- $status = 'pending';
- }
-
- // Determine review policy: if review is not required and user can publish, auto-publish
- if ($status === 'pending') {
- $submit_settings = [];
- if (class_exists('\AKSRT\Settings\Settings')) {
- $submit_settings = \AKSRT\Settings\Settings::get_value('post_list.submit', []);
- }
- $review_required = $submit_settings['review_required'] ?? true;
- $skip_moderation = false;
- if (class_exists('\AKSRT\Settings\Settings')) {
- $all = \AKSRT\Settings\Settings::get_all();
- $skip_moderation = !empty($all['skip_submit_moderation']);
- }
-
- if (!$review_required || $skip_moderation) {
- // Auto-publish if user has publish_posts capability
- if (user_can($user_id, 'publish_posts')) {
- $status = 'publish';
- }
- }
- }
-
- $post_data = [
- 'post_title' => $title,
- 'post_content' => $content,
- 'post_status' => $status,
- 'post_author' => $user_id,
- 'post_type' => 'post',
- ];
-
- $post_id = wp_insert_post($post_data, true);
-
- if (is_wp_error($post_id)) {
- return ['success' => false, 'message' => $post_id->get_error_message()];
- }
-
- // 设置分类
- if ($category > 0) {
- wp_set_post_categories($post_id, [$category]);
- }
-
- // 设置标签
- if ($tags !== '') {
- $tag_names = array_map('trim', explode(',', $tags));
- wp_set_post_tags($post_id, $tag_names);
- }
-
- // 设置封面图
- $thumbnail_url = sanitize_url((string) ($data['thumbnail'] ?? ''));
- if ($thumbnail_url !== '') {
- $this->set_thumbnail_from_url($post_id, $thumbnail_url);
- }
-
- // 设置专题 (topics taxonomy)
- $this->apply_topics_and_filters($post_id, $data);
-
- return [
- 'success' => true,
- 'post_id' => $post_id,
- 'message' => $status === 'draft' ? __('草稿已保存', 'aksrt') : __('投稿已提交,等待审核', 'aksrt'),
- ];
- }
-
- /**
- * 更新文章
- */
- public function update(int $post_id, array $data): array {
- $user_id = get_current_user_id();
if ($user_id <= 0) {
- return ['success' => false, 'message' => __('请先登录', 'aksrt')];
+ return !empty($settings['guest_enabled']) && (int) ($settings['default_user_id'] ?? 0) > 0;
+ }
+
+ return $this->user_can_cap('new_post_add', $user_id);
+ }
+
+ public function can_edit_post(int $post_id, ?int $user_id = null): bool {
+ if ($post_id <= 0) {
+ return false;
+ }
+ if ($user_id === null) {
+ $user_id = get_current_user_id();
+ }
+ if ($user_id <= 0) {
+ return false;
+ }
+ if (user_can($user_id, 'manage_options') || user_can($user_id, 'edit_others_posts')) {
+ return true;
}
$post = get_post($post_id);
- if (!$post || (int) $post->post_author !== $user_id) {
- return ['success' => false, 'message' => __('没有编辑权限', 'aksrt')];
+ if (!$post || $post->post_type !== 'post') {
+ return false;
+ }
+ if ((int) $post->post_author !== $user_id) {
+ return false;
}
- $update_data = ['ID' => $post_id];
-
- if (isset($data['title'])) {
- $update_data['post_title'] = sanitize_text_field($data['title']);
- }
- if (isset($data['content'])) {
- $update_data['post_content'] = wp_kses_post($data['content']);
- }
- if (isset($data['status'])) {
- $update_data['post_status'] = sanitize_key($data['status']);
+ // Own drafts always editable when user can add posts.
+ if ($post->post_status === 'draft' && $this->user_can_cap('new_post_add', $user_id)) {
+ return true;
}
- $result = wp_update_post($update_data, true);
+ return $this->user_can_cap('new_post_edit', $user_id, $post_id);
+ }
- if (is_wp_error($result)) {
- return ['success' => false, 'message' => $result->get_error_message()];
+ public function can_delete_post(int $post_id, ?int $user_id = null): bool {
+ if ($user_id === null) {
+ $user_id = get_current_user_id();
}
-
- // Update category
- $category = (int) ($data['category'] ?? 0);
- if ($category > 0) {
- wp_set_post_categories($post_id, [$category]);
+ if ($user_id <= 0) {
+ return false;
}
-
- // Update tags
- if (isset($data['tags'])) {
- $tags = sanitize_text_field((string) $data['tags']);
- if ($tags !== '') {
- $tag_names = array_map('trim', explode(',', $tags));
- wp_set_post_tags($post_id, $tag_names);
- } else {
- wp_set_post_tags($post_id, []);
- }
+ if (user_can($user_id, 'manage_options')) {
+ return true;
}
-
- // Update thumbnail
- if (isset($data['thumbnail'])) {
- $thumbnail_url = sanitize_url((string) $data['thumbnail']);
- if ($thumbnail_url !== '') {
- $this->set_thumbnail_from_url($post_id, $thumbnail_url);
- }
+ $post = get_post($post_id);
+ if (!$post || $post->post_type !== 'post') {
+ return false;
}
+ if ((int) $post->post_author !== $user_id) {
+ return false;
+ }
+ if ($post->post_status === 'draft' && $this->user_can_cap('new_post_add', $user_id)) {
+ return true;
+ }
+ return $this->user_can_cap('new_post_delete', $user_id, $post_id);
+ }
- // Update topics + custom filter meta
- $this->apply_topics_and_filters($post_id, $data);
-
- return ['success' => true, 'post_id' => $post_id, 'message' => __('已更新', 'aksrt')];
+ public function user_can_cap(string $cap, int $user_id = 0, ?int $context = null): bool {
+ if ($user_id > 0 && user_can($user_id, 'manage_options')) {
+ return true;
+ }
+ if (function_exists('aksrt_user_can')) {
+ return aksrt_user_can($cap, $user_id, $context);
+ }
+ if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'user_can')) {
+ return \AKSRT\Settings\Settings::user_can($cap, $user_id, $context);
+ }
+ return $user_id > 0;
}
/**
- * 从 URL 设置封面图
- *
- * Downloads the image from the given URL, sideloads it into the media library,
- * and sets it as the post's featured image.
- *
- * @param int $post_id Post ID.
- * @param string $url Image URL.
+ * @param array $data
+ * @return array{success:bool,message:string,post_id?:int}
+ */
+ public function submit(array $data): array {
+ $action = sanitize_key((string) ($data['action_type'] ?? $data['submit_action'] ?? 'posts_save'));
+ if ($action === 'aksrt_save_draft' || ($data['status'] ?? '') === 'draft') {
+ $action = 'posts_draft';
+ } else {
+ $action = 'posts_save';
+ }
+ return $this->persist($data, $action);
+ }
+
+ /**
+ * @param array $data
+ * @return array{success:bool,message:string,post_id?:int}
+ */
+ public function update(int $post_id, array $data): array {
+ $data['posts_id'] = $post_id;
+ $action = (($data['status'] ?? '') === 'draft') ? 'posts_draft' : 'posts_save';
+ return $this->persist($data, $action);
+ }
+
+ public function ajax_submit_post(): void {
+ $this->verify_nonce();
+ $this->maybe_verify_captcha();
+
+ $data = wp_unslash($_POST);
+ $result = $this->persist(is_array($data) ? $data : [], 'posts_save');
+ $this->send_result($result);
+ }
+
+ public function ajax_save_draft(): void {
+ $this->verify_nonce();
+ if (!is_user_logged_in()) {
+ wp_send_json_error(['message' => __('请先登录', 'aksrt')]);
+ }
+ $data = wp_unslash($_POST);
+ $result = $this->persist(is_array($data) ? $data : [], 'posts_draft');
+ $this->send_result($result);
+ }
+
+ public function ajax_update_post(): void {
+ $this->verify_nonce();
+ $data = wp_unslash($_POST);
+ if (!is_array($data)) {
+ $data = [];
+ }
+ $post_id = absint($data['posts_id'] ?? $data['post_id'] ?? $data['edit_id'] ?? 0);
+ if ($post_id <= 0) {
+ wp_send_json_error(['message' => __('文章 ID 无效', 'aksrt')]);
+ }
+ $data['posts_id'] = $post_id;
+ $action = (($data['status'] ?? '') === 'draft') ? 'posts_draft' : 'posts_save';
+ $result = $this->persist($data, $action);
+ $this->send_result($result);
+ }
+
+ public function ajax_delete_draft(): void {
+ $this->verify_nonce();
+ $user_id = get_current_user_id();
+ if ($user_id <= 0) {
+ wp_send_json_error(['message' => __('请先登录', 'aksrt')]);
+ }
+ $post_id = absint($_POST['draft_id'] ?? $_POST['post_id'] ?? $_POST['id'] ?? 0);
+ if ($post_id <= 0) {
+ wp_send_json_error(['message' => __('草稿 ID 无效', 'aksrt')]);
+ }
+ $post = get_post($post_id);
+ if (!$post || (int) $post->post_author !== $user_id) {
+ wp_send_json_error(['message' => __('没有删除权限', 'aksrt')]);
+ }
+ if ($post->post_status !== 'draft' && !$this->can_delete_post($post_id, $user_id)) {
+ wp_send_json_error(['message' => __('只能删除草稿', 'aksrt')]);
+ }
+ if ($post->post_status === 'draft') {
+ $ok = wp_delete_post($post_id, true);
+ } else {
+ $ok = wp_trash_post($post_id);
+ }
+ if ($ok) {
+ wp_send_json_success(['message' => __('已删除', 'aksrt')]);
+ }
+ wp_send_json_error(['message' => __('删除失败', 'aksrt')]);
+ }
+
+ public function ajax_delete_post(): void {
+ $this->verify_nonce();
+ $user_id = get_current_user_id();
+ $post_id = absint($_POST['id'] ?? $_POST['post_id'] ?? 0);
+ if (!$this->can_delete_post($post_id, $user_id)) {
+ wp_send_json_error(['message' => __('您没有删除此内容的权限', 'aksrt')]);
+ }
+ $post = get_post($post_id);
+ if (!$post || $post->post_status === 'trash') {
+ wp_send_json_success(['message' => __('文章已经删除', 'aksrt'), 'reload' => true]);
+ }
+ wp_trash_post($post_id);
+ wp_send_json_success([
+ 'message' => __('文章已经删除', 'aksrt'),
+ 'reload' => true,
+ 'goto' => home_url('/'),
+ ]);
+ }
+
+ private function verify_nonce(): void {
+ $nonce = (string) ($_REQUEST['_nonce'] ?? $_REQUEST['nonce'] ?? $_REQUEST['_wpnonce'] ?? '');
+ if ($nonce === '' || (
+ !wp_verify_nonce($nonce, 'aksrt_submit')
+ && !wp_verify_nonce($nonce, 'aksrt_submit_nonce')
+ )) {
+ wp_send_json_error(['message' => __('安全验证失败,请刷新页面重试', 'aksrt')], 403);
+ }
+ }
+
+ private function maybe_verify_captcha(): void {
+ $settings = $this->settings();
+ if (empty($settings['captcha'])) {
+ return;
+ }
+ if (!function_exists('aksrt_require_captcha')) {
+ return;
+ }
+ $payload = [];
+ if (isset($_POST['captcha']) && is_array($_POST['captcha'])) {
+ $payload = wp_unslash($_POST['captcha']);
+ }
+ $verify = aksrt_require_captcha('submit', is_array($payload) ? $payload : []);
+ if (is_wp_error($verify)) {
+ wp_send_json_error(['message' => $verify->get_error_message()]);
+ }
+ }
+
+ /**
+ * @param array{success:bool,message?:string} $result
+ */
+ private function send_result(array $result): void {
+ if (!empty($result['success'])) {
+ wp_send_json_success($result);
+ }
+ wp_send_json_error($result);
+ }
+
+ /**
+ * Downloads image URL into media library as featured image.
*/
private function set_thumbnail_from_url(int $post_id, string $url): void {
if ($url === '' || !filter_var($url, FILTER_VALIDATE_URL)) {
return;
}
-
- // Require media handling functions
if (!function_exists('media_sideload_image')) {
require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/image.php';
}
-
- // Check if URL is an image
- $ext = strtolower(pathinfo(parse_url($url, PHP_URL_PATH) ?? '', PATHINFO_EXTENSION));
- $allowed_exts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'];
- if ($ext !== '' && !in_array($ext, $allowed_exts, true)) {
+ $ext = strtolower(pathinfo((string) (parse_url($url, PHP_URL_PATH) ?? ''), PATHINFO_EXTENSION));
+ $allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
+ if ($ext !== '' && !in_array($ext, $allowed, true)) {
return;
}
-
- // Sideload the image
$attachment_id = media_sideload_image($url, $post_id, '', 'id');
-
if (!is_wp_error($attachment_id)) {
- set_post_thumbnail($post_id, $attachment_id);
- } elseif (defined('WP_DEBUG') && WP_DEBUG) {
- error_log(sprintf('[AKSRT Front Submit] Failed to set thumbnail for post %d: %s — %s', $post_id, $url, $attachment_id->get_error_message()));
+ set_post_thumbnail($post_id, (int) $attachment_id);
}
}
/**
- * AJAX 提交投稿
+ * @param string[] $classes
+ * @return string[]
*/
- public function ajax_submit_post(): void {
- check_ajax_referer('aksrt_submit', '_nonce');
-
- $data = $_POST;
- $result = $this->submit($data);
-
- if ($result['success']) {
- wp_send_json_success($result);
+ public function body_class(array $classes): array {
+ if ($this->is_submit_template()) {
+ $classes[] = 'aksrt-page-submit';
+ $classes[] = 'aksrt-hide-float';
+ $classes[] = 'aksrt-hide-tabbar';
}
-
- wp_send_json_error($result);
+ return $classes;
}
/**
- * AJAX 保存草稿
+ * @param array $robots
+ * @return array
*/
- public function ajax_save_draft(): void {
- check_ajax_referer('aksrt_submit', '_nonce');
-
- $_POST['status'] = 'draft';
- $data = $_POST;
- $result = $this->submit($data);
-
- if ($result['success']) {
- wp_send_json_success($result);
+ public function robots_noindex(array $robots): array {
+ if ($this->is_submit_template()) {
+ $robots['noindex'] = true;
+ $robots['nofollow'] = true;
}
-
- wp_send_json_error($result);
+ return $robots;
}
/**
- * AJAX 更新文章
+ * @param mixed $enabled
+ * @return mixed
*/
- public function ajax_update_post(): void {
- check_ajax_referer('aksrt_submit', '_nonce');
-
- $post_id = (int) ($_POST['post_id'] ?? 0);
- if ($post_id <= 0) {
- wp_send_json_error(['message' => __('文章 ID 无效', 'aksrt')]);
+ public function disable_chrome_on_submit($enabled) {
+ if ($this->is_submit_template()) {
+ return false;
}
-
- $result = $this->update($post_id, $_POST);
-
- if ($result['success']) {
- wp_send_json_success($result);
- }
-
- wp_send_json_error($result);
+ return $enabled;
}
- /**
- * AJAX handler: delete draft
- */
- public function ajax_delete_draft(): void {
- check_ajax_referer('aksrt_submit', '_nonce');
-
- $user_id = get_current_user_id();
- if ($user_id <= 0) {
- wp_send_json_error(['message' => __('请先登录', 'aksrt')]);
- }
-
- // Fix: JS sends 'draft_id', not 'post_id'
- $post_id = (int) (($_POST['draft_id'] ?? $_POST['post_id']) ?? 0);
- if ($post_id <= 0) {
- wp_send_json_error(['message' => __('草稿 ID 无效', 'aksrt')]);
- }
-
- $post = get_post($post_id);
- if (!$post || (int) $post->post_author !== $user_id) {
- wp_send_json_error(['message' => __('没有删除权限', 'aksrt')]);
- }
-
- if ($post->post_status !== 'draft') {
- wp_send_json_error(['message' => __('只能删除草稿', 'aksrt')]);
- }
-
- $result = wp_delete_post($post_id, true);
- if ($result) {
- wp_send_json_success(['message' => __('草稿已删除', 'aksrt')]);
- }
-
- wp_send_json_error(['message' => __('删除失败', 'aksrt')]);
- }
-
- /**
- * Apply topics taxonomy terms and custom filter meta values to a post.
- *
- * Reads 'topics' array and 'cf_{dim_key}' scalar values from $data.
- * Custom filter keys are validated against the configured dimension definitions.
- *
- * @param int $post_id Post ID.
- * @param array $data Submission data ($_POST).
- */
- private function apply_topics_and_filters(int $post_id, array $data): void {
- // Topics taxonomy
- if (isset($data['topics']) && is_array($data['topics']) && taxonomy_exists('topics')) {
- $topic_ids = array_map('absint', $data['topics']);
- $topic_ids = array_filter($topic_ids);
- wp_set_object_terms($post_id, array_values($topic_ids), 'topics');
- }
-
- // Custom filter meta values (_aksrt_cf_{dim_key})
- foreach ($data as $key => $val) {
- if (strpos($key, 'cf_') !== 0) {
- continue;
- }
- $dim_key = sanitize_key(substr($key, 3));
- if ($dim_key === '') {
- continue;
- }
- $cf_val = sanitize_key((string) $val);
- $meta_key = '_aksrt_cf_' . $dim_key;
- if ($cf_val !== '') {
- update_post_meta($post_id, $meta_key, $cf_val);
- } else {
- delete_post_meta($post_id, $meta_key);
- }
- }
+ public function is_submit_template(): bool {
+ return is_page_template('templates/pages/page-submit.php');
}
}
diff --git a/src/AKSRT/Core/FrontSubmitFeatured.php b/src/AKSRT/Core/FrontSubmitFeatured.php
new file mode 100644
index 00000000..d76a6838
--- /dev/null
+++ b/src/AKSRT/Core/FrontSubmitFeatured.php
@@ -0,0 +1,157 @@
+ $data
+ */
+ private function save_featured_meta(int $post_id, array $data, int $user_id): void {
+ if (empty($data['featured_data']) && empty($data['featured_type'])) {
+ return;
+ }
+
+ $featured = $data['featured_data'] ?? null;
+ if (is_string($featured)) {
+ $decoded = json_decode(wp_unslash($featured), true);
+ $featured = is_array($decoded) ? $decoded : null;
+ }
+ if (!is_array($featured)) {
+ // Flat payload: featured_type + fields.
+ $featured = [
+ 'type' => sanitize_key((string) ($data['featured_type'] ?? '')),
+ 'data' => $data['featured_payload'] ?? $data,
+ ];
+ }
+
+ $type = sanitize_key((string) ($featured['type'] ?? ''));
+ $payload = is_array($featured['data'] ?? null) ? $featured['data'] : $featured;
+
+ $post_meta = get_post_meta($post_id, DomainModel::POST_META_KEY, true);
+ if (!is_array($post_meta)) {
+ $post_meta = DomainModel::default_post_meta();
+ }
+
+ switch ($type) {
+ case 'video':
+ if (!$this->user_can_cap('new_post_video_cover', $user_id) && !$this->user_can_cap('new_post_image_cover', $user_id)) {
+ return;
+ }
+ $video = esc_url_raw((string) ($payload['url'] ?? $payload['video'] ?? ''));
+ $pic = esc_url_raw((string) ($payload['pic'] ?? $payload['poster'] ?? $payload['cover'] ?? ''));
+ $post_meta['featured_video'] = $video;
+ $post_meta['featured_slide'] = '';
+ if ($pic !== '') {
+ $post_meta['thumbnail_url'] = $pic;
+ $post_meta['cover_images'] = $pic;
+ $this->maybe_set_thumbnail_url($post_id, $pic);
+ }
+ break;
+
+ case 'slide':
+ if (!$this->user_can_cap('new_post_slide_cover', $user_id) && !$this->user_can_cap('new_post_image_cover', $user_id)) {
+ return;
+ }
+ $ids = $payload['ids'] ?? $payload['slides'] ?? $payload['images'] ?? [];
+ $urls = [];
+ if (is_string($ids)) {
+ $ids = array_filter(array_map('trim', explode(',', $ids)));
+ }
+ if (is_array($ids)) {
+ foreach ($ids as $item) {
+ if (is_array($item)) {
+ $u = esc_url_raw((string) ($item['url'] ?? ''));
+ if ($u !== '') {
+ $urls[] = $u;
+ } elseif (!empty($item['id'])) {
+ $src = wp_get_attachment_image_url((int) $item['id'], 'full');
+ if ($src) {
+ $urls[] = $src;
+ }
+ }
+ } elseif (is_numeric($item)) {
+ $src = wp_get_attachment_image_url((int) $item, 'full');
+ if ($src) {
+ $urls[] = $src;
+ }
+ } else {
+ $u = esc_url_raw((string) $item);
+ if ($u !== '') {
+ $urls[] = $u;
+ }
+ }
+ }
+ }
+ $post_meta['featured_slide'] = implode(',', $urls);
+ $post_meta['featured_video'] = '';
+ if (!empty($urls[0])) {
+ $post_meta['thumbnail_url'] = $urls[0];
+ $post_meta['cover_images'] = $urls[0];
+ $this->maybe_set_thumbnail_url($post_id, $urls[0]);
+ }
+ break;
+
+ case 'image':
+ if (!$this->user_can_cap('new_post_image_cover', $user_id)) {
+ return;
+ }
+ $url = esc_url_raw((string) ($payload['url'] ?? $payload['image'] ?? ''));
+ $id = absint($payload['id'] ?? 0);
+ if ($url === '' && $id > 0) {
+ $src = wp_get_attachment_image_url($id, 'full');
+ if ($src) {
+ $url = $src;
+ }
+ }
+ $post_meta['featured_video'] = '';
+ $post_meta['featured_slide'] = '';
+ if ($url !== '') {
+ $post_meta['thumbnail_url'] = $url;
+ $post_meta['cover_images'] = $url;
+ if ($id > 0) {
+ set_post_thumbnail($post_id, $id);
+ } else {
+ $this->maybe_set_thumbnail_url($post_id, $url);
+ }
+ }
+ break;
+
+ case 'close':
+ case 'none':
+ case 'clear':
+ $post_meta['featured_video'] = '';
+ $post_meta['featured_slide'] = '';
+ $post_meta['thumbnail_url'] = '';
+ $post_meta['cover_images'] = '';
+ delete_post_thumbnail($post_id);
+ break;
+
+ default:
+ return;
+ }
+
+ update_post_meta($post_id, DomainModel::POST_META_KEY, $post_meta);
+ }
+
+ private function maybe_set_thumbnail_url(int $post_id, string $url): void {
+ if ($url === '') {
+ return;
+ }
+ // Prefer attachment if URL is local media.
+ $attach_id = attachment_url_to_postid($url);
+ if ($attach_id > 0) {
+ set_post_thumbnail($post_id, $attach_id);
+ return;
+ }
+ $this->set_thumbnail_from_url($post_id, $url);
+ }
+}
diff --git a/src/AKSRT/Core/FrontSubmitPay.php b/src/AKSRT/Core/FrontSubmitPay.php
new file mode 100644
index 00000000..9028df98
--- /dev/null
+++ b/src/AKSRT/Core/FrontSubmitPay.php
@@ -0,0 +1,197 @@
+ $data
+ * @param array $settings
+ * @return array{success:bool,message?:string}
+ */
+ private function save_pay_meta(int $post_id, array $data, int $user_id, array $settings): array {
+ if (!$this->user_can_cap('new_post_pay', $user_id)) {
+ return ['success' => true];
+ }
+
+ // Only touch pay meta when the form sent pay fields.
+ $has_pay_payload = isset($data['aksrt_pay']) || isset($data['pay_enabled']) || isset($data['zibpay_s']) || isset($data['pay_type']);
+ if (!$has_pay_payload) {
+ return ['success' => true];
+ }
+
+ $raw = is_array($data['aksrt_pay'] ?? null) ? $data['aksrt_pay'] : [];
+ $enabled = !empty($data['pay_enabled']) || !empty($data['zibpay_s']);
+
+ $existing = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($post_id) : DomainModel::default_pay_meta();
+ $meta = is_array($existing) ? $existing : DomainModel::default_pay_meta();
+
+ if (!$enabled) {
+ $meta['pay_type'] = DomainModel::PAY_TYPE_NO;
+ $this->write_pay_meta($post_id, $meta);
+ return ['success' => true];
+ }
+
+ $pay_modo = sanitize_key((string) ($raw['pay_modo'] ?? $data['pay_modo'] ?? '0'));
+ $pay_modo = $pay_modo === 'points' ? 'points' : '0';
+
+ $pay_type = sanitize_key((string) ($raw['pay_type'] ?? $data['pay_type'] ?? DomainModel::PAY_TYPE_READING));
+ if ($pay_type === '1' || $pay_type === 'reading' || $pay_type === 'payshow') {
+ $pay_type = DomainModel::PAY_TYPE_READING;
+ } elseif ($pay_type === '2' || $pay_type === 'download') {
+ $pay_type = DomainModel::PAY_TYPE_DOWNLOAD;
+ } else {
+ $pay_type = DomainModel::normalize_pay_type($pay_type);
+ if ($pay_type === DomainModel::PAY_TYPE_NO) {
+ $pay_type = DomainModel::PAY_TYPE_READING;
+ }
+ }
+
+ $downloads = $this->normalize_front_downloads($raw['downloads'] ?? $data['pay_download'] ?? []);
+ if (!empty($downloads) && $this->user_can_cap('new_post_pay_download', $user_id)) {
+ $pay_type = DomainModel::PAY_TYPE_DOWNLOAD;
+ $meta['downloads'] = $downloads;
+ } elseif ($pay_type === DomainModel::PAY_TYPE_DOWNLOAD && !$this->user_can_cap('new_post_pay_download', $user_id)) {
+ $pay_type = DomainModel::PAY_TYPE_READING;
+ $meta['downloads'] = [];
+ }
+
+ $meta['pay_type'] = $pay_type;
+ $meta['pay_modo'] = $pay_modo;
+
+ $min = (float) ($settings['pay_price_min'] ?? 0);
+ $max = (float) ($settings['pay_price_max'] ?? 0);
+ $allow_free = !empty($settings['pay_free_s']);
+ $vip_manual = !empty($settings['pay_vip_price_s']);
+ $vip1_disc = (int) ($settings['pay_vip_1_discount'] ?? 100);
+ $vip2_disc = (int) ($settings['pay_vip_2_discount'] ?? 100);
+
+ if ($pay_modo === 'points') {
+ $points = isset($raw['points_price']) ? (int) $raw['points_price'] : (int) ($data['points_price'] ?? 0);
+ if ($points < 0) {
+ return ['success' => false, 'message' => __('请设置正确的积分价格', 'aksrt')];
+ }
+ if (!$allow_free && $points <= 0) {
+ return ['success' => false, 'message' => __('请设置付费内容的积分价格', 'aksrt')];
+ }
+ if ($min > 0 && $points < $min) {
+ return ['success' => false, 'message' => sprintf(__('积分价格不能低于:%s', 'aksrt'), (string) $min)];
+ }
+ if ($max > 0 && $points > $max) {
+ return ['success' => false, 'message' => sprintf(__('积分价格不能高于:%s', 'aksrt'), (string) $max)];
+ }
+
+ if ($vip_manual) {
+ $vip1 = isset($raw['vip1_points']) ? (int) $raw['vip1_points'] : (int) ($raw['vip_1_points'] ?? 0);
+ $vip2 = isset($raw['vip2_points']) ? (int) $raw['vip2_points'] : (int) ($raw['vip_2_points'] ?? 0);
+ } else {
+ $vip1 = (int) floor($points * $vip1_disc / 100);
+ $vip2 = (int) floor($points * $vip2_disc / 100);
+ }
+ $vip1 = max(0, min($points, $vip1));
+ $vip2 = max(0, min($vip1, $vip2));
+
+ $meta['points_price'] = $points;
+ $meta['price'] = 0.0;
+ $meta['vip1_points'] = $vip1;
+ $meta['vip2_points'] = $vip2;
+ $meta['vip1_price'] = 0.0;
+ $meta['vip2_price'] = 0.0;
+ } else {
+ $price = isset($raw['price']) ? (float) $raw['price'] : (float) ($raw['pay_price'] ?? $data['pay_price'] ?? 0);
+ $price = round($price, 2);
+ if ($price < 0) {
+ return ['success' => false, 'message' => __('请设置正确的价格', 'aksrt')];
+ }
+ if (!$allow_free && $price <= 0) {
+ return ['success' => false, 'message' => __('请设置付费内容的价格', 'aksrt')];
+ }
+ if ($min > 0 && $price < $min) {
+ return ['success' => false, 'message' => sprintf(__('价格不能低于:%s', 'aksrt'), (string) $min)];
+ }
+ if ($max > 0 && $price > $max) {
+ return ['success' => false, 'message' => sprintf(__('价格不能高于:%s', 'aksrt'), (string) $max)];
+ }
+
+ if ($vip_manual) {
+ $vip1 = isset($raw['vip1_price']) ? (float) $raw['vip1_price'] : (float) ($raw['vip_1_price'] ?? 0);
+ $vip2 = isset($raw['vip2_price']) ? (float) $raw['vip2_price'] : (float) ($raw['vip_2_price'] ?? 0);
+ $vip1 = round($vip1, 2);
+ $vip2 = round($vip2, 2);
+ if ($vip1 > $price) {
+ return ['success' => false, 'message' => __('会员价不能高于普通价', 'aksrt')];
+ }
+ if ($vip2 > $vip1) {
+ return ['success' => false, 'message' => __('更高等级会员价不能高于较低等级会员价', 'aksrt')];
+ }
+ } else {
+ $vip1 = round($price * $vip1_disc / 100, 2);
+ $vip2 = round($price * $vip2_disc / 100, 2);
+ }
+ $vip1 = max(0, min($price, $vip1));
+ $vip2 = max(0, min($vip1, $vip2));
+
+ $meta['price'] = $price;
+ $meta['points_price'] = 0;
+ $meta['vip1_price'] = $vip1;
+ $meta['vip2_price'] = $vip2;
+ $meta['vip1_points'] = 0;
+ $meta['vip2_points'] = 0;
+ }
+
+ $this->write_pay_meta($post_id, $meta);
+ return ['success' => true];
+ }
+
+ /**
+ * @param mixed $raw
+ * @return array>
+ */
+ private function normalize_front_downloads($raw): array {
+ if (!is_array($raw)) {
+ return [];
+ }
+ $out = [];
+ foreach ($raw as $row) {
+ if (!is_array($row)) {
+ continue;
+ }
+ // Accept zibll-ish keys + AKSRT keys.
+ $url = (string) ($row['url'] ?? $row['link'] ?? '');
+ $url = esc_url_raw(trim($url));
+ if ($url === '') {
+ continue;
+ }
+ $out[] = [
+ 'label' => sanitize_text_field((string) ($row['label'] ?? $row['name'] ?? '')),
+ 'url' => $url,
+ 'type' => 'external',
+ 'extract_code' => sanitize_text_field((string) ($row['extract_code'] ?? $row['copy_val'] ?? $row['more'] ?? '')),
+ ];
+ }
+ return DomainModel::sanitize_downloads($out);
+ }
+
+ /**
+ * @param array $meta
+ */
+ private function write_pay_meta(int $post_id, array $meta): void {
+ $meta = DomainModel::sanitize_pay_meta($meta);
+ update_post_meta($post_id, DomainModel::PAY_META_KEY, $meta);
+ update_post_meta($post_id, DomainModel::PAY_TYPE_KEY, (string) ($meta['pay_type'] ?? DomainModel::PAY_TYPE_NO));
+ update_post_meta($post_id, DomainModel::PAY_PRICE_KEY, (float) ($meta['price'] ?? 0));
+ update_post_meta(
+ $post_id,
+ DomainModel::RESOURCE_MARKER_KEY,
+ DomainModel::is_resource_pay_type((string) ($meta['pay_type'] ?? 'no')) ? 1 : 0
+ );
+ }
+}
diff --git a/src/AKSRT/Core/FrontSubmitSave.php b/src/AKSRT/Core/FrontSubmitSave.php
new file mode 100644
index 00000000..8eb72f69
--- /dev/null
+++ b/src/AKSRT/Core/FrontSubmitSave.php
@@ -0,0 +1,354 @@
+ $data Request payload.
+ * @param string $action posts_save|posts_draft
+ * @return array{success:bool,message:string,post_id?:int,url?:string,goto?:string,reload?:bool,time?:string,posts_id?:int}
+ */
+ public function persist(array $data, string $action = 'posts_save'): array {
+ $user_id = get_current_user_id();
+ $settings = $this->settings();
+ $posts_id = absint($data['posts_id'] ?? $data['edit_id'] ?? $data['post_id'] ?? 0);
+ $is_guest = $user_id <= 0;
+ $is_edit = $posts_id > 0;
+ $is_draft = $action === 'posts_draft';
+
+ if (empty($settings['enabled'])) {
+ return ['success' => false, 'message' => __('投稿功能已关闭', 'aksrt')];
+ }
+
+ if ($is_guest) {
+ if (empty($settings['guest_enabled']) || (int) $settings['default_user_id'] <= 0) {
+ return ['success' => false, 'message' => __('请先登录', 'aksrt')];
+ }
+ if ($is_draft) {
+ return ['success' => false, 'message' => __('未登录无法保存草稿', 'aksrt')];
+ }
+ if ($is_edit) {
+ return ['success' => false, 'message' => __('未登录无法编辑文章', 'aksrt')];
+ }
+ }
+
+ if ($is_edit) {
+ if (!$this->can_edit_post($posts_id, $user_id)) {
+ return ['success' => false, 'message' => __('没有编辑权限', 'aksrt')];
+ }
+ } elseif (!$this->can_submit($is_guest ? 0 : $user_id)) {
+ return ['success' => false, 'message' => __('没有投稿权限', 'aksrt')];
+ }
+
+ $title = sanitize_text_field((string) ($data['post_title'] ?? $data['title'] ?? ''));
+ $content = (string) ($data['post_content'] ?? $data['content'] ?? '');
+ $excerpt = sanitize_textarea_field((string) ($data['excerpt'] ?? $data['post_excerpt'] ?? ''));
+
+ if ($title === '') {
+ return ['success' => false, 'message' => __('请填写文章标题', 'aksrt')];
+ }
+ if (trim(wp_strip_all_tags($content)) === '') {
+ return ['success' => false, 'message' => __('还未填写任何内容', 'aksrt')];
+ }
+
+ $categories = $this->normalize_category_ids($data);
+ $tags_raw = (string) ($data['tags'] ?? '');
+
+ if (!$is_draft) {
+ $title_len = $this->text_length($title);
+ if ($title_len < (int) $settings['title_min']) {
+ return ['success' => false, 'message' => __('标题太短!', 'aksrt')];
+ }
+ if ($title_len > (int) $settings['title_max']) {
+ return [
+ 'success' => false,
+ 'message' => sprintf(
+ /* translators: %d: max title length */
+ __('标题太长了,不能超过%d个字', 'aksrt'),
+ (int) $settings['title_max']
+ ),
+ ];
+ }
+ if ($this->text_length(wp_strip_all_tags($content)) < 10) {
+ return ['success' => false, 'message' => __('文章内容过少', 'aksrt')];
+ }
+ if (!empty($settings['allow_category']) && empty($categories)) {
+ return ['success' => false, 'message' => __('请选择文章分类', 'aksrt')];
+ }
+ if (!empty($settings['cover_required']) && empty($data['featured_data']) && empty($data['thumbnail'])) {
+ return ['success' => false, 'message' => __('请设置封面图', 'aksrt')];
+ }
+ }
+
+ // Allowed category whitelist.
+ $allowed = $settings['allowed_cat_ids'] ?? [];
+ if (!empty($allowed) && !empty($categories)) {
+ $categories = array_values(array_intersect($categories, $allowed));
+ if (!$is_draft && empty($categories) && !empty($settings['allow_category'])) {
+ return ['success' => false, 'message' => __('所选分类不可用', 'aksrt')];
+ }
+ }
+
+ $author_id = $user_id;
+ if ($is_guest) {
+ $guest_name = sanitize_text_field((string) ($data['user_name'] ?? ''));
+ if ($guest_name === '') {
+ return ['success' => false, 'message' => __('请输入昵称', 'aksrt')];
+ }
+ $contact = sanitize_text_field((string) ($data['contact_details'] ?? ''));
+ $suffix = $contact !== ''
+ ? sprintf(__('[投稿-姓名:%1$s,联系:%2$s]', 'aksrt'), $guest_name, $contact)
+ : sprintf(__('[投稿-姓名:%s]', 'aksrt'), $guest_name);
+ $title = $title . $suffix;
+ $author_id = (int) $settings['default_user_id'];
+ }
+
+ $status = 'draft';
+ if (!$is_draft) {
+ $status = $this->resolve_publish_status($user_id, $settings);
+ }
+
+ $can_iframe = $this->user_can_cap('new_post_iframe_video', $user_id);
+ if ($can_iframe) {
+ add_filter('wp_kses_allowed_html', [$this, 'allow_iframe_kses'], 99, 2);
+ }
+ $content = wp_kses_post($content);
+ if ($can_iframe) {
+ remove_filter('wp_kses_allowed_html', [$this, 'allow_iframe_kses'], 99);
+ }
+
+ $postarr = [
+ 'post_type' => 'post',
+ 'post_title' => $title,
+ 'post_content' => $content,
+ 'post_status' => $status,
+ 'comment_status' => 'open',
+ ];
+ if ($excerpt !== '' || array_key_exists('excerpt', $data) || array_key_exists('post_excerpt', $data)) {
+ $postarr['post_excerpt'] = $excerpt;
+ }
+ if (!empty($settings['allow_tags']) && $tags_raw !== '') {
+ $tag_names = preg_split('/[,,\n]+/u', $tags_raw) ?: [];
+ $tag_names = array_values(array_filter(array_map('trim', $tag_names)));
+ $postarr['tags_input'] = $tag_names;
+ }
+ if (!empty($categories)) {
+ $postarr['post_category'] = $categories;
+ }
+
+ if ($is_edit) {
+ $postarr['ID'] = $posts_id;
+ $in_id = wp_update_post($postarr, true);
+ } else {
+ $postarr['post_author'] = $author_id;
+ $in_id = wp_insert_post($postarr, true);
+ }
+
+ if (is_wp_error($in_id)) {
+ return ['success' => false, 'message' => $in_id->get_error_message()];
+ }
+ if (!$in_id) {
+ return ['success' => false, 'message' => __('文章保存失败,请稍后再试', 'aksrt')];
+ }
+
+ // Re-apply categories when empty update should clear (logged-in only).
+ if (!empty($settings['allow_category']) && isset($data['category'])) {
+ wp_set_post_categories((int) $in_id, $categories);
+ }
+ if (!empty($settings['allow_tags']) && array_key_exists('tags', $data) && $tags_raw === '') {
+ wp_set_post_tags((int) $in_id, []);
+ }
+
+ $this->apply_topics_and_filters((int) $in_id, $data, $settings);
+
+ $pay_result = $this->save_pay_meta((int) $in_id, $data, $user_id, $settings);
+ if (!$pay_result['success']) {
+ $pay_result['post_id'] = (int) $in_id;
+ $pay_result['posts_id'] = (int) $in_id;
+ return $pay_result;
+ }
+
+ $this->save_featured_meta((int) $in_id, $data, $user_id);
+
+ // Legacy thumbnail URL fallback.
+ $thumbnail_url = esc_url_raw((string) ($data['thumbnail'] ?? ''));
+ if ($thumbnail_url !== '' && empty($data['featured_data'])) {
+ $this->set_thumbnail_from_url((int) $in_id, $thumbnail_url);
+ }
+
+ $post = get_post((int) $in_id);
+ if ($post instanceof \WP_Post) {
+ do_action($is_edit ? 'aksrt_new_edit_posts' : 'aksrt_new_add_posts', $post);
+ if ($status === 'pending') {
+ do_action('aksrt_new_posts_pending', $post);
+ }
+ }
+
+ $url = get_permalink((int) $in_id) ?: '';
+ $send = [
+ 'success' => true,
+ 'post_id' => (int) $in_id,
+ 'posts_id' => (int) $in_id,
+ 'url' => $url,
+ ];
+
+ switch ($status) {
+ case 'pending':
+ $send['message'] = __('内容已提交,正在等待审核', 'aksrt');
+ if ($user_id > 0 && function_exists('aksrt_get_user_home_url')) {
+ $send['reload'] = true;
+ $send['goto'] = add_query_arg(
+ ['tab' => 'post', 'post_status' => 'pending'],
+ aksrt_get_user_home_url($user_id)
+ );
+ }
+ break;
+ case 'draft':
+ $send['message'] = __('草稿已保存', 'aksrt');
+ $send['time'] = current_time('mysql');
+ break;
+ default:
+ $send['message'] = __('文章已发布', 'aksrt');
+ $send['reload'] = true;
+ $send['goto'] = $url;
+ break;
+ }
+
+ return $send;
+ }
+
+ /**
+ * @param array $settings
+ */
+ private function resolve_publish_status(int $user_id, array $settings): string {
+ $mode = (string) ($settings['review_mode'] ?? 'pending');
+ if ($mode === 'draft') {
+ return 'draft';
+ }
+ if ($mode === 'publish') {
+ return 'publish';
+ }
+ // pending mode: skip audit when capability grants it.
+ if ($this->user_can_cap('new_post_audit_no', $user_id)) {
+ return 'publish';
+ }
+ return 'pending';
+ }
+
+ /**
+ * @param array $data
+ * @return int[]
+ */
+ private function normalize_category_ids(array $data): array {
+ $raw = $data['category'] ?? $data['categories'] ?? [];
+ if (!is_array($raw)) {
+ $raw = $raw === '' || $raw === null ? [] : [$raw];
+ }
+ $ids = array_values(array_unique(array_filter(array_map('absint', $raw))));
+ return $ids;
+ }
+
+ private function text_length(string $text): int {
+ $text = trim(wp_strip_all_tags($text));
+ if (function_exists('mb_strlen')) {
+ return (int) mb_strlen($text);
+ }
+ return strlen($text);
+ }
+
+ /**
+ * @param array $data
+ * @param array $settings
+ */
+ private function apply_topics_and_filters(int $post_id, array $data, array $settings): void {
+ if (!empty($settings['topics_s']) && taxonomy_exists('topics')) {
+ $topics = $data['topics'] ?? [];
+ if (!is_array($topics)) {
+ $topics = $topics === '' || $topics === null ? [] : [$topics];
+ }
+ $topic_ids = array_values(array_filter(array_map('absint', $topics)));
+ wp_set_object_terms($post_id, $topic_ids, 'topics');
+ }
+
+ if (empty($settings['custom_filter_s'])) {
+ return;
+ }
+
+ $dims = [];
+ if (class_exists('\AKSRT\Settings\Settings')) {
+ $filter_all = \AKSRT\Settings\Settings::get_post_filter_settings();
+ $dims = is_array($filter_all['custom_filter'] ?? null) ? $filter_all['custom_filter'] : [];
+ }
+
+ $allowed_keys = [];
+ foreach ($dims as $dim) {
+ if (!empty($dim['key'])) {
+ $allowed_keys[] = sanitize_key((string) $dim['key']);
+ }
+ }
+
+ foreach ($data as $key => $val) {
+ $dim_key = '';
+ if (strpos((string) $key, 'cf_') === 0) {
+ $dim_key = sanitize_key(substr((string) $key, 3));
+ } elseif (strpos((string) $key, '_aksrt_cf_') === 0) {
+ $dim_key = sanitize_key(substr((string) $key, 10));
+ } elseif (in_array(sanitize_key((string) $key), $allowed_keys, true)) {
+ $dim_key = sanitize_key((string) $key);
+ }
+ if ($dim_key === '' || (!empty($allowed_keys) && !in_array($dim_key, $allowed_keys, true))) {
+ continue;
+ }
+ $meta_key = '_aksrt_cf_' . $dim_key;
+ if (is_array($val)) {
+ $vals = array_values(array_filter(array_map('sanitize_key', $val)));
+ if ($vals) {
+ update_post_meta($post_id, $meta_key, $vals);
+ } else {
+ delete_post_meta($post_id, $meta_key);
+ }
+ } else {
+ $cf_val = sanitize_key((string) $val);
+ if ($cf_val !== '') {
+ update_post_meta($post_id, $meta_key, $cf_val);
+ } else {
+ delete_post_meta($post_id, $meta_key);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param array $tags
+ * @return array
+ */
+ public function allow_iframe_kses(array $tags, $context = ''): array {
+ if ($context !== 'post' && $context !== '') {
+ return $tags;
+ }
+ $tags['iframe'] = [
+ 'src' => true,
+ 'width' => true,
+ 'height' => true,
+ 'frameborder' => true,
+ 'allowfullscreen' => true,
+ 'allow' => true,
+ 'style' => true,
+ 'class' => true,
+ 'id' => true,
+ 'scrolling' => true,
+ ];
+ return $tags;
+ }
+}
diff --git a/src/AKSRT/Core/Templates.php b/src/AKSRT/Core/Templates.php
index 3e68d44d..f4e1f2bf 100644
--- a/src/AKSRT/Core/Templates.php
+++ b/src/AKSRT/Core/Templates.php
@@ -42,6 +42,8 @@ class Templates {
'templates/pages/page-archives.php' => __('AKSRT - 文章归档', 'aksrt'),
'templates/pages/page-categories.php' => __('AKSRT - 分类汇总', 'aksrt'),
'templates/pages/page-newposts.php' => __('AKSRT - 最新文章', 'aksrt'),
+ // 注意:page-newposts = 最新文章列表;投稿页是 page-submit(写文章/投稿)
+ 'templates/pages/page-submit.php' => __('AKSRT - 写文章/投稿', 'aksrt'),
'templates/pages/page-posts-navs.php' => __('AKSRT - 文章导航', 'aksrt'),
'templates/pages/page-friends.php' => __('AKSRT - 友情链接', 'aksrt'),
'templates/pages/page-orders.php' => __('AKSRT - 订单中心', 'aksrt'),
diff --git a/src/AKSRT/Settings/PostDisplay.php b/src/AKSRT/Settings/PostDisplay.php
index aa80638d..c0bb6662 100644
--- a/src/AKSRT/Settings/PostDisplay.php
+++ b/src/AKSRT/Settings/PostDisplay.php
@@ -378,18 +378,49 @@ class PostDisplay {
public static function get_submit_settings(): array {
$settings = Settings::get_all();
+ $title_min = max(1, min(100, (int) ($settings['post_article_title_strlen_limit_min'] ?? 5)));
+ $title_max = max(5, min(200, (int) ($settings['post_article_title_strlen_limit_max'] ?? 30)));
+ if ($title_max < $title_min) {
+ $title_max = $title_min;
+ }
+
+ $review_mode = sanitize_key((string) ($settings['front_submit_review_mode'] ?? 'pending'));
+ if (!in_array($review_mode, ['pending', 'publish', 'draft'], true)) {
+ $review_mode = 'pending';
+ }
+
+ $editor_mode = sanitize_key((string) ($settings['front_submit_editor_mode'] ?? 'rich'));
+ if (!in_array($editor_mode, ['rich', 'markdown', 'plain'], true)) {
+ $editor_mode = 'rich';
+ }
+
return [
'enabled' => !isset($settings['front_submit_enabled']) || !empty($settings['front_submit_enabled']),
'btn_text' => sanitize_text_field((string) ($settings['post_article_btn_txte'] ?? '')) ?: __('发布文章', 'aksrt'),
- 'title_min' => max(1, min(100, (int) ($settings['post_article_title_strlen_limit_min'] ?? 5))),
- 'title_max' => max(5, min(200, (int) ($settings['post_article_title_strlen_limit_max'] ?? 30))),
+ 'title_min' => $title_min,
+ 'title_max' => $title_max,
'title_placeholder' => sanitize_text_field((string) ($settings['post_t_placeholder'] ?? '')) ?: __('请输入标题', 'aksrt'),
'content_placeholder' => sanitize_textarea_field((string) ($settings['post_c_placeholder'] ?? '')) ?: __('请输入内容', 'aksrt'),
- 'allowed_cat_ids' => is_array($settings['post_article_cat'] ?? null) ? array_map('intval', $settings['post_article_cat']) : [],
+ 'allowed_cat_ids' => is_array($settings['post_article_cat'] ?? null) ? array_values(array_filter(array_map('intval', $settings['post_article_cat']))) : [],
'default_user_id' => max(0, (int) ($settings['post_article_user'] ?? 0)),
+ 'guest_enabled' => !empty($settings['front_submit_guest_enabled']),
+ 'allow_title' => !isset($settings['front_submit_title']) || !empty($settings['front_submit_title']),
+ 'allow_content' => !isset($settings['front_submit_content']) || !empty($settings['front_submit_content']),
+ 'allow_excerpt' => !isset($settings['front_submit_excerpt']) || !empty($settings['front_submit_excerpt']),
+ 'allow_cover' => !isset($settings['front_submit_cover']) || !empty($settings['front_submit_cover']),
+ 'allow_category' => !isset($settings['front_submit_category']) || !empty($settings['front_submit_category']),
+ 'allow_tags' => !isset($settings['front_submit_tags']) || !empty($settings['front_submit_tags']),
+ 'tags_s' => !isset($settings['front_submit_tags']) || !empty($settings['front_submit_tags']),
+ 'cover_required' => !empty($settings['front_submit_cover_required']),
+ 'max_images' => max(0, min(50, (int) ($settings['front_submit_max_images'] ?? 5))),
+ 'review_mode' => $review_mode,
+ 'editor_mode' => $editor_mode,
+ 'draft_save' => !isset($settings['front_submit_draft_save']) || !empty($settings['front_submit_draft_save']),
+ 'leave_warning' => !isset($settings['front_submit_leave_warning']) || !empty($settings['front_submit_leave_warning']),
+ 'captcha' => !empty($settings['front_submit_captcha']) || !empty($settings['verification_newposts_s']),
'pay_free_s' => !isset($settings['post_article_pay_free_s']) || !empty($settings['post_article_pay_free_s']),
- 'pay_price_min' => max(0, (int) ($settings['post_article_pay_price_min'] ?? 0)),
- 'pay_price_max' => max(0, (int) ($settings['post_article_pay_price_max'] ?? 100)),
+ 'pay_price_min' => max(0, (float) ($settings['post_article_pay_price_min'] ?? 0)),
+ 'pay_price_max' => max(0, (float) ($settings['post_article_pay_price_max'] ?? 100)),
'pay_vip_price_s' => !isset($settings['post_article_pay_vip_price_s']) || !empty($settings['post_article_pay_vip_price_s']),
'pay_vip_1_discount' => max(0, min(100, (int) ($settings['post_article_pay_vip_1_discount'] ?? 80))),
'pay_vip_2_discount' => max(0, min(100, (int) ($settings['post_article_pay_vip_2_discount'] ?? 50))),
diff --git a/templates/pages/page-submit.php b/templates/pages/page-submit.php
index 2c8d34b7..d4c96c3b 100644
--- a/templates/pages/page-submit.php
+++ b/templates/pages/page-submit.php
@@ -1,310 +1,138 @@
set_404();
+ status_header(404);
+ nocache_headers();
+ include get_query_template('404');
+ return;
+}
+
+// Guest without guest mode → login.
+if (!empty($ctx['is_guest']) && empty($ctx['settings']['guest_enabled'])) {
+ $login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url(get_permalink()) : wp_login_url(get_permalink());
+ wp_safe_redirect($login);
exit;
}
-$user_id = get_current_user_id();
-$can_submit = class_exists('\AKSRT\Core\FrontSubmit') && \AKSRT\Core\FrontSubmit::get_instance()->can_submit($user_id);
-
-if (!$can_submit) {
- wp_redirect(home_url('/'));
+// Invalid edit id → strip.
+if (!empty($_GET['edit']) && empty($ctx['edit_post'])) {
+ wp_safe_redirect(remove_query_arg('edit'));
exit;
}
-// Check if editing existing draft
-$edit_id = isset($_GET['edit']) ? absint($_GET['edit']) : 0;
-$edit_post = null;
-if ($edit_id > 0) {
- $edit_post = get_post($edit_id);
- if (!$edit_post || (int) $edit_post->post_author !== $user_id || $edit_post->post_status !== 'draft') {
- $edit_post = null;
- $edit_id = 0;
- }
+// Assets.
+$css = get_template_directory() . '/assets/css/frontend/pages/submit.css';
+if (file_exists($css)) {
+ wp_enqueue_style(
+ 'aksrt-submit',
+ get_template_directory_uri() . '/assets/css/frontend/pages/submit.css',
+ [],
+ (string) filemtime($css)
+ );
}
-
-get_header();
-
-// Submit settings
-$submit_settings = [];
-if (class_exists('\AKSRT\Settings\Settings')) {
- $all = \AKSRT\Settings\Settings::get_all();
- $submit_settings = [
- 'enabled' => !empty($all['front_submit_enabled']),
- 'review_mode' => $all['front_submit_review_mode'] ?? 'pending',
- 'allow_cover' => !isset($all['front_submit_cover']) || !empty($all['front_submit_cover']),
- 'allow_excerpt' => !isset($all['front_submit_excerpt']) || !empty($all['front_submit_excerpt']),
- 'allow_category' => !isset($all['front_submit_category']) || !empty($all['front_submit_category']),
- 'allow_tags' => !isset($all['front_submit_tags']) || !empty($all['front_submit_tags']),
- 'max_images' => (int) ($all['front_submit_max_images'] ?? 5),
- 'allowed_cat_ids' => is_array($all['post_article_cat'] ?? null) ? array_map('intval', $all['post_article_cat']) : [],
- 'topics_s' => !empty($all['post_article_topics_s']),
- 'custom_filter_s' => !isset($all['post_article_custom_filter_s']) || !empty($all['post_article_custom_filter_s']),
- ];
-}
-
-// §3.7 topics: load topics taxonomy terms
-$topics_terms = [];
-if (!empty($submit_settings['topics_s']) && taxonomy_exists('topics')) {
- $topics_terms = get_terms(['taxonomy' => 'topics', 'hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC']);
- if (is_wp_error($topics_terms)) $topics_terms = [];
-}
-$current_topics = [];
-if ($edit_post && taxonomy_exists('topics')) {
- $terms = wp_get_object_terms($edit_post->ID, 'topics', ['fields' => 'ids']);
- if (!is_wp_error($terms)) $current_topics = $terms;
-}
-
-// §3.4 custom_filter: load dimension definitions for submit form
-$custom_dimensions = [];
-if (!empty($submit_settings['custom_filter_s']) && class_exists('\AKSRT\Settings\Settings')) {
- $filter_all = \AKSRT\Settings\Settings::get_post_filter_settings();
- $custom_dimensions = $filter_all['custom_filter'];
-}
-
-// §3.4 custom_filter: load current values for edit mode
-$current_cf = [];
-if ($edit_post && !empty($custom_dimensions)) {
- foreach ($custom_dimensions as $dim) {
- $current_cf[$dim['key']] = get_post_meta($edit_post->ID, '_aksrt_cf_' . $dim['key'], true);
- }
-}
-
-// §3.7 post_article_cat: filter categories by allowed list
-$cat_args = [
- 'taxonomy' => 'category',
- 'hide_empty' => false,
- 'orderby' => 'name',
- 'order' => 'ASC',
-];
-if (!empty($submit_settings['allowed_cat_ids'])) {
- $cat_args['include'] = $submit_settings['allowed_cat_ids'];
-}
-$categories = get_categories($cat_args);
-
-// Current values for edit mode
-$current_title = $edit_post ? $edit_post->post_title : '';
-$current_content = $edit_post ? $edit_post->post_content : '';
-$current_excerpt = $edit_post ? $edit_post->post_excerpt : '';
-$current_cats = $edit_post ? wp_get_post_categories($edit_post->ID) : [];
-$current_tags = $edit_post ? implode(', ', wp_get_post_tags($edit_post->ID, ['fields' => 'names'])) : '';
-$current_thumb = $edit_post ? get_the_post_thumbnail_url($edit_post->ID, 'large') : '';
-?>
-
-
-
-
-
-
-
-
- 'post',
- 'post_status' => 'draft',
- 'author' => $user_id,
- 'numberposts' => 10,
- 'orderby' => 'modified',
- 'order' => 'DESC',
- ]);
- if (!empty($user_drafts)) :
- ?>
-
-
-
-
-
-is_available();
+ $mini = get_template_directory() . '/assets/js/mini-upload.js';
+ if (file_exists($mini)) {
+ wp_enqueue_script(
+ 'aksrt-mini-upload',
+ get_template_directory_uri() . '/assets/js/mini-upload.js',
+ [],
+ (string) filemtime($mini),
+ true
+ );
+ }
+ if (!empty($ctx['can_upload_img']) || !empty($ctx['can_cover_image']) || !empty($ctx['can_cover_slide'])) {
+ wp_enqueue_media();
+ }
+
wp_localize_script('aksrt-front-submit', 'aksrtSubmitData', [
'ajaxUrl' => admin_url('admin-ajax.php'),
- 'nonce' => wp_create_nonce('aksrt_submit_nonce'),
+ 'nonce' => wp_create_nonce('aksrt_submit'),
'restUrl' => esc_url_raw(rest_url('aksrt/v1')),
'restNonce' => wp_create_nonce('wp_rest'),
+ 'leaveWarning' => !empty($ctx['settings']['leave_warning']),
'ai' => [
- 'available' => $ai_available,
- 'summary' => $ai_available,
- 'tags' => $ai_available,
+ 'available' => !empty($ctx['ai_available']),
+ 'summary' => !empty($ctx['ai_available']),
+ 'tags' => !empty($ctx['ai_available']),
],
'i18n' => [
- 'confirmLeave' => __('你有未保存的更改,确定要离开吗?', 'aksrt'),
- 'saving' => __('保存中...', 'aksrt'),
- 'saved' => __('草稿已保存', 'aksrt'),
- 'submitting' => __('提交中...', 'aksrt'),
- 'submitted' => __('投稿已提交', 'aksrt'),
- 'error' => __('操作失败,请重试', 'aksrt'),
- 'confirmDelete' => __('确定要删除这个草稿吗?', 'aksrt'),
- 'aiGenerating' => __('生成中…', 'aksrt'),
- 'aiFailed' => __('AI 生成失败', 'aksrt'),
- 'needContent' => __('请先填写正文内容', 'aksrt'),
- 'aiFilled' => __('已填入,可继续编辑', 'aksrt'),
+ 'confirmLeave' => __('你有未保存的更改,确定要离开吗?', 'aksrt'),
+ 'saving' => __('保存中...', 'aksrt'),
+ 'saved' => __('草稿已保存', 'aksrt'),
+ 'submitting' => __('提交中...', 'aksrt'),
+ 'submitted' => __('投稿已提交', 'aksrt'),
+ 'error' => __('操作失败,请重试', 'aksrt'),
+ 'confirmDelete' => __('确定要删除这个草稿吗?', 'aksrt'),
+ 'aiGenerating' => __('生成中…', 'aksrt'),
+ 'aiFailed' => __('AI 生成失败', 'aksrt'),
+ 'needContent' => __('请先填写正文内容', 'aksrt'),
+ 'aiFilled' => __('已填入,可继续编辑', 'aksrt'),
+ 'lastSaved' => __('最后保存:', 'aksrt'),
+ 'preview' => __('预览文章', 'aksrt'),
+ 'pickImage' => __('选择图片', 'aksrt'),
+ 'pickVideo' => __('选择视频封面图', 'aksrt'),
+ 'videoUrl' => __('请输入视频地址', 'aksrt'),
],
]);
}
+get_header();
+?>
+
+
+
+settings() : (class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_submit_settings() : []);
+$enabled = !isset($settings['enabled']) || !empty($settings['enabled']);
+
+$edit_id = isset($_GET['edit']) ? absint($_GET['edit']) : 0;
+$edit_post = null;
+if ($edit_id > 0 && $front) {
+ if ($front->can_edit_post($edit_id, $user_id)) {
+ $edit_post = get_post($edit_id);
+ if (!$edit_post || $edit_post->post_type !== 'post') {
+ $edit_post = null;
+ $edit_id = 0;
+ }
+ } else {
+ $edit_id = 0;
+ }
+}
+
+$is_guest = $user_id <= 0;
+$can_add = $front ? $front->can_submit($user_id) : false;
+$can_edit = $edit_post instanceof WP_Post;
+$is_can_new = $can_edit || $can_add;
+
+// Caps.
+$cap = static function (string $c) use ($front, $user_id, $edit_id): bool {
+ if ($front) {
+ return $front->user_can_cap($c, $user_id, $edit_id ?: null);
+ }
+ return function_exists('aksrt_user_can') ? aksrt_user_can($c, $user_id, $edit_id ?: null) : ($user_id > 0);
+};
+
+$post_meta = [];
+if ($edit_post) {
+ $raw_meta = get_post_meta($edit_post->ID, \AKSRT\Core\DomainModel::POST_META_KEY, true);
+ $post_meta = is_array($raw_meta) ? $raw_meta : [];
+}
+$pay_meta = ($edit_post && function_exists('aksrt_get_pay_meta'))
+ ? aksrt_get_pay_meta($edit_post->ID)
+ : (class_exists('\AKSRT\Core\DomainModel') ? \AKSRT\Core\DomainModel::default_pay_meta() : []);
+
+// Featured initial args for JS.
+$featured_args = ['type' => '', 'data' => new stdClass(), 'options' => [
+ 'image' => $cap('new_post_image_cover'),
+ 'slide' => $cap('new_post_slide_cover'),
+ 'video' => $cap('new_post_video_cover'),
+ 'ratio' => 50,
+]];
+if ($edit_post) {
+ if (!empty($post_meta['featured_video'])) {
+ $featured_args['type'] = 'video';
+ $featured_args['data'] = [
+ 'url' => (string) $post_meta['featured_video'],
+ 'pic' => (string) ($post_meta['thumbnail_url'] ?? ''),
+ ];
+ } elseif (!empty($post_meta['featured_slide'])) {
+ $slides = array_filter(array_map('trim', explode(',', (string) $post_meta['featured_slide'])));
+ $featured_args['type'] = 'slide';
+ $featured_args['data'] = array_map(static function ($u) {
+ return ['url' => $u];
+ }, $slides);
+ } else {
+ $thumb = get_the_post_thumbnail_url($edit_post, 'full')
+ ?: (string) ($post_meta['thumbnail_url'] ?? $post_meta['cover_images'] ?? '');
+ if ($thumb !== '') {
+ $featured_args['type'] = 'image';
+ $featured_args['data'] = ['url' => $thumb];
+ }
+ }
+}
+
+// Terms.
+$cat_args = [
+ 'taxonomy' => 'category',
+ 'hide_empty' => false,
+ 'orderby' => 'name',
+ 'order' => 'ASC',
+];
+if (!empty($settings['allowed_cat_ids'])) {
+ $cat_args['include'] = $settings['allowed_cat_ids'];
+} else {
+ $cat_args['parent'] = 0;
+}
+$categories = get_terms($cat_args);
+if (is_wp_error($categories)) {
+ $categories = [];
+}
+$current_cats = $edit_post ? wp_get_post_categories($edit_post->ID) : [];
+
+$topics_terms = [];
+$current_topics = [];
+if (!empty($settings['topics_s']) && taxonomy_exists('topics')) {
+ $topics_terms = get_terms(['taxonomy' => 'topics', 'hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC']);
+ if (is_wp_error($topics_terms)) {
+ $topics_terms = [];
+ }
+ if ($edit_post) {
+ $t = wp_get_object_terms($edit_post->ID, 'topics', ['fields' => 'ids']);
+ $current_topics = is_wp_error($t) ? [] : $t;
+ }
+}
+
+$custom_dimensions = [];
+$current_cf = [];
+if (!empty($settings['custom_filter_s']) && class_exists('\AKSRT\Settings\Settings')) {
+ $filter_all = \AKSRT\Settings\Settings::get_post_filter_settings();
+ $custom_dimensions = $filter_all['custom_filter'] ?? [];
+ if ($edit_post && !empty($custom_dimensions)) {
+ foreach ($custom_dimensions as $dim) {
+ $key = sanitize_key((string) ($dim['key'] ?? ''));
+ if ($key === '') {
+ continue;
+ }
+ $current_cf[$key] = get_post_meta($edit_post->ID, '_aksrt_cf_' . $key, true);
+ }
+ }
+}
+
+$current_title = $edit_post ? $edit_post->post_title : '';
+$current_content = $edit_post ? $edit_post->post_content : '';
+$current_excerpt = $edit_post ? $edit_post->post_excerpt : '';
+$current_tags = $edit_post ? implode(', ', wp_get_post_tags($edit_post->ID, ['fields' => 'names'])) : '';
+$post_status = $edit_post ? $edit_post->post_status : '';
+$post_modified = $edit_post ? $edit_post->post_modified : '';
+
+$draft_count = 0;
+$user_drafts = [];
+if ($user_id > 0) {
+ $user_drafts = get_posts([
+ 'post_type' => 'post',
+ 'post_status' => 'draft',
+ 'author' => $user_id,
+ 'numberposts' => 10,
+ 'orderby' => 'modified',
+ 'order' => 'DESC',
+ ]);
+ $draft_count = count($user_drafts);
+}
+
+// VIP labels / points.
+$vip = class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_vip_settings')
+ ? \AKSRT\Settings\Settings::get_vip_settings()
+ : [];
+if (empty($vip) && class_exists('\AKSRT\Settings\PaymentRuntime')) {
+ // Fallback: read common keys.
+ $all = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
+ $vip = [
+ 'vip1_enabled' => !empty($all['pay_user_vip_1_s']),
+ 'vip2_enabled' => !empty($all['pay_user_vip_2_s']),
+ 'vip1_name' => (string) ($all['pay_user_vip_1_name'] ?? 'VIP1'),
+ 'vip2_name' => (string) ($all['pay_user_vip_2_name'] ?? 'VIP2'),
+ ];
+}
+$points_enabled = class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'credits_enabled')
+ ? \AKSRT\Settings\Settings::credits_enabled()
+ : false;
+
+$submit_label = __('发布', 'aksrt');
+if ($is_guest) {
+ $submit_label = __('审核', 'aksrt');
+} elseif ($edit_post && in_array($post_status, ['publish', 'pending'], true)) {
+ $submit_label = __('保存', 'aksrt');
+} elseif (($settings['review_mode'] ?? 'pending') === 'pending' && !$cap('new_post_audit_no')) {
+ $submit_label = __('审核', 'aksrt');
+}
+
+$ai_available = class_exists('\AKSRT\Ai\AI') && \AKSRT\Ai\AI::get_instance()->is_available();
+
+return [
+ 'front' => $front,
+ 'user_id' => $user_id,
+ 'settings' => $settings,
+ 'enabled' => $enabled,
+ 'edit_id' => $edit_id,
+ 'edit_post' => $edit_post,
+ 'is_guest' => $is_guest,
+ 'is_can_new' => $is_can_new,
+ 'cap' => $cap,
+ 'post_meta' => $post_meta,
+ 'pay_meta' => $pay_meta,
+ 'featured_args' => $featured_args,
+ 'categories' => $categories,
+ 'current_cats' => $current_cats,
+ 'topics_terms' => $topics_terms,
+ 'current_topics' => $current_topics,
+ 'custom_dimensions' => $custom_dimensions,
+ 'current_cf' => $current_cf,
+ 'current_title' => $current_title,
+ 'current_content' => $current_content,
+ 'current_excerpt' => $current_excerpt,
+ 'current_tags' => $current_tags,
+ 'post_status' => $post_status,
+ 'post_modified' => $post_modified,
+ 'draft_count' => $draft_count,
+ 'user_drafts' => $user_drafts,
+ 'vip' => $vip,
+ 'points_enabled' => $points_enabled,
+ 'submit_label' => $submit_label,
+ 'ai_available' => $ai_available,
+ 'can_pay' => $cap('new_post_pay'),
+ 'can_pay_download' => $cap('new_post_pay_download'),
+ 'can_cover_image' => $cap('new_post_image_cover'),
+ 'can_cover_slide' => $cap('new_post_slide_cover'),
+ 'can_cover_video' => $cap('new_post_video_cover'),
+ 'can_upload_img' => $cap('new_post_upload_img'),
+ 'can_upload_video' => $cap('new_post_upload_video'),
+ 'can_upload_file' => $cap('new_post_upload_file'),
+ 'can_hide' => $cap('new_post_hide'),
+ 'can_iframe' => $cap('new_post_iframe_video'),
+];
diff --git a/templates/parts/submit/editor-main.php b/templates/parts/submit/editor-main.php
new file mode 100644
index 00000000..1cefa8f6
--- /dev/null
+++ b/templates/parts/submit/editor-main.php
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'post_content',
+ 'textarea_rows' => 20,
+ 'editor_height' => wp_is_mobile() ? 460 : 470,
+ 'media_buttons' => !empty($ctx['can_upload_img']) || !empty($ctx['can_upload_video']) || !empty($ctx['can_upload_file']),
+ 'default_editor' => 'tinymce',
+ 'quicktags' => false,
+ 'teeny' => false,
+ 'tinymce' => [
+ 'toolbar1' => $toolbar1,
+ 'toolbar2' => 'fontsizeselect,forecolor,removeformat,undo,redo,wp_help',
+ 'plugins' => $plugins,
+ 'placeholder' => (string) ($settings['content_placeholder'] ?? ''),
+ ],
+ ];
+ wp_editor((string) $ctx['current_content'], 'post_content', $editor_settings);
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0) : ?>
+ 'post', 'post_status' => 'draft'], aksrt_get_user_home_url($ctx['user_id']))
+ : '';
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/templates/parts/submit/featured.php b/templates/parts/submit/featured.php
new file mode 100644
index 00000000..77bf3cc5
--- /dev/null
+++ b/templates/parts/submit/featured.php
@@ -0,0 +1,71 @@
+
+
+
+
+
 $data['url']); ?>)
+
+
 $data[0]['url']); ?>)
+
+
+
+
 $data['pic']); ?>)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/templates/parts/submit/sidebar-pay.php b/templates/parts/submit/sidebar-pay.php
new file mode 100644
index 00000000..f4440176
--- /dev/null
+++ b/templates/parts/submit/sidebar-pay.php
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
>
+
+
+
+
diff --git a/templates/parts/submit/sidebar-submit.php b/templates/parts/submit/sidebar-submit.php
new file mode 100644
index 00000000..b22539df
--- /dev/null
+++ b/templates/parts/submit/sidebar-submit.php
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID)
+ : add_query_arg('edit', $draft->ID, get_permalink());
+ ?>
+
+
+
+
+
diff --git a/templates/parts/submit/sidebar-terms.php b/templates/parts/submit/sidebar-terms.php
new file mode 100644
index 00000000..06d57c3c
--- /dev/null
+++ b/templates/parts/submit/sidebar-terms.php
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ' . esc_html__('暂无可选择的分类', 'aksrt') . '';
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/templates/parts/submit/term-checks.php b/templates/parts/submit/term-checks.php
new file mode 100644
index 00000000..5ad34e43
--- /dev/null
+++ b/templates/parts/submit/term-checks.php
@@ -0,0 +1,71 @@
+parent;
+ if (!isset($children_map[$pid])) {
+ $children_map[$pid] = [];
+ }
+ $children_map[$pid][] = $term;
+ }
+
+ $render = static function (int $pid, int $d) use (&$render, $children_map, $checked, $name): void {
+ if (empty($children_map[$pid])) {
+ return;
+ }
+ foreach ($children_map[$pid] as $term) {
+ $tid = (int) $term->term_id;
+ $pad = $d > 0 ? ' style="margin-left:' . (int) ($d * 16) . 'px"' : '';
+ echo '';
+ $render($tid, $d + 1);
+ }
+ };
+
+ // If include list was used without hierarchy, still render flat.
+ if (empty($children_map) && !empty($terms)) {
+ foreach ($terms as $term) {
+ if (!$term instanceof WP_Term) {
+ continue;
+ }
+ $tid = (int) $term->term_id;
+ echo '';
+ }
+ return;
+ }
+
+ $render($parent, $depth);
+
+ // Orphans (included children without parent in set).
+ $seen_parents = array_keys($children_map);
+ foreach ($terms as $term) {
+ if (!$term instanceof WP_Term) {
+ continue;
+ }
+ $pid = (int) $term->parent;
+ if ($pid > 0 && !isset($children_map[$pid]) && !isset($children_map[0])) {
+ // already handled if parent missing — skip double.
+ }
+ }
+ }
+endif;