feat(front-submit): 1:1 写文章/投稿页对齐 zibll newposts
Register AKSRT 写文章/投稿 page template, wire header publish entry to front submit URL, rewrite page-submit as dual-column TinyMCE compose with featured cover, multi-category/topics/filters, pay box, drafts, and capability-gated FrontSubmit AJAX (pay/featured traits).
This commit is contained in:
@@ -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/` }),
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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 } },
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+514
-367
@@ -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 = '<i class="fas fa-spinner fa-spin"></i> ' + (status === 'draft' ? (i18n.saving || '保存中...') : (i18n.submitting || '提交中...'));
|
||||
self.isSubmitting = true;
|
||||
btn.innerHTML =
|
||||
'<i class="fas fa-spinner fa-spin"></i> ' +
|
||||
(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) {
|
||||
.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;
|
||||
self.form.setAttribute('data-dirty', 'false');
|
||||
btn.innerHTML = '<i class="fas fa-check"></i> ' + (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);
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
alert(data.data && data.data.message ? data.data.message : (i18n.error || '操作失败'));
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = originalText;
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
alert(i18n.error || '操作失败,请重试');
|
||||
if (d.time) {
|
||||
var timeEl = qs('.aksrt-submit-modified-time');
|
||||
if (timeEl) {
|
||||
timeEl.innerHTML =
|
||||
'<span class="badg">' + (i18n.lastSaved || 'Last saved: ') + d.time + '</span>';
|
||||
}
|
||||
}
|
||||
if (d.url) {
|
||||
var view = qs('.aksrt-submit-view-btn');
|
||||
if (view) {
|
||||
view.innerHTML =
|
||||
'<a class="but c-blue" target="_blank" href="' +
|
||||
d.url +
|
||||
'"><i class="fa fa-file-text-o"></i> ' +
|
||||
(i18n.preview || 'Preview') +
|
||||
'</a>';
|
||||
}
|
||||
}
|
||||
btn.innerHTML =
|
||||
'<i class="fas fa-check"></i> ' +
|
||||
(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 = originalText;
|
||||
btn.innerHTML = original;
|
||||
self.isSubmitting = false;
|
||||
}, 1200);
|
||||
})
|
||||
.finally(function () {
|
||||
.catch(function (err) {
|
||||
alert(err.message || i18n.error || 'Error');
|
||||
btn.disabled = false;
|
||||
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) {
|
||||
.then(function (r) {
|
||||
return r.json();
|
||||
})
|
||||
.then(function (res) {
|
||||
if (res && res.success) {
|
||||
var item = btn.closest('.aksrt-submit-drafts__item');
|
||||
if (item) item.remove();
|
||||
if (item) {
|
||||
item.remove();
|
||||
}
|
||||
} else {
|
||||
alert(data.data && data.data.message ? data.data.message : '删除失败');
|
||||
alert((res && res.data && res.data.message) || i18n.error || 'Error');
|
||||
}
|
||||
})
|
||||
.catch(function () { alert('删除失败'); });
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Editor toolbar actions
|
||||
*/
|
||||
bindEditorToolbar: function () {
|
||||
var textarea = document.getElementById('submit-content');
|
||||
if (!textarea) return;
|
||||
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) {}
|
||||
},
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('[data-action]');
|
||||
if (!btn) return;
|
||||
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;
|
||||
},
|
||||
|
||||
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```');
|
||||
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 = '<img src="' + d.url + '" alt="">';
|
||||
} else if (t === 'slide' && Array.isArray(d) && d[0] && d[0].url) {
|
||||
html =
|
||||
'<img src="' +
|
||||
d[0].url +
|
||||
'" alt=""><span class="aksrt-submit-featured__badge"><i class="fas fa-images"></i></span>';
|
||||
} else if (t === 'video') {
|
||||
html =
|
||||
(d.pic
|
||||
? '<img src="' + d.pic + '" alt="">'
|
||||
: '<div class="aksrt-submit-featured__video-placeholder"><i class="fas fa-play"></i></div>') +
|
||||
'<span class="aksrt-submit-featured__badge"><i class="fas fa-video"></i></span>';
|
||||
} else {
|
||||
FRONT_SUBMIT.wrapSelection(ta, '`');
|
||||
html = '<div class="aksrt-submit-featured__empty"><i class="fas fa-image"></i><span>Cover</span></div>';
|
||||
}
|
||||
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();
|
||||
});
|
||||
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) {
|
||||
var box = qs('.featured-edit, .aksrt-submit-featured');
|
||||
if (!box) {
|
||||
return;
|
||||
}
|
||||
box.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('[data-featured-action]');
|
||||
if (!btn) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
var act = btn.getAttribute('data-featured-action');
|
||||
if (act === 'close') {
|
||||
self.setFeatured('close', {});
|
||||
return;
|
||||
}
|
||||
if (act === 'image') {
|
||||
self.openMedia({ title: i18n.pickImage, multiple: false }, function (file) {
|
||||
if (file) {
|
||||
self.setFeatured('image', { url: file.url, id: file.id });
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
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');
|
||||
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 content = getEditorContent();
|
||||
if (!content || content.replace(/<[^>]+>/g, '').trim().length < 10) {
|
||||
alert(i18n.needContent || 'Need content');
|
||||
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 }));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
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 }));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
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, {
|
||||
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': restNonce,
|
||||
'X-WP-Nonce': cfg.restNonce || '',
|
||||
},
|
||||
body: JSON.stringify(body || {}),
|
||||
body: JSON.stringify({ content: content }),
|
||||
})
|
||||
.then(function (r) {
|
||||
return r.json().then(function (json) {
|
||||
return { ok: r.ok, json: json };
|
||||
});
|
||||
return r.json();
|
||||
})
|
||||
.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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
onSuccess(res.json.data || {});
|
||||
})
|
||||
.catch(function () {
|
||||
btn.removeAttribute('data-loading');
|
||||
btn.textContent = original;
|
||||
alert(i18n.aiFailed || 'AI 生成失败');
|
||||
.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();
|
||||
}
|
||||
})();
|
||||
|
||||
+3
-2
@@ -326,10 +326,11 @@ if ($_footer_conter === '') {
|
||||
<?php
|
||||
$floating_settings = $has_settings ? \AKSRT\Settings\Settings::get_floating_buttons_settings() : [];
|
||||
$floating_items = $has_settings ? \AKSRT\Settings\Settings::get_floating_buttons_items() : [];
|
||||
$floating_enabled = !empty($floating_settings['enabled']) && !empty($floating_items);
|
||||
$aksrt_hide_chrome = is_page_template('templates/pages/page-submit.php');
|
||||
$floating_enabled = !$aksrt_hide_chrome && !empty($floating_settings['enabled']) && !empty($floating_items);
|
||||
$tabbar_settings = $has_settings ? \AKSRT\Settings\Settings::get_footer_tabbar_settings() : [];
|
||||
$tabbar_items = $has_settings ? \AKSRT\Settings\Settings::get_footer_tabbar_items() : [];
|
||||
$tabbar_enabled = !empty($tabbar_settings['enabled']) && !empty($tabbar_items);
|
||||
$tabbar_enabled = !$aksrt_hide_chrome && !empty($tabbar_settings['enabled']) && !empty($tabbar_items);
|
||||
$floating_classes = 'float-right';
|
||||
$floating_classes .= ' ' . (!empty($floating_settings['style']) ? sanitize_html_class($floating_settings['style']) : 'round');
|
||||
$floating_classes .= !empty($floating_settings['position']) && $floating_settings['position'] === 'center' ? ' position-center' : ' position-bottom';
|
||||
|
||||
+5
-2
@@ -130,7 +130,10 @@ if (!empty($aksrt_bg_styles)) {
|
||||
$aksrt_user_credit = function_exists('aksrt_user_credits') && is_user_logged_in() ? (int) aksrt_user_credits(get_current_user_id()) : 0;
|
||||
$aksrt_currency_name = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_currency_name() : __('积分', 'aksrt');
|
||||
$aksrt_orders_url = function_exists('aksrt_get_orders_page_url') ? aksrt_get_orders_page_url() : '';
|
||||
$aksrt_publish_url = function_exists('aksrt_get_new_post_url') ? aksrt_get_new_post_url() : '';
|
||||
if ($aksrt_publish_url === '') {
|
||||
$aksrt_publish_url = admin_url('post-new.php');
|
||||
}
|
||||
$aksrt_messages_url = function_exists('aksrt_get_messages_page_url') ? aksrt_get_messages_page_url() : '';
|
||||
$aksrt_unread_count = (is_user_logged_in() && function_exists('aksrt_get_user_unread_notification_count'))
|
||||
? (int) aksrt_get_user_unread_notification_count(get_current_user_id())
|
||||
@@ -260,7 +263,7 @@ if (!empty($aksrt_bg_styles)) {
|
||||
|
||||
<div class="nav-actions__cta">
|
||||
<?php if ($aksrt_can_publish) : ?>
|
||||
<a class="btn-primary" href="<?php echo esc_url(admin_url('post-new.php')); ?>">
|
||||
<a class="btn-primary" href="<?php echo esc_url($aksrt_publish_url); ?>">
|
||||
<i class="fas fa-pen-nib" aria-hidden="true"></i>
|
||||
<span><?php esc_html_e('发布', 'aksrt'); ?></span>
|
||||
</a>
|
||||
@@ -462,7 +465,7 @@ if (!empty($aksrt_bg_styles)) {
|
||||
<div class="nav-actions__cta nav-actions__cta--drawer">
|
||||
<?php if ($aksrt_can_publish) : ?>
|
||||
<div class="nav-drawer__publish">
|
||||
<a class="btn-primary" href="<?php echo esc_url(admin_url('post-new.php')); ?>">
|
||||
<a class="btn-primary" href="<?php echo esc_url($aksrt_publish_url); ?>">
|
||||
<i class="fas fa-pen-nib" aria-hidden="true"></i>
|
||||
<span><?php esc_html_e('发布内容', 'aksrt'); ?></span>
|
||||
</a>
|
||||
|
||||
@@ -436,9 +436,11 @@ if (!function_exists('aksrt_single_render_author_card')) :
|
||||
<div class="user-btns mt20">
|
||||
<?php if ($is_self) : ?>
|
||||
<?php
|
||||
$submit = function_exists('aksrt_get_page_url_by_template')
|
||||
$submit = function_exists('aksrt_get_new_post_url')
|
||||
? (string) aksrt_get_new_post_url()
|
||||
: (function_exists('aksrt_get_page_url_by_template')
|
||||
? (string) aksrt_get_page_url_by_template('templates/pages/page-submit.php')
|
||||
: '';
|
||||
: '');
|
||||
$center = function_exists('aksrt_get_user_center_url')
|
||||
? (string) aksrt_get_user_center_url()
|
||||
: $author_url;
|
||||
|
||||
@@ -32,6 +32,34 @@ function aksrt_get_user_center_url(string $tab = ''): string {
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_new_post_url')) :
|
||||
/**
|
||||
* 前台写文章/投稿页 URL(模板 templates/pages/page-submit.php)。
|
||||
*
|
||||
* @param int $edit_id 编辑中的文章 ID;0 表示新建。
|
||||
* @return string 未指派页面时返回空串。
|
||||
*/
|
||||
function aksrt_get_new_post_url(int $edit_id = 0): string {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$submit = \AKSRT\Settings\Settings::get_submit_settings();
|
||||
if (isset($submit['enabled']) && empty($submit['enabled'])) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
$url = function_exists('aksrt_get_page_url_by_template')
|
||||
? aksrt_get_page_url_by_template('templates/pages/page-submit.php')
|
||||
: '';
|
||||
if ($url === '') {
|
||||
return '';
|
||||
}
|
||||
if ($edit_id > 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)。
|
||||
|
||||
@@ -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' => '投稿时可选择自定义筛选维度值'],
|
||||
|
||||
+284
-306
@@ -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<string, mixed>
|
||||
*/
|
||||
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 {
|
||||
$settings = $this->settings();
|
||||
if (empty($settings['enabled'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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']) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
return ['success' => false, 'message' => $result->get_error_message()];
|
||||
return $this->user_can_cap('new_post_edit', $user_id, $post_id);
|
||||
}
|
||||
|
||||
// Update category
|
||||
$category = (int) ($data['category'] ?? 0);
|
||||
if ($category > 0) {
|
||||
wp_set_post_categories($post_id, [$category]);
|
||||
public function can_delete_post(int $post_id, ?int $user_id = null): bool {
|
||||
if ($user_id === null) {
|
||||
$user_id = get_current_user_id();
|
||||
}
|
||||
if ($user_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (user_can($user_id, 'manage_options')) {
|
||||
return true;
|
||||
}
|
||||
$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 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, []);
|
||||
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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'user_can')) {
|
||||
return \AKSRT\Settings\Settings::user_can($cap, $user_id, $context);
|
||||
}
|
||||
}
|
||||
|
||||
// Update topics + custom filter meta
|
||||
$this->apply_topics_and_filters($post_id, $data);
|
||||
|
||||
return ['success' => true, 'post_id' => $post_id, 'message' => __('已更新', 'aksrt')];
|
||||
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<string, mixed> $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<string, mixed> $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<string,bool> $robots
|
||||
* @return array<string,bool>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
return $enabled;
|
||||
}
|
||||
|
||||
$result = $this->update($post_id, $_POST);
|
||||
|
||||
if ($result['success']) {
|
||||
wp_send_json_success($result);
|
||||
}
|
||||
|
||||
wp_send_json_error($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Core;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Featured cover (image / slide / video) for front-end submit.
|
||||
*/
|
||||
trait FrontSubmitFeatured {
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Core;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Front-end pay meta save for article submit.
|
||||
*/
|
||||
trait FrontSubmitPay {
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @param array<string, mixed> $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<int, array<string, string>>
|
||||
*/
|
||||
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<string, mixed> $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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Core;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Create / update / status machine for front-end article submit.
|
||||
*/
|
||||
trait FrontSubmitSave {
|
||||
|
||||
/**
|
||||
* Persist a front-end submission (new or edit).
|
||||
*
|
||||
* @param array<string, mixed> $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<string, mixed> $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<string, mixed> $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<string,mixed> $data
|
||||
* @param array<string,mixed> $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<string,string> $tags
|
||||
* @return array<string,string>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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'),
|
||||
|
||||
@@ -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))),
|
||||
|
||||
+94
-266
@@ -1,295 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* Template Name: AKSRT - Submit
|
||||
* Template Name: AKSRT - 写文章/投稿
|
||||
* Template Post Type: page
|
||||
*
|
||||
* 前台投稿页面模板。
|
||||
* 支持标题、摘要、分类、标签、正文、封面、草稿保存。
|
||||
* 前台文章投稿页(对齐 zibll newposts:双栏 + TinyMCE + 封面 + 付费 + 草稿)。
|
||||
* 注意:page-newposts.php 是「最新文章」列表,不是投稿。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!is_user_logged_in()) {
|
||||
wp_redirect(aksrt_get_login_url(get_permalink()));
|
||||
$ctx = include get_template_directory() . '/templates/parts/submit/context.php';
|
||||
if (!is_array($ctx)) {
|
||||
$ctx = [];
|
||||
}
|
||||
|
||||
// Feature disabled → soft 404.
|
||||
if (empty($ctx['enabled'])) {
|
||||
global $wp_query;
|
||||
$wp_query->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') : '';
|
||||
?>
|
||||
|
||||
<main id="main-content" class="site-main content-shell content-shell--page" tabindex="-1">
|
||||
<div class="aksrt-submit-page container">
|
||||
<header class="aksrt-submit-page__header">
|
||||
<h1 class="aksrt-submit-page__title">
|
||||
<?php echo $edit_post ? esc_html__('编辑草稿', 'aksrt') : esc_html__('投稿', 'aksrt'); ?>
|
||||
</h1>
|
||||
<p class="aksrt-submit-page__desc">
|
||||
<?php echo $edit_post
|
||||
? esc_html__('继续编辑你的草稿,完成后提交审核。', 'aksrt')
|
||||
: esc_html__('分享你的内容,提交后将进入审核流程。', 'aksrt'); ?>
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<form class="aksrt-submit-form" id="aksrt-submit-form" data-aksrt-submit-form data-dirty="false">
|
||||
<?php if ($edit_id > 0) : ?>
|
||||
<input type="hidden" name="edit_id" value="<?php echo esc_attr((string) $edit_id); ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="aksrt-form-group">
|
||||
<label for="submit-title"><?php esc_html_e('标题', 'aksrt'); ?> *</label>
|
||||
<input type="text" id="submit-title" name="title" class="aksrt-form-input" required maxlength="200" placeholder="<?php esc_attr_e('请输入文章标题', 'aksrt'); ?>" value="<?php echo esc_attr($current_title); ?>">
|
||||
</div>
|
||||
|
||||
<?php if (!empty($submit_settings['allow_excerpt'])) : ?>
|
||||
<div class="aksrt-form-group">
|
||||
<label for="submit-excerpt">
|
||||
<?php esc_html_e('摘要', 'aksrt'); ?>
|
||||
<button type="button" class="aksrt-ai-assist-btn" data-ai-action="summary" hidden><?php esc_html_e('AI 生成摘要', 'aksrt'); ?></button>
|
||||
</label>
|
||||
<textarea id="submit-excerpt" name="excerpt" class="aksrt-form-input" rows="3" maxlength="300" placeholder="<?php esc_attr_e('可选:文章摘要', 'aksrt'); ?>"><?php echo esc_textarea($current_excerpt); ?></textarea>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="aksrt-submit-form__row">
|
||||
<?php if (!empty($submit_settings['allow_category'])) : ?>
|
||||
<div class="aksrt-form-group aksrt-submit-form__col">
|
||||
<label for="submit-category"><?php esc_html_e('分类', 'aksrt'); ?></label>
|
||||
<select id="submit-category" name="category" class="aksrt-form-input">
|
||||
<option value=""><?php esc_html_e('选择分类', 'aksrt'); ?></option>
|
||||
<?php foreach ($categories as $cat) : ?>
|
||||
<option value="<?php echo esc_attr((string) $cat->term_id); ?>" <?php selected(in_array($cat->term_id, $current_cats)); ?>><?php echo esc_html($cat->name); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($submit_settings['allow_tags'])) : ?>
|
||||
<div class="aksrt-form-group aksrt-submit-form__col">
|
||||
<label for="submit-tags">
|
||||
<?php esc_html_e('标签', 'aksrt'); ?>
|
||||
<button type="button" class="aksrt-ai-assist-btn" data-ai-action="tags" hidden><?php esc_html_e('AI 建议标签', 'aksrt'); ?></button>
|
||||
</label>
|
||||
<input type="text" id="submit-tags" name="tags" class="aksrt-form-input" placeholder="<?php esc_attr_e('多个标签用逗号分隔', 'aksrt'); ?>" value="<?php echo esc_attr($current_tags); ?>">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($submit_settings['topics_s']) && !empty($topics_terms)) : ?>
|
||||
<div class="aksrt-form-group">
|
||||
<label for="submit-topics"><?php esc_html_e('专题', 'aksrt'); ?></label>
|
||||
<select id="submit-topics" name="topics[]" class="aksrt-form-input" multiple>
|
||||
<?php foreach ($topics_terms as $topic) : ?>
|
||||
<option value="<?php echo esc_attr((string) $topic->term_id); ?>" <?php selected(in_array($topic->term_id, $current_topics)); ?>><?php echo esc_html($topic->name); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($submit_settings['custom_filter_s']) && !empty($custom_dimensions)) : ?>
|
||||
<?php foreach ($custom_dimensions as $dim) : ?>
|
||||
<div class="aksrt-form-group">
|
||||
<label for="submit-cf-<?php echo esc_attr($dim['key']); ?>"><?php echo esc_html($dim['name']); ?></label>
|
||||
<select id="submit-cf-<?php echo esc_attr($dim['key']); ?>" name="cf_<?php echo esc_attr($dim['key']); ?>" class="aksrt-form-input">
|
||||
<option value=""><?php printf(esc_html__('选择%s', 'aksrt'), esc_html($dim['name'])); ?></option>
|
||||
<?php foreach ($dim['vals'] as $val) : ?>
|
||||
<option value="<?php echo esc_attr($val['key']); ?>" <?php selected(!empty($current_cf[$dim['key']]) && $current_cf[$dim['key']] === $val['key']); ?>><?php echo esc_html($val['name']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="aksrt-form-group">
|
||||
<label for="submit-content"><?php esc_html_e('内容', 'aksrt'); ?> *</label>
|
||||
<div class="aksrt-submit-form__editor-toolbar">
|
||||
<button type="button" class="aksrt-submit-form__toolbar-btn" data-action="bold" title="<?php esc_attr_e('加粗', 'aksrt'); ?>"><i class="fas fa-bold"></i></button>
|
||||
<button type="button" class="aksrt-submit-form__toolbar-btn" data-action="italic" title="<?php esc_attr_e('斜体', 'aksrt'); ?>"><i class="fas fa-italic"></i></button>
|
||||
<button type="button" class="aksrt-submit-form__toolbar-btn" data-action="code" title="<?php esc_attr_e('代码', 'aksrt'); ?>"><i class="fas fa-code"></i></button>
|
||||
<button type="button" class="aksrt-submit-form__toolbar-btn" data-action="link" title="<?php esc_attr_e('链接', 'aksrt'); ?>"><i class="fas fa-link"></i></button>
|
||||
<button type="button" class="aksrt-submit-form__toolbar-btn" data-action="image" title="<?php esc_attr_e('图片', 'aksrt'); ?>"><i class="fas fa-image"></i></button>
|
||||
<button type="button" class="aksrt-submit-form__toolbar-btn" data-action="quote" title="<?php esc_attr_e('引用', 'aksrt'); ?>"><i class="fas fa-quote-left"></i></button>
|
||||
<button type="button" class="aksrt-submit-form__toolbar-btn" data-action="heading" title="<?php esc_attr_e('标题', 'aksrt'); ?>"><i class="fas fa-heading"></i></button>
|
||||
<button type="button" class="aksrt-submit-form__toolbar-btn" data-action="list" title="<?php esc_attr_e('列表', 'aksrt'); ?>"><i class="fas fa-list-ul"></i></button>
|
||||
</div>
|
||||
<textarea id="submit-content" name="content" class="aksrt-form-input aksrt-submit-form__textarea" rows="16" required placeholder="<?php esc_attr_e('请输入文章内容(支持 HTML)', 'aksrt'); ?>"><?php echo esc_textarea($current_content); ?></textarea>
|
||||
<div class="aksrt-submit-form__word-count" id="submit-word-count"></div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($submit_settings['allow_cover'])) : ?>
|
||||
<div class="aksrt-form-group">
|
||||
<label for="submit-thumbnail"><?php esc_html_e('封面图', 'aksrt'); ?></label>
|
||||
<div class="aksrt-submit-form__cover-input">
|
||||
<input type="url" id="submit-thumbnail" name="thumbnail" class="aksrt-form-input" placeholder="<?php esc_attr_e('输入封面图地址', 'aksrt'); ?>" value="<?php echo esc_url($current_thumb); ?>">
|
||||
<button type="button" class="aksrt-submit-form__cover-btn" id="aksrt-pick-cover">
|
||||
<i class="fas fa-image"></i>
|
||||
<?php esc_html_e('选择图片', 'aksrt'); ?>
|
||||
</button>
|
||||
</div>
|
||||
<div class="aksrt-submit-form__cover-preview" id="cover-preview" <?php echo $current_thumb ? '' : 'style="display:none"'; ?>>
|
||||
<img id="cover-preview-img" src="<?php echo esc_url($current_thumb); ?>" alt="">
|
||||
<button type="button" class="aksrt-submit-form__cover-remove" id="cover-remove" title="<?php esc_attr_e('移除封面', 'aksrt'); ?>">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="aksrt-submit-form__actions">
|
||||
<button type="submit" class="aksrt-auth-submit" id="aksrt-submit-btn">
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
<?php echo $edit_post ? esc_html__('更新投稿', 'aksrt') : esc_html__('提交投稿', 'aksrt'); ?>
|
||||
</button>
|
||||
<button type="button" class="btn-outline" id="aksrt-save-draft">
|
||||
<i class="fas fa-save"></i>
|
||||
<?php esc_html_e('保存草稿', 'aksrt'); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="_nonce" value="<?php echo esc_attr(wp_create_nonce('aksrt_submit')); ?>">
|
||||
<input type="hidden" name="status" id="submit-status" value="pending">
|
||||
</form>
|
||||
|
||||
<!-- Draft list -->
|
||||
<?php
|
||||
$user_drafts = get_posts([
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'draft',
|
||||
'author' => $user_id,
|
||||
'numberposts' => 10,
|
||||
'orderby' => 'modified',
|
||||
'order' => 'DESC',
|
||||
]);
|
||||
if (!empty($user_drafts)) :
|
||||
?>
|
||||
<div class="aksrt-submit-drafts">
|
||||
<h3 class="aksrt-submit-drafts__title">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
<?php esc_html_e('我的草稿', 'aksrt'); ?>
|
||||
</h3>
|
||||
<div class="aksrt-submit-drafts__list">
|
||||
<?php foreach ($user_drafts as $draft) : ?>
|
||||
<div class="aksrt-submit-drafts__item">
|
||||
<a href="<?php echo esc_url(add_query_arg('edit', $draft->ID, get_permalink())); ?>" class="aksrt-submit-drafts__link">
|
||||
<span class="aksrt-submit-drafts__name"><?php echo esc_html($draft->post_title ?: __('无标题', 'aksrt')); ?></span>
|
||||
<time class="aksrt-submit-drafts__date"><?php echo esc_html(get_the_date('Y-m-d H:i', $draft->ID)); ?></time>
|
||||
</a>
|
||||
<button type="button" class="aksrt-submit-drafts__delete" data-draft-id="<?php echo esc_attr((string) $draft->ID); ?>" title="<?php esc_attr_e('删除草稿', 'aksrt'); ?>">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php
|
||||
// Enqueue the front-submit script
|
||||
$front_submit_path = get_template_directory() . '/assets/js/front-submit.js';
|
||||
if (file_exists($front_submit_path)) {
|
||||
$js = get_template_directory() . '/assets/js/front-submit.js';
|
||||
if (file_exists($js)) {
|
||||
wp_enqueue_script(
|
||||
'aksrt-front-submit',
|
||||
get_template_directory_uri() . '/assets/js/front-submit.js',
|
||||
['aksrt-script'],
|
||||
(string) filemtime($front_submit_path),
|
||||
['jquery'],
|
||||
(string) filemtime($js),
|
||||
true
|
||||
);
|
||||
$ai_available = class_exists('\AKSRT\Ai\AI') && \AKSRT\Ai\AI::get_instance()->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'),
|
||||
@@ -303,8 +95,44 @@ if (file_exists($front_submit_path)) {
|
||||
'aiFailed' => __('AI 生成失败', 'aksrt'),
|
||||
'needContent' => __('请先填写正文内容', 'aksrt'),
|
||||
'aiFilled' => __('已填入,可继续编辑', 'aksrt'),
|
||||
'lastSaved' => __('最后保存:', 'aksrt'),
|
||||
'preview' => __('预览文章', 'aksrt'),
|
||||
'pickImage' => __('选择图片', 'aksrt'),
|
||||
'pickVideo' => __('选择视频封面图', 'aksrt'),
|
||||
'videoUrl' => __('请输入视频地址', 'aksrt'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
get_header();
|
||||
?>
|
||||
<main id="main-content" class="site-main container aksrt-article-compose" tabindex="-1">
|
||||
<form class="aksrt-submit-form aksrt-article-compose__form" id="aksrt-submit-form" data-aksrt-submit-form data-dirty="false" novalidate>
|
||||
<div class="aksrt-article-compose__layout">
|
||||
<?php
|
||||
$main = get_template_directory() . '/templates/parts/submit/editor-main.php';
|
||||
if (file_exists($main)) {
|
||||
include $main;
|
||||
}
|
||||
?>
|
||||
<aside class="aksrt-submit-sidebar">
|
||||
<?php
|
||||
$terms = get_template_directory() . '/templates/parts/submit/sidebar-terms.php';
|
||||
if (file_exists($terms)) {
|
||||
include $terms;
|
||||
}
|
||||
$pay = get_template_directory() . '/templates/parts/submit/sidebar-pay.php';
|
||||
if (file_exists($pay)) {
|
||||
include $pay;
|
||||
}
|
||||
$actions = get_template_directory() . '/templates/parts/submit/sidebar-submit.php';
|
||||
if (file_exists($actions)) {
|
||||
include $actions;
|
||||
}
|
||||
?>
|
||||
</aside>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
<?php
|
||||
get_footer();
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
/**
|
||||
* Submit page context builder — shared by page-submit shell + partials.
|
||||
*
|
||||
* Expects no output. Returns $ctx array.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$front = class_exists('\AKSRT\Core\FrontSubmit') ? \AKSRT\Core\FrontSubmit::get_instance() : null;
|
||||
$user_id = get_current_user_id();
|
||||
$settings = $front ? $front->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'),
|
||||
];
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
/**
|
||||
* Main column: cover + title + editor + status.
|
||||
*
|
||||
* @var array $ctx
|
||||
* @package AKSRT
|
||||
*/
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$settings = $ctx['settings'];
|
||||
$edit_post = $ctx['edit_post'];
|
||||
$is_can_new = !empty($ctx['is_can_new']);
|
||||
?>
|
||||
<div class="aksrt-submit-main">
|
||||
<div class="aksrt-submit-panel aksrt-submit-panel--editor">
|
||||
<?php
|
||||
$featured_path = get_template_directory() . '/templates/parts/submit/featured.php';
|
||||
if (file_exists($featured_path)) {
|
||||
include $featured_path;
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="aksrt-submit-title-wrap">
|
||||
<textarea
|
||||
name="post_title"
|
||||
id="submit-title"
|
||||
class="aksrt-submit-title"
|
||||
rows="1"
|
||||
maxlength="<?php echo esc_attr((string) ((int) ($settings['title_max'] ?? 30) * 4)); ?>"
|
||||
placeholder="<?php echo esc_attr((string) ($settings['title_placeholder'] ?? __('请输入标题', 'aksrt'))); ?>"
|
||||
data-auto-height
|
||||
data-max-height="<?php echo wp_is_mobile() ? '110' : '78'; ?>"
|
||||
><?php echo esc_textarea((string) $ctx['current_title']); ?></textarea>
|
||||
</div>
|
||||
|
||||
<?php if (!$is_can_new) : ?>
|
||||
<div class="aksrt-submit-nocan">
|
||||
<div class="aksrt-submit-nocan__icon"><i class="fas fa-lock"></i></div>
|
||||
<p><?php esc_html_e('无法发布', 'aksrt'); ?></p>
|
||||
<p class="muted"><?php esc_html_e('你当前没有前台投稿权限,请联系管理员开通。', 'aksrt'); ?></p>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="aksrt-submit-editor-wrap">
|
||||
<?php
|
||||
$toolbar1 = 'formatselect,bold,italic,bullist,numlist,alignleft,aligncenter,alignright,link,blockquote,code,fullscreen';
|
||||
$plugins = 'paste,link,lists,fullscreen,wordpress,wplink,charmap';
|
||||
if (!empty($ctx['can_upload_img']) || !empty($ctx['can_upload_video']) || !empty($ctx['can_upload_file'])) {
|
||||
// media_buttons handle uploads; keep toolbar clean.
|
||||
}
|
||||
$editor_settings = [
|
||||
'textarea_name' => '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);
|
||||
?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($ctx['can_hide']) || !empty($ctx['can_pay'])) : ?>
|
||||
<div class="aksrt-submit-shortcode-tools">
|
||||
<?php if (!empty($ctx['can_hide'])) : ?>
|
||||
<button type="button" class="but" data-insert-shortcode="hide" title="<?php esc_attr_e('插入隐藏内容', 'aksrt'); ?>">
|
||||
<i class="fas fa-eye-slash"></i> <?php esc_html_e('隐藏内容', 'aksrt'); ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($ctx['can_pay'])) : ?>
|
||||
<button type="button" class="but" data-insert-shortcode="payshow" title="<?php esc_attr_e('插入付费可见内容', 'aksrt'); ?>">
|
||||
<i class="fas fa-lock"></i> <?php esc_html_e('付费可见', 'aksrt'); ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="aksrt-submit-status-row">
|
||||
<span class="aksrt-submit-view-btn">
|
||||
<?php if ($edit_post) : ?>
|
||||
<a class="but c-blue" target="_blank" href="<?php echo esc_url(get_permalink($edit_post)); ?>">
|
||||
<i class="fa fa-file-text-o"></i> <?php esc_html_e('预览文章', 'aksrt'); ?>
|
||||
</a>
|
||||
<?php if (current_user_can('manage_options')) : ?>
|
||||
<a class="but c-yellow" href="<?php echo esc_url(get_edit_post_link($edit_post)); ?>">
|
||||
<?php esc_html_e('后台编辑', 'aksrt'); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php elseif (!empty($ctx['draft_count']) && $ctx['user_id'] > 0) : ?>
|
||||
<?php
|
||||
$draft_url = function_exists('aksrt_get_user_home_url')
|
||||
? add_query_arg(['tab' => 'post', 'post_status' => 'draft'], aksrt_get_user_home_url($ctx['user_id']))
|
||||
: '';
|
||||
?>
|
||||
<?php if ($draft_url !== '') : ?>
|
||||
<a class="but" target="_blank" href="<?php echo esc_url($draft_url); ?>">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %d: draft count */
|
||||
esc_html__('您已有 %d 篇草稿 点击查看', 'aksrt'),
|
||||
(int) $ctx['draft_count']
|
||||
);
|
||||
?>
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<span class="aksrt-submit-modified-time">
|
||||
<?php if (!empty($ctx['post_modified'])) : ?>
|
||||
<span class="badg"><?php echo esc_html(sprintf(__('最后保存:%s', 'aksrt'), $ctx['post_modified'])); ?></span>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* Cover featured editor box.
|
||||
*
|
||||
* @var array $ctx
|
||||
* @package AKSRT
|
||||
*/
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (empty($ctx['settings']['allow_cover'])) {
|
||||
return;
|
||||
}
|
||||
if (empty($ctx['can_cover_image']) && empty($ctx['can_cover_slide']) && empty($ctx['can_cover_video'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$args = $ctx['featured_args'] ?? [];
|
||||
$type = (string) ($args['type'] ?? '');
|
||||
$data = $args['data'] ?? [];
|
||||
$can_image = !empty($ctx['can_cover_image']);
|
||||
$can_slide = !empty($ctx['can_cover_slide']);
|
||||
$can_video = !empty($ctx['can_cover_video']);
|
||||
?>
|
||||
<div class="aksrt-submit-featured featured-edit"
|
||||
data-featured-args="<?php echo esc_attr(wp_json_encode($args)); ?>"
|
||||
data-can-image="<?php echo $can_image ? '1' : '0'; ?>"
|
||||
data-can-slide="<?php echo $can_slide ? '1' : '0'; ?>"
|
||||
data-can-video="<?php echo $can_video ? '1' : '0'; ?>">
|
||||
<div class="aksrt-submit-featured__preview" data-featured-preview>
|
||||
<?php if ($type === 'image' && !empty($data['url'])) : ?>
|
||||
<img src="<?php echo esc_url((string) $data['url']); ?>" alt="">
|
||||
<?php elseif ($type === 'slide' && is_array($data) && !empty($data[0]['url'])) : ?>
|
||||
<img src="<?php echo esc_url((string) $data[0]['url']); ?>" alt="">
|
||||
<span class="aksrt-submit-featured__badge"><i class="fas fa-images"></i></span>
|
||||
<?php elseif ($type === 'video') : ?>
|
||||
<?php if (!empty($data['pic'])) : ?>
|
||||
<img src="<?php echo esc_url((string) $data['pic']); ?>" alt="">
|
||||
<?php else : ?>
|
||||
<div class="aksrt-submit-featured__video-placeholder"><i class="fas fa-play"></i></div>
|
||||
<?php endif; ?>
|
||||
<span class="aksrt-submit-featured__badge"><i class="fas fa-video"></i></span>
|
||||
<?php else : ?>
|
||||
<div class="aksrt-submit-featured__empty">
|
||||
<i class="fas fa-image"></i>
|
||||
<span><?php esc_html_e('设置文章封面', 'aksrt'); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="aksrt-submit-featured__actions">
|
||||
<?php if ($can_image) : ?>
|
||||
<button type="button" class="but" data-featured-action="image">
|
||||
<i class="fas fa-image"></i> <?php esc_html_e('图片', 'aksrt'); ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if ($can_slide) : ?>
|
||||
<button type="button" class="but" data-featured-action="slide">
|
||||
<i class="fas fa-images"></i> <?php esc_html_e('幻灯片', 'aksrt'); ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if ($can_video) : ?>
|
||||
<button type="button" class="but" data-featured-action="video">
|
||||
<i class="fas fa-video"></i> <?php esc_html_e('视频', 'aksrt'); ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<button type="button" class="but c-red" data-featured-action="close" <?php echo $type === '' ? 'hidden' : ''; ?>>
|
||||
<i class="fas fa-trash"></i> <?php esc_html_e('清除', 'aksrt'); ?>
|
||||
</button>
|
||||
</div>
|
||||
<input type="hidden" name="featured_type" value="<?php echo esc_attr($type); ?>" data-featured-type>
|
||||
<input type="hidden" name="featured_data" value="<?php echo esc_attr(wp_json_encode(['type' => $type, 'data' => $data])); ?>" data-featured-json>
|
||||
</div>
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
/**
|
||||
* Pay content sidebar card.
|
||||
*
|
||||
* @var array $ctx
|
||||
* @package AKSRT
|
||||
*/
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (empty($ctx['can_pay'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$settings = $ctx['settings'];
|
||||
$pay = is_array($ctx['pay_meta'] ?? null) ? $ctx['pay_meta'] : [];
|
||||
$enabled = !empty($pay['pay_type']) && ($pay['pay_type'] !== 'no');
|
||||
$modo = (($pay['pay_modo'] ?? '0') === 'points') ? 'points' : '0';
|
||||
$points_on = !empty($ctx['points_enabled']);
|
||||
$vip_manual = !empty($settings['pay_vip_price_s']);
|
||||
$vip1_on = !empty($ctx['vip']['vip1_enabled']);
|
||||
$vip2_on = !empty($ctx['vip']['vip2_enabled']);
|
||||
$vip1_name = (string) ($ctx['vip']['vip1_name'] ?? 'VIP1');
|
||||
$vip2_name = (string) ($ctx['vip']['vip2_name'] ?? 'VIP2');
|
||||
$downloads = is_array($pay['downloads'] ?? null) ? $pay['downloads'] : [];
|
||||
?>
|
||||
<div class="aksrt-submit-panel aksrt-submit-pay dependency-box" data-pay-box>
|
||||
<div class="aksrt-submit-panel__title"><?php esc_html_e('付费内容', 'aksrt'); ?></div>
|
||||
<div class="aksrt-submit-panel__body">
|
||||
<div class="aksrt-submit-pay__row">
|
||||
<span><?php esc_html_e('内容付费', 'aksrt'); ?></span>
|
||||
<label class="aksrt-switch">
|
||||
<input type="checkbox" name="pay_enabled" value="1" <?php checked($enabled); ?> data-pay-toggle>
|
||||
<span class="aksrt-switch__ui"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-submit-pay__body" data-pay-fields <?php echo $enabled ? '' : 'hidden'; ?>>
|
||||
<?php if ($points_on) : ?>
|
||||
<div class="aksrt-submit-pay__row">
|
||||
<span><?php esc_html_e('支付类型', 'aksrt'); ?></span>
|
||||
<div class="aksrt-submit-segmented">
|
||||
<button type="button" class="but <?php echo $modo !== 'points' ? 'active' : ''; ?>" data-pay-modo="0"><?php esc_html_e('现金', 'aksrt'); ?></button>
|
||||
<button type="button" class="but <?php echo $modo === 'points' ? 'active' : ''; ?>" data-pay-modo="points"><?php esc_html_e('积分', 'aksrt'); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<input type="hidden" name="aksrt_pay[pay_modo]" value="<?php echo esc_attr($modo); ?>" data-pay-modo-input>
|
||||
<input type="hidden" name="aksrt_pay[pay_type]" value="<?php echo esc_attr((string) ($pay['pay_type'] ?? 'reading')); ?>" data-pay-type-input>
|
||||
|
||||
<div data-pay-cash <?php echo $modo === 'points' ? 'hidden' : ''; ?>>
|
||||
<label class="aksrt-submit-field">
|
||||
<span><?php esc_html_e('设置价格', 'aksrt'); ?></span>
|
||||
<input type="number" step="0.01" min="0" name="aksrt_pay[price]" class="aksrt-form-input" value="<?php echo esc_attr((string) ($pay['price'] ?? '')); ?>">
|
||||
</label>
|
||||
<?php if ($vip_manual && $vip1_on) : ?>
|
||||
<label class="aksrt-submit-field">
|
||||
<span><?php echo esc_html(sprintf(__('%s 价格', 'aksrt'), $vip1_name)); ?></span>
|
||||
<input type="number" step="0.01" min="0" name="aksrt_pay[vip1_price]" class="aksrt-form-input" value="<?php echo esc_attr((string) ($pay['vip1_price'] ?? '')); ?>">
|
||||
</label>
|
||||
<?php endif; ?>
|
||||
<?php if ($vip_manual && $vip2_on) : ?>
|
||||
<label class="aksrt-submit-field">
|
||||
<span><?php echo esc_html(sprintf(__('%s 价格', 'aksrt'), $vip2_name)); ?></span>
|
||||
<input type="number" step="0.01" min="0" name="aksrt_pay[vip2_price]" class="aksrt-form-input" value="<?php echo esc_attr((string) ($pay['vip2_price'] ?? '')); ?>">
|
||||
</label>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div data-pay-points <?php echo $modo !== 'points' ? 'hidden' : ''; ?>>
|
||||
<label class="aksrt-submit-field">
|
||||
<span><?php esc_html_e('设置积分', 'aksrt'); ?></span>
|
||||
<input type="number" step="1" min="0" name="aksrt_pay[points_price]" class="aksrt-form-input" value="<?php echo esc_attr((string) ($pay['points_price'] ?? '')); ?>">
|
||||
</label>
|
||||
<?php if ($vip_manual && $vip1_on) : ?>
|
||||
<label class="aksrt-submit-field">
|
||||
<span><?php echo esc_html(sprintf(__('%s 积分', 'aksrt'), $vip1_name)); ?></span>
|
||||
<input type="number" step="1" min="0" name="aksrt_pay[vip1_points]" class="aksrt-form-input" value="<?php echo esc_attr((string) ($pay['vip1_points'] ?? '')); ?>">
|
||||
</label>
|
||||
<?php endif; ?>
|
||||
<?php if ($vip_manual && $vip2_on) : ?>
|
||||
<label class="aksrt-submit-field">
|
||||
<span><?php echo esc_html(sprintf(__('%s 积分', 'aksrt'), $vip2_name)); ?></span>
|
||||
<input type="number" step="1" min="0" name="aksrt_pay[vip2_points]" class="aksrt-form-input" value="<?php echo esc_attr((string) ($pay['vip2_points'] ?? '')); ?>">
|
||||
</label>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($ctx['can_pay_download'])) : ?>
|
||||
<div class="aksrt-submit-pay-downloads" data-pay-downloads>
|
||||
<div class="aksrt-submit-pay__row">
|
||||
<strong><?php esc_html_e('付费下载', 'aksrt'); ?></strong>
|
||||
<button type="button" class="but hollow c-blue" data-pay-download-add>
|
||||
<i class="fas fa-plus"></i> <?php esc_html_e('添加资源', 'aksrt'); ?>
|
||||
</button>
|
||||
</div>
|
||||
<div class="aksrt-submit-pay-downloads__list" data-pay-download-list>
|
||||
<?php foreach ($downloads as $i => $row) :
|
||||
$url = (string) ($row['url'] ?? '');
|
||||
$label = (string) ($row['label'] ?? '');
|
||||
$code = (string) ($row['extract_code'] ?? '');
|
||||
?>
|
||||
<div class="aksrt-submit-pay-downloads__item" data-pay-download-item>
|
||||
<input type="url" class="aksrt-form-input" name="aksrt_pay[downloads][<?php echo (int) $i; ?>][url]" value="<?php echo esc_attr($url); ?>" placeholder="<?php esc_attr_e('资源链接', 'aksrt'); ?>">
|
||||
<input type="text" class="aksrt-form-input" name="aksrt_pay[downloads][<?php echo (int) $i; ?>][label]" value="<?php echo esc_attr($label); ?>" placeholder="<?php esc_attr_e('名称', 'aksrt'); ?>">
|
||||
<input type="text" class="aksrt-form-input" name="aksrt_pay[downloads][<?php echo (int) $i; ?>][extract_code]" value="<?php echo esc_attr($code); ?>" placeholder="<?php esc_attr_e('提取码/备注', 'aksrt'); ?>">
|
||||
<button type="button" class="but c-red cir" data-pay-download-remove title="<?php esc_attr_e('删除', 'aksrt'); ?>"><i class="fas fa-trash"></i></button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<template data-pay-download-tpl>
|
||||
<div class="aksrt-submit-pay-downloads__item" data-pay-download-item>
|
||||
<input type="url" class="aksrt-form-input" data-name="url" placeholder="<?php esc_attr_e('资源链接', 'aksrt'); ?>">
|
||||
<input type="text" class="aksrt-form-input" data-name="label" placeholder="<?php esc_attr_e('名称', 'aksrt'); ?>">
|
||||
<input type="text" class="aksrt-form-input" data-name="extract_code" placeholder="<?php esc_attr_e('提取码/备注', 'aksrt'); ?>">
|
||||
<button type="button" class="but c-red cir" data-pay-download-remove title="<?php esc_attr_e('删除', 'aksrt'); ?>"><i class="fas fa-trash"></i></button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<p class="aksrt-submit-hint"><?php esc_html_e('请确保已经在文中添加了付费可见的隐藏内容', 'aksrt'); ?></p>
|
||||
</div>
|
||||
|
||||
<p class="aksrt-submit-hint" data-pay-off-hint <?php echo $enabled ? 'hidden' : ''; ?>>
|
||||
<?php esc_html_e('如果您在文章中添加了付费可见的隐藏内容,请在此设置付费功能', 'aksrt'); ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Submit actions + drafts list.
|
||||
*
|
||||
* @var array $ctx
|
||||
* @package AKSRT
|
||||
*/
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$settings = $ctx['settings'];
|
||||
$is_can_new = !empty($ctx['is_can_new']);
|
||||
$edit_post = $ctx['edit_post'];
|
||||
$status = (string) $ctx['post_status'];
|
||||
$can_draft = !empty($ctx['user_id']) && empty($ctx['is_guest']) && !empty($settings['draft_save']);
|
||||
if ($edit_post && in_array($status, ['publish', 'pending'], true)) {
|
||||
// Still allow draft button? zibll hides draft for publish/pending.
|
||||
$can_draft = false;
|
||||
}
|
||||
?>
|
||||
<div class="aksrt-submit-panel">
|
||||
<div class="aksrt-submit-panel__body aksrt-submit-actions">
|
||||
<p class="aksrt-submit-ready"><?php esc_html_e('Are you ready', 'aksrt'); ?></p>
|
||||
|
||||
<input type="hidden" name="posts_id" value="<?php echo esc_attr((string) (int) $ctx['edit_id']); ?>">
|
||||
<input type="hidden" name="status" id="submit-status" value="pending">
|
||||
<input type="hidden" name="_nonce" value="<?php echo esc_attr(wp_create_nonce('aksrt_submit')); ?>">
|
||||
|
||||
<?php if (!$is_can_new) : ?>
|
||||
<p class="aksrt-submit-hint"><?php esc_html_e('暂无发布权限', 'aksrt'); ?></p>
|
||||
<?php else : ?>
|
||||
<?php if (!empty($ctx['is_guest'])) : ?>
|
||||
<p class="aksrt-submit-hint"><?php esc_html_e('您当前未登录,不能保存草稿,文章提交后不可再修改!', 'aksrt'); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($settings['captcha']) && function_exists('aksrt_render_captcha')) : ?>
|
||||
<div class="aksrt-submit-captcha">
|
||||
<?php aksrt_render_captcha('submit'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="aksrt-submit-actions__btns">
|
||||
<?php if ($can_draft) : ?>
|
||||
<button type="button" class="but jb-green aksrt-submit-btn" data-action="posts_draft">
|
||||
<i class="fa fa-dot-circle-o"></i> <?php esc_html_e('保存草稿', 'aksrt'); ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<button type="button" class="but jb-blue aksrt-submit-btn" data-action="posts_save" id="aksrt-submit-btn">
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: 发布|审核|保存 */
|
||||
esc_html__('提交%s', 'aksrt'),
|
||||
esc_html((string) $ctx['submit_label'])
|
||||
);
|
||||
?>
|
||||
</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($ctx['user_drafts'])) : ?>
|
||||
<div class="aksrt-submit-panel aksrt-submit-drafts">
|
||||
<div class="aksrt-submit-panel__title">
|
||||
<i class="fas fa-file-alt"></i> <?php esc_html_e('我的草稿', 'aksrt'); ?>
|
||||
</div>
|
||||
<div class="aksrt-submit-panel__body">
|
||||
<?php foreach ($ctx['user_drafts'] as $draft) :
|
||||
$edit_url = function_exists('aksrt_get_new_post_url')
|
||||
? aksrt_get_new_post_url((int) $draft->ID)
|
||||
: add_query_arg('edit', $draft->ID, get_permalink());
|
||||
?>
|
||||
<div class="aksrt-submit-drafts__item" data-draft-id="<?php echo esc_attr((string) $draft->ID); ?>">
|
||||
<a href="<?php echo esc_url($edit_url); ?>" class="aksrt-submit-drafts__link">
|
||||
<span class="aksrt-submit-drafts__name"><?php echo esc_html($draft->post_title ?: __('无标题', 'aksrt')); ?></span>
|
||||
<time><?php echo esc_html(get_the_modified_date('Y-m-d H:i', $draft)); ?></time>
|
||||
</a>
|
||||
<button type="button" class="aksrt-submit-drafts__delete" data-draft-id="<?php echo esc_attr((string) $draft->ID); ?>" title="<?php esc_attr_e('删除草稿', 'aksrt'); ?>">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/**
|
||||
* Sidebar: guest + terms + tags.
|
||||
*
|
||||
* @var array $ctx
|
||||
* @package AKSRT
|
||||
*/
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$settings = $ctx['settings'];
|
||||
$term_helper = get_template_directory() . '/templates/parts/submit/term-checks.php';
|
||||
if (file_exists($term_helper)) {
|
||||
require_once $term_helper;
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if (!empty($ctx['is_guest'])) : ?>
|
||||
<div class="aksrt-submit-panel">
|
||||
<div class="aksrt-submit-panel__title"><?php esc_html_e('用户信息', 'aksrt'); ?></div>
|
||||
<div class="aksrt-submit-panel__body">
|
||||
<p class="aksrt-submit-hint"><?php esc_html_e('请输入昵称', 'aksrt'); ?></p>
|
||||
<input class="aksrt-form-input" type="text" name="user_name" placeholder="<?php esc_attr_e('请输入昵称', 'aksrt'); ?>" required>
|
||||
<p class="aksrt-submit-hint"><?php esc_html_e('请输入您的联系方式', 'aksrt'); ?></p>
|
||||
<input class="aksrt-form-input" type="text" name="contact_details" placeholder="<?php esc_attr_e('输入联系方式', 'aksrt'); ?>">
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="aksrt-submit-panel">
|
||||
<div class="aksrt-submit-panel__title"><?php esc_html_e('文章参数', 'aksrt'); ?></div>
|
||||
<div class="aksrt-submit-panel__body term-select-box">
|
||||
|
||||
<?php if (!empty($settings['allow_category'])) : ?>
|
||||
<details class="aksrt-submit-drop" open>
|
||||
<summary>
|
||||
<span><i class="fa fa-folder-open-o"></i> <?php esc_html_e('分类', 'aksrt'); ?></span>
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</summary>
|
||||
<div class="aksrt-submit-drop__body">
|
||||
<?php
|
||||
if (!empty($ctx['categories'])) {
|
||||
aksrt_submit_render_term_checks($ctx['categories'], array_map('intval', $ctx['current_cats']), 'category');
|
||||
} else {
|
||||
echo '<p class="aksrt-submit-hint">' . esc_html__('暂无可选择的分类', 'aksrt') . '</p>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</details>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($settings['topics_s']) && !empty($ctx['topics_terms'])) : ?>
|
||||
<details class="aksrt-submit-drop">
|
||||
<summary>
|
||||
<span><i class="fa fa-cube"></i> <?php esc_html_e('专题', 'aksrt'); ?></span>
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</summary>
|
||||
<div class="aksrt-submit-drop__body">
|
||||
<?php aksrt_submit_render_term_checks($ctx['topics_terms'], array_map('intval', $ctx['current_topics']), 'topics', 'topics'); ?>
|
||||
</div>
|
||||
</details>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($settings['custom_filter_s']) && !empty($ctx['custom_dimensions'])) : ?>
|
||||
<?php foreach ($ctx['custom_dimensions'] as $dim) :
|
||||
$key = sanitize_key((string) ($dim['key'] ?? ''));
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
$name = (string) ($dim['name'] ?? $key);
|
||||
$vals = is_array($dim['vals'] ?? null) ? $dim['vals'] : [];
|
||||
$cur = $ctx['current_cf'][$key] ?? [];
|
||||
if (!is_array($cur)) {
|
||||
$cur = $cur === '' || $cur === null ? [] : [$cur];
|
||||
}
|
||||
$cur = array_map('strval', $cur);
|
||||
?>
|
||||
<details class="aksrt-submit-drop">
|
||||
<summary>
|
||||
<span><i class="fa fa-filter"></i> <?php echo esc_html($name); ?></span>
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</summary>
|
||||
<div class="aksrt-submit-drop__body aksrt-submit-tags">
|
||||
<?php foreach ($vals as $val) :
|
||||
$vk = is_array($val) ? (string) ($val['key'] ?? '') : (string) $val;
|
||||
$vn = is_array($val) ? (string) ($val['name'] ?? $vk) : (string) $val;
|
||||
if ($vk === '') {
|
||||
continue;
|
||||
}
|
||||
$active = in_array($vk, $cur, true);
|
||||
?>
|
||||
<label class="aksrt-submit-tag <?php echo $active ? 'is-active' : ''; ?>">
|
||||
<input type="checkbox" name="cf_<?php echo esc_attr($key); ?>[]" value="<?php echo esc_attr($vk); ?>" <?php checked($active); ?>>
|
||||
<span><?php echo esc_html($vn); ?></span>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</details>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($settings['allow_tags'])) : ?>
|
||||
<div class="aksrt-submit-field">
|
||||
<div class="aksrt-submit-field__label">
|
||||
<?php esc_html_e('标签', 'aksrt'); ?>
|
||||
<?php if (!empty($ctx['ai_available'])) : ?>
|
||||
<button type="button" class="aksrt-ai-assist-btn" data-ai-action="tags" hidden><?php esc_html_e('AI 建议标签', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<p class="aksrt-submit-hint"><?php esc_html_e('填写文章的标签,每个标签用逗号隔开', 'aksrt'); ?></p>
|
||||
<textarea class="aksrt-form-input" rows="3" name="tags" placeholder="<?php esc_attr_e('输入文章标签', 'aksrt'); ?>"><?php echo esc_textarea((string) $ctx['current_tags']); ?></textarea>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($settings['allow_excerpt'])) : ?>
|
||||
<div class="aksrt-submit-field">
|
||||
<div class="aksrt-submit-field__label">
|
||||
<?php esc_html_e('摘要', 'aksrt'); ?>
|
||||
<?php if (!empty($ctx['ai_available'])) : ?>
|
||||
<button type="button" class="aksrt-ai-assist-btn" data-ai-action="summary" hidden><?php esc_html_e('AI 生成摘要', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<textarea class="aksrt-form-input" rows="3" name="excerpt" maxlength="300" placeholder="<?php esc_attr_e('可选:文章摘要', 'aksrt'); ?>"><?php echo esc_textarea((string) $ctx['current_excerpt']); ?></textarea>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* Hierarchical multi-check term list.
|
||||
*
|
||||
* @param WP_Term[] $terms
|
||||
* @param int[] $checked
|
||||
* @param string $name
|
||||
* @param string $taxonomy
|
||||
* @param int $parent
|
||||
* @param int $depth
|
||||
*/
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_submit_render_term_checks')) :
|
||||
function aksrt_submit_render_term_checks(array $terms, array $checked, string $name, string $taxonomy = 'category', int $parent = 0, int $depth = 0): void {
|
||||
$children_map = [];
|
||||
$roots = [];
|
||||
foreach ($terms as $term) {
|
||||
if (!$term instanceof WP_Term) {
|
||||
continue;
|
||||
}
|
||||
$pid = (int) $term->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 '<div' . $pad . '><label class="aksrt-submit-check">';
|
||||
echo '<input type="checkbox" name="' . esc_attr($name) . '[]" value="' . esc_attr((string) $tid) . '"' . (in_array($tid, $checked, true) ? ' checked' : '') . '>';
|
||||
echo '<span>' . esc_html($term->name) . '</span></label></div>';
|
||||
$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 '<div><label class="aksrt-submit-check">';
|
||||
echo '<input type="checkbox" name="' . esc_attr($name) . '[]" value="' . esc_attr((string) $tid) . '"' . (in_array($tid, $checked, true) ? ' checked' : '') . '>';
|
||||
echo '<span>' . esc_html($term->name) . '</span></label></div>';
|
||||
}
|
||||
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;
|
||||
Reference in New Issue
Block a user