diff --git a/assets/css/frontend/nav/header-userbox.css b/assets/css/frontend/nav/header-userbox.css new file mode 100644 index 00000000..9db422c2 --- /dev/null +++ b/assets/css/frontend/nav/header-userbox.css @@ -0,0 +1,96 @@ +/** + * Header user-box density (check-in / wallet / payvip / signout modal). + * + * @package AKSRT + */ + +.aksrt-header-wallet { + display: flex; + gap: 10px; + justify-content: space-between; + padding: 8px 10px; + border-radius: 8px; + background: var(--surface-2, rgba(0, 0, 0, 0.04)); + color: inherit; + text-decoration: none; + font-size: 12px; +} + +.aksrt-header-wallet__item b { + margin-left: 2px; +} + +.aksrt-header-checkin { + width: 100%; + border: 0; + border-radius: 6px; + padding: 8px 10px; + cursor: pointer; + background: rgba(255, 193, 7, 0.18); + color: #b78103; +} + +.aksrt-header-checkin.is-done { + opacity: 0.75; + cursor: default; +} + +.aksrt-header-payvip .payvip-icon, +.aksrt-header-payvip .but { + display: block; + text-align: center; + text-decoration: none; + padding: 8px 10px; + border-radius: 6px; + background: linear-gradient(90deg, #ff6b6b, #f04494); + color: #fff; +} + +.aksrt-header-badges { + flex-wrap: wrap; + gap: 4px; +} + +#modal_signout.modal { + position: fixed; + inset: 0; + z-index: 1100; + display: none; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.45); + padding: 16px; +} + +#modal_signout.modal.is-open, +#modal_signout.modal.show { + display: flex; +} + +#modal_signout .modal-content { + background: var(--main-bg-color, #fff); + border-radius: 10px; + min-width: 280px; + max-width: 92vw; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.18); +} + +#modal_signout .but.c-red { + background: #e74c3c; + color: #fff; + text-decoration: none; + padding: 8px 14px; + border-radius: 6px; +} + +#modal_signout .but { + border: 0; + background: var(--surface-2, #f0f0f0); + padding: 8px 14px; + border-radius: 6px; + cursor: pointer; +} + +.user-dropdown .social_loginbar { + padding: 0 10px 12px; +} diff --git a/assets/css/routes/links.css b/assets/css/routes/links.css new file mode 100644 index 00000000..573129e2 --- /dev/null +++ b/assets/css/routes/links.css @@ -0,0 +1,459 @@ +/** + * AKSRT 网址导航 — zibll links page shell/styles. + * + * @package AKSRT + */ + +/* ---- cards ---- */ +.links-cat-header { + padding: 0 15px 10px; +} + +.links-box:not(.zib-widget):not(.links-style-simple):not(.links-style-image) { + margin: -5px; + display: flex; + flex-wrap: wrap; +} + +.links-box .links-card { + padding: 10px 4px 10px 8px; + min-width: 0; + margin: 5px; + width: calc(50% - 10px); + box-sizing: border-box; +} + +.links-style-bigcard { + display: flex; + flex-wrap: wrap; +} + +.links-box .links-card.big-card { + padding: 20px 10px 15px; + text-align: center; +} + +.links-box:not(.zib-widget) .links-card { + background: var(--main-bg-color, var(--surface-1, #fff)); +} + +.author-minicard.links-card { + border-radius: 8px; + overflow: hidden; + box-shadow: 0 1px 4px var(--main-shadow, rgba(0, 0, 0, 0.06)); + transition: transform 0.15s ease, box-shadow 0.15s ease; +} + +.author-minicard.links-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 14px var(--main-shadow, rgba(0, 0, 0, 0.1)); +} + +.links-card a { + display: block; + color: inherit; + text-decoration: none; +} + +.links-card .list-inline { + display: flex; + align-items: center; + gap: 10px; + list-style: none; + margin: 0; + padding: 0; +} + +.links-card .list-inline > li { + margin: 0; +} + +.link-img { + --this-size: 42px; + width: var(--this-size); + height: var(--this-size); + flex-shrink: 0; +} + +.link-img img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 4px; + display: block; +} + +.links-card.big-card .link-img { + --this-size: 58px; + margin: 0 auto; +} + +.links-card.big-card .link-img img { + border-radius: 100px; +} + +.links-style-image { + --link-img-size: 60px; + --this-margin: 10px; + display: flex; + flex-wrap: wrap; +} + +.links-style-image .link-img.link-only-img { + margin: var(--this-margin); + --this-size: var(--link-img-size); +} + +.links-style-image .link-img.link-only-img img { + border-radius: 8px; +} + +/* ---- fixed category nav ---- */ +.fixed-wrap .fixed-wrap-nav.zib-widget, +.link-nav-box .zib-widget { + padding: 8px 15px; +} + +.fixed-wrap-nav .tab-nav-theme { + border-bottom: none; + padding: 0; + list-style: none; + margin: 0; +} + +.link-nav-box .nav > li > a { + display: block; + padding: 6px; + color: var(--muted-2-color, #888); + text-decoration: none; +} + +.link-nav-box .nav > li > a:hover, +.link-nav-box .nav > .active > a { + color: var(--focus-color, var(--color-primary, #f04494)); +} + +.link-nav-submit, +.link-cat-submit { + margin-top: 10px; +} + +.link-cat-submit { + display: none; +} + +.links-page-container .title-theme { + font-weight: 600; + font-size: 1.15em; + margin-bottom: 6px; +} + +/* ---- search cover ---- */ +.link-page-search-cover.page-cover { + position: relative; + overflow: hidden; + padding-bottom: calc(25% + 100px); + margin-bottom: 20px; +} + +.link-page-search-cover .fit-cover { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; +} + +.link-page-search-cover .header-slider-search { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: min(560px, 92%); + z-index: 2; +} + +.link-page-search-cover .tab-nav-theme { + display: flex; + gap: 4px; + list-style: none; + padding: 0; + margin: 0 0 8px; + justify-content: center; +} + +.link-page-search-cover .tab-nav-theme li a { + opacity: 0.7; + padding: 0 10px; + color: #fff; + text-decoration: none; + cursor: pointer; +} + +.link-page-search-cover .tab-nav-theme li a:hover, +.link-page-search-cover .tab-nav-theme li.active a { + color: #fff; + opacity: 1; + font-weight: 600; +} + +.link-page-search-cover .tab-pane { + display: none; +} + +.link-page-search-cover .tab-pane.active, +.link-page-search-cover .tab-pane.in.active { + display: block; +} + +.link-page-search-cover .line-form { + position: relative; + background: rgba(255, 255, 255, 0.92); + border-radius: 8px; + padding: 8px 12px; +} + +.link-page-search-cover .line-form-input { + width: 100%; + border: 0; + background: transparent; + outline: none; + font-size: 15px; + padding: 8px 36px 8px 4px; +} + +.link-page-search-cover .abs-right { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); +} + +.link-page-search-cover .abs-right button { + border: 0; + background: transparent; + cursor: pointer; + color: var(--muted-2-color, #666); +} + +.link-page-search-cover .header-slider-search-more { + color: #fff; + text-shadow: 0 1px 4px rgba(0, 0, 0, 0.35); + margin-bottom: 8px; +} + +/* ---- submit modal ---- */ +#submit-links-modal.modal { + position: fixed; + inset: 0; + z-index: 1050; + display: none; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.45); + padding: 16px; +} + +#submit-links-modal.modal.is-open, +#submit-links-modal.modal.show { + display: flex; +} + +#submit-links-modal .modal-dialog { + width: min(520px, 100%); + max-height: 90vh; + overflow: auto; +} + +#submit-links-modal .modal-content { + background: var(--main-bg-color, #fff); + border-radius: 10px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.18); +} + +#submit-links-modal .modal-body { + padding: 20px; +} + +#submit-links-modal .close { + float: right; + border: 0; + background: transparent; + cursor: pointer; + font-size: 18px; +} + +#submit-links-modal .preview-square { + width: 72px; + height: 72px; + border: 1px dashed var(--main-border-color, #ddd); + border-radius: 8px; + overflow: hidden; + display: inline-block; +} + +#submit-links-modal .preview-square img { + width: 100%; + height: 100%; + object-fit: cover; +} + +#submit-links-modal .hide { + display: none !important; +} + +#submit-links-modal .form-control { + width: 100%; + padding: 8px 10px; + border: 1px solid var(--main-border-color, #ddd); + border-radius: 6px; + box-sizing: border-box; +} + +#submit-links-modal .row.gutters-5 { + display: flex; + flex-wrap: wrap; + margin: 0 -5px; +} + +#submit-links-modal .col-sm-6, +#submit-links-modal .col-sm-12 { + padding: 0 5px; + box-sizing: border-box; + width: 100%; +} + +@media (min-width: 576px) { + #submit-links-modal .col-sm-6 { + width: 50%; + } +} + +/* ---- responsive grid ---- */ +@media (min-width: 992px) { + .links-box .links-card { + width: calc(25% - 10px); + } + + .site-layout-1 .links-box .links-card, + .fluid-widget-wrap .links-box .links-card { + width: calc(20% - 10px); + } + + .fluid-widget-wrap .links-box.links-style-bigcard .links-card, + .site-layout-1 .links-box.links-style-bigcard .links-card { + width: calc(16.6665% - 10px); + } + + .sidebar .links-box .links-card { + width: calc(50% - 10px); + } + + .fixed-wrap { + position: relative; + --fixed-width: 120px; + } + + .fixed-wrap > .fixed-wrap-nav { + position: absolute; + width: var(--fixed-width); + } + + .fixed-wrap > .fixed-wrap-nav + .fixed-wrap-content { + margin-left: calc(var(--fixed-width) + 15px); + } + + .fixed-wrap-nav.affix { + position: sticky; + top: 80px; + } + + .fixed-wrap .tab-nav-theme.mini-scrollbar { + display: flex; + flex-direction: column; + overflow: auto; + max-height: 70vh; + text-align: center; + border-bottom: none; + padding: 0; + margin: 0; + } + + .fixed-wrap .tab-nav-theme.mini-scrollbar > li > a { + padding: 6px; + margin: 1px 0; + display: block; + font-size: 16px; + color: var(--muted-2-color, #888); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .fixed-wrap .tab-nav-theme.mini-scrollbar > li > a:hover { + color: var(--main-color, var(--color-primary)); + } + + .fixed-wrap .tab-nav-theme.mini-scrollbar > li.active > a { + color: var(--focus-color, var(--color-primary)); + font-weight: 500; + background: color-mix(in srgb, var(--focus-color, var(--color-primary)) 12%, transparent); + border-radius: 6px; + } + + .link-nav-submit { + display: block; + } +} + +@media (max-width: 991px) { + .link-nav-submit { + display: none; + } + + .link-cat-submit { + display: block; + } + + .link-nav-box > .zib-widget { + background: transparent; + box-shadow: none; + padding: 0; + margin: 0; + } + + .link-nav-box .nav { + display: flex; + gap: 8px; + overflow-x: auto; + margin: 0 0 10px; + padding: 0; + list-style: none; + } + + .link-nav-box .nav > li { + display: inline-block; + flex: 0 0 auto; + } + + .link-nav-box .nav > li > a { + padding: 2px 6px; + font-size: 1.05em; + white-space: nowrap; + } +} + +@media (max-width: 640px) { + .links-box .links-card { + width: calc(50% - 10px); + } + + .link-page-search-cover .tab-nav-theme li a { + padding: 0 3px; + font-size: 14px; + } + + .links-page-container .fixed-wrap { + overflow: hidden; + } +} diff --git a/assets/css/routes/posts-navs.css b/assets/css/routes/posts-navs.css new file mode 100644 index 00000000..fc601cb8 --- /dev/null +++ b/assets/css/routes/posts-navs.css @@ -0,0 +1,261 @@ +/** + * AKSRT 文章导航页 — zibll postsnavs shell + admin settings. + * + * @package AKSRT + */ + +/* header card */ +.page-template-posts-navs .content-wrap, +main.page-template-posts-navs { + display: flex; + flex-wrap: wrap; + gap: 20px; +} + +main.page-template-posts-navs .content-wrap { + flex: 1 1 0; + min-width: 0; +} + +main.page-template-posts-navs .sidebar { + flex: 0 0 260px; + max-width: 100%; +} + +.posts-navs .title-theme h2 { + margin: 0; + font-size: 1.15em; +} + +.posts-navs__list { + list-style: none; + margin: 0; + padding: 0; +} + +.posts-navs__item { + margin: 0 0 10px; + padding: 0; +} + +.posts-navs__item .text-ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.posts-navs__item .icon-circle { + display: inline-block; + width: 4px; + height: 4px; + border-radius: 50%; + background: var(--muted-3-color, #ccc); + margin: 0 0.55em; + vertical-align: middle; +} + +.posts-navs__item a { + color: var(--color-ink, inherit); + text-decoration: none; +} + +.posts-navs__item a:hover { + color: var(--focus-color, var(--color-primary)); +} + +.posts-navs .theme-box { + margin-bottom: 20px; +} + +.posts-navs .box-body.notop { + padding-top: 0; +} + +/* TOC sidebar filled by section-navs.js */ +.posts-nav-box .posts-nav-lists, +.posts-nav-box .aksrt-section-nav { + max-height: 400px; + padding-left: 10px; + overflow-y: auto; +} + +.posts-nav-box .zib-widget { + background: var(--main-bg-color, var(--surface-1, #fff)); + border-radius: 8px; + box-shadow: 0 1px 4px var(--main-shadow, rgba(0, 0, 0, 0.06)); + padding: 10px; +} + +.posts-nav-lists .bl { + border-left: 1px solid var(--main-border-color, #eee); + padding: 12px 10px 12px 0; + list-style: none; + margin: 0; + position: relative; +} + +.posts-nav-lists li { + position: relative; + list-style: none; +} + +.posts-nav-lists li a { + margin: 0 10px; + display: block; + border-radius: 4px; + padding: 8px; + margin-right: 20px; + color: var(--muted-2-color, #666); + text-decoration: none; +} + +.posts-nav-lists li a:hover, +.posts-nav-lists li.active a { + background: var(--main-shadow, rgba(0, 0, 0, 0.04)); + color: var(--focus-color, var(--color-primary)); +} + +.posts-nav-lists li.n-H1 a, +.posts-nav-lists li.n-H2 a { + font-weight: 700; +} + +/* settings modal */ +#post-navs-sz.modal { + position: fixed; + inset: 0; + z-index: 1050; + display: none; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.45); + padding: 16px; +} + +#post-navs-sz.modal.is-open, +#post-navs-sz.modal.show { + display: flex; +} + +#post-navs-sz .modal-dialog { + width: min(640px, 100%); + max-height: 90vh; + overflow: auto; +} + +#post-navs-sz .modal-content { + background: var(--main-bg-color, #fff); + border-radius: 10px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.18); +} + +#post-navs-sz .modal-header, +#post-navs-sz .modal-body, +#post-navs-sz .modal-footer { + padding: 16px 20px; +} + +#post-navs-sz .modal-header { + border-bottom: 1px solid var(--main-border-color, #eee); +} + +#post-navs-sz .modal-footer { + border-top: 1px solid var(--main-border-color, #eee); + text-align: right; +} + +#post-navs-sz .close { + float: right; + border: 0; + background: transparent; + cursor: pointer; +} + +#post-navs-sz .form-control { + width: 100%; + padding: 8px 10px; + border: 1px solid var(--main-border-color, #ddd); + border-radius: 6px; + box-sizing: border-box; +} + +#post-navs-sz .form-group { + margin-bottom: 14px; +} + +#post-navs-sz .row { + display: flex; + flex-wrap: wrap; + margin: 0 -8px; +} + +#post-navs-sz .col-xs-6, +#post-navs-sz .col-sm-6 { + width: 50%; + padding: 0 8px; + box-sizing: border-box; +} + +.cat-group { + height: 200px; + background: var(--body-bg-color, var(--surface-2, #f6f7f9)); + border: 1px solid var(--main-border-color, #ddd); + border-radius: 4px; + overflow: hidden; + padding: 5px; + overflow-y: auto; +} + +.sortable-cat-item { + background: var(--main-border-color, #e8e8e8); + padding: 4px 8px; + transition: 0.2s; + border-radius: 4px; + margin: 2px; + display: inline-block; + cursor: grab; + width: calc(100% - 15px); + min-width: 100px; + box-sizing: border-box; + user-select: none; +} + +.sortable-cat-item .badge { + margin-left: 4px; + background: rgba(0, 0, 0, 0.08); + color: inherit; + border-radius: 10px; + padding: 0 6px; + font-size: 12px; +} + +#cat-sortable-ok .sortable-cat-item { + background: rgba(73, 214, 253, 0.12); + color: #069cf3; +} + +.sortable-cat-item.is-dragging { + opacity: 0.5; +} + +.sortable-cat-item.sortable-selected { + background: rgba(251, 82, 82, 0.1) !important; + color: #fb5454 !important; +} + +.cat-group.is-drop-target { + outline: 2px dashed var(--focus-color, #f04494); +} + +@media (max-width: 991px) { + main.page-template-posts-navs .sidebar { + flex: 1 1 100%; + order: -1; + } + + #post-navs-sz .col-xs-6, + #post-navs-sz .col-sm-6 { + width: 100%; + margin-bottom: 12px; + } +} diff --git a/assets/css/routes/user-auth.css b/assets/css/routes/user-auth.css new file mode 100644 index 00000000..723a08e6 --- /dev/null +++ b/assets/css/routes/user-auth.css @@ -0,0 +1,89 @@ +/** + * User identity auth page styles (zibll user-auth). + * + * @package AKSRT + */ + +.aksrt-user-auth-panel { + overflow: hidden; + margin-top: 20px; +} + +.aksrt-user-auth-panel .colorful-bg.c-blue { + background: linear-gradient(135deg, #4e9bff 0%, #2f6df6 100%); + color: #fff; +} + +.aksrt-user-auth-panel .muted-2-color { + color: var(--muted-2-color, #888); +} + +.aksrt-user-auth-panel .form-control { + width: 100%; + padding: 8px 10px; + border: 1px solid var(--main-border-color, #ddd); + border-radius: 6px; + box-sizing: border-box; +} + +.aksrt-user-auth-panel .mb20 { + margin-bottom: 20px; +} + +.aksrt-user-auth-panel .mb6 { + margin-bottom: 6px; +} + +.aksrt-user-auth-panel .but.c-blue, +.aksrt-user-auth-panel button.but { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + background: var(--focus-color, #2f6df6); + color: #fff; + border: 0; + border-radius: 6px; + padding: 10px 18px; + cursor: pointer; +} + +.aksrt-user-auth-panel .btn-block { + width: 100%; +} + +.aksrt-user-auth-panel__imgs { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.aksrt-user-auth-panel__imgs .preview-item { + width: 96px; + height: 96px; + border-radius: 6px; + overflow: hidden; + border: 1px solid var(--main-border-color, #eee); +} + +.aksrt-user-auth-panel__imgs img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.aksrt-user-auth-panel .c-red { + color: #e74c3c; +} + +.page-template-user-auth .content-wrap { + display: flex; + gap: 20px; + flex-wrap: wrap; +} + +.page-template-user-auth .content-layout { + flex: 1 1 0; + min-width: 0; +} diff --git a/assets/js/header-userbox.js b/assets/js/header-userbox.js new file mode 100644 index 00000000..12f20529 --- /dev/null +++ b/assets/js/header-userbox.js @@ -0,0 +1,113 @@ +/** + * Header user-box: check-in REST + signout modal. + * + * @package AKSRT + */ +(function () { + 'use strict'; + + function restBase() { + if (window.aksrtData && window.aksrtData.restUrl) return window.aksrtData.restUrl.replace(/\/?$/, '/'); + if (window.aksrtData && window.aksrtData.siteUrl) return String(window.aksrtData.siteUrl).replace(/\/?$/, '/') + 'wp-json/'; + return '/wp-json/'; + } + + function restNonce() { + return (window.aksrtData && window.aksrtData.restNonce) || ''; + } + + function toast(msg, type) { + if (window.aksrt && typeof window.aksrt.toast === 'function') { + window.aksrt.toast(msg, type || 'info'); + return; + } + // eslint-disable-next-line no-alert + window.alert(msg); + } + + function openModal(id) { + var modal = document.getElementById(id); + if (!modal) return; + modal.classList.add('is-open', 'show'); + modal.setAttribute('aria-hidden', 'false'); + } + + function closeModal(id) { + var modal = document.getElementById(id); + if (!modal) return; + modal.classList.remove('is-open', 'show'); + modal.setAttribute('aria-hidden', 'true'); + } + + function initSignoutModal() { + document.addEventListener('click', function (e) { + var openBtn = e.target.closest('[data-target="#modal_signout"], [href="#modal_signout"]'); + if (openBtn) { + e.preventDefault(); + openModal('modal_signout'); + return; + } + var dismiss = e.target.closest('#modal_signout [data-dismiss="modal"]'); + if (dismiss) { + e.preventDefault(); + closeModal('modal_signout'); + return; + } + if (e.target && e.target.id === 'modal_signout') { + closeModal('modal_signout'); + } + }); + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') closeModal('modal_signout'); + }); + } + + function initCheckin() { + document.addEventListener('click', function (e) { + var btn = e.target.closest('[data-aksrt-checkin="1"]'); + if (!btn || btn.disabled) return; + e.preventDefault(); + btn.disabled = true; + fetch(restBase() + 'aksrt/v1/signin', { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + 'X-WP-Nonce': restNonce(), + }, + body: '{}', + }) + .then(function (r) { + return r.json().then(function (data) { + return { ok: r.ok, data: data }; + }); + }) + .then(function (res) { + if (!res.ok) { + var msg = (res.data && (res.data.message || res.data.msg)) || '签到失败'; + toast(msg, 'danger'); + btn.disabled = false; + return; + } + toast((res.data && res.data.message) || '签到成功', 'success'); + btn.classList.add('is-done'); + btn.innerHTML = ' 今日已签到'; + }) + .catch(function () { + toast('网络错误', 'danger'); + btn.disabled = false; + }); + }); + } + + function init() { + initSignoutModal(); + initCheckin(); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/assets/js/links-page.js b/assets/js/links-page.js new file mode 100644 index 00000000..c3eb37c3 --- /dev/null +++ b/assets/js/links-page.js @@ -0,0 +1,221 @@ +/** + * AKSRT 网址导航页 — search tabs, sticky cat nav, submit modal/AJAX. + * + * @package AKSRT + */ +(function () { + 'use strict'; + + function qs(sel, root) { + return (root || document).querySelector(sel); + } + + function qsa(sel, root) { + return Array.prototype.slice.call((root || document).querySelectorAll(sel)); + } + + function ajaxUrl() { + if (window.aksrtData && window.aksrtData.ajaxUrl) return window.aksrtData.ajaxUrl; + if (window.ajaxurl) return window.ajaxurl; + return '/wp-admin/admin-ajax.php'; + } + + function toast(msg, type) { + if (window.aksrt && typeof window.aksrt.toast === 'function') { + window.aksrt.toast(msg, type || 'info'); + return; + } + if (window.notyf) { + try { + window.notyf(msg, type === 'danger' ? 'danger' : ''); + return; + } catch (e) { /* ignore */ } + } + // eslint-disable-next-line no-alert + window.alert(msg); + } + + /* ---------- search engine tabs ---------- */ + function initSearchTabs() { + var cover = qs('.link-page-search-cover'); + if (!cover) return; + + qsa('.tab-nav-theme a[data-aksrt-tab-target]', cover).forEach(function (a) { + a.addEventListener('click', function (e) { + e.preventDefault(); + var target = a.getAttribute('data-aksrt-tab-target'); + if (!target) return; + qsa('.tab-nav-theme li', cover).forEach(function (li) { + li.classList.remove('active'); + }); + if (a.parentElement) a.parentElement.classList.add('active'); + qsa('.tab-pane', cover).forEach(function (pane) { + pane.classList.remove('active', 'in'); + }); + var pane = qs(target, cover); + if (pane) pane.classList.add('active', 'in'); + }); + }); + } + + /* ---------- sticky category nav highlight ---------- */ + function initCatNav() { + var wrap = qs('.links-page-container .fixed-wrap'); + if (!wrap) return; + var nav = qs('.link-nav-box', wrap); + var links = qsa('.link-nav-box a[href^="#"]', wrap); + var sections = links + .map(function (a) { + var id = a.getAttribute('href'); + return id ? document.getElementById(id.replace(/^#/, '')) : null; + }) + .filter(Boolean); + if (!links.length || !sections.length) return; + + links.forEach(function (a) { + a.addEventListener('click', function (e) { + var id = a.getAttribute('href'); + var el = id ? document.getElementById(id.replace(/^#/, '')) : null; + if (!el) return; + e.preventDefault(); + var top = el.getBoundingClientRect().top + window.pageYOffset - 90; + window.scrollTo({ top: top, behavior: 'smooth' }); + }); + }); + + function updateActive() { + var y = window.pageYOffset + 120; + var current = sections[0]; + for (var i = 0; i < sections.length; i++) { + if (sections[i].offsetTop <= y) current = sections[i]; + } + if (!current) return; + links.forEach(function (a) { + var li = a.parentElement; + if (!li) return; + if (a.getAttribute('href') === '#' + current.id) { + li.classList.add('active'); + } else { + li.classList.remove('active'); + } + }); + if (nav) nav.classList.add('affix'); + } + + window.addEventListener('scroll', updateActive, { passive: true }); + updateActive(); + } + + /* ---------- submit modal ---------- */ + function initSubmitModal() { + var modal = qs('#submit-links-modal'); + if (!modal) return; + + function openModal(e) { + if (e) e.preventDefault(); + modal.classList.add('is-open', 'show'); + modal.setAttribute('aria-hidden', 'false'); + document.documentElement.classList.add('aksrt-modal-open'); + } + + function closeModal(e) { + if (e) e.preventDefault(); + modal.classList.remove('is-open', 'show'); + modal.setAttribute('aria-hidden', 'true'); + document.documentElement.classList.remove('aksrt-modal-open'); + } + + qsa('a[href="#submit-links-modal"], [data-toggle="modal"][href="#submit-links-modal"]').forEach(function (btn) { + btn.addEventListener('click', openModal); + }); + + qsa('[data-dismiss="modal"]', modal).forEach(function (btn) { + btn.addEventListener('click', closeModal); + }); + + modal.addEventListener('click', function (e) { + if (e.target === modal) closeModal(e); + }); + + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape' && modal.classList.contains('is-open')) closeModal(e); + }); + + var fileInput = qs('.aksrt-links-logo-input', modal); + var preview = qs('.aksrt-links-logo-preview', modal); + if (fileInput && preview) { + fileInput.addEventListener('change', function () { + var file = fileInput.files && fileInput.files[0]; + if (!file) return; + var url = URL.createObjectURL(file); + preview.src = url; + }); + } + + var form = qs('.aksrt-links-submit-form', modal); + if (!form) return; + + form.addEventListener('submit', function (e) { + e.preventDefault(); + var btn = qs('.aksrt-links-submit-btn', form); + if (btn) { + btn.disabled = true; + btn.classList.add('loading'); + } + + var fd = new FormData(form); + if (!fd.get('action')) fd.set('action', 'aksrt_frontend_links_submit'); + + fetch(ajaxUrl(), { + method: 'POST', + body: fd, + credentials: 'same-origin', + }) + .then(function (r) { + return r.json(); + }) + .then(function (data) { + if (!data || data.error) { + toast((data && data.msg) || '提交失败', 'danger'); + return; + } + toast(data.msg || '提交成功', 'success'); + if (data.reload) { + window.setTimeout(function () { + window.location.reload(); + }, 600); + } else { + closeModal(); + form.reset(); + } + }) + .catch(function () { + toast('网络错误,请稍后重试', 'danger'); + }) + .finally(function () { + if (btn) { + btn.disabled = false; + btn.classList.remove('loading'); + } + }); + }); + } + + function init() { + if (!document.body.classList.contains('page-template-templates-pages-page-links-php') + && !qs('.page-template-links') + && !qs('.links-page-container') + && !qs('#submit-links-modal')) { + // Still bind if modal exists alone. + } + initSearchTabs(); + initCatNav(); + initSubmitModal(); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/assets/js/posts-navs-page.js b/assets/js/posts-navs-page.js new file mode 100644 index 00000000..aa81a3fc --- /dev/null +++ b/assets/js/posts-navs-page.js @@ -0,0 +1,255 @@ +/** + * AKSRT 文章导航页 — admin settings modal + HTML5 drag-sort (no SortableJS dependency). + * TOC generation is handled by section-navs.js via [data-nav] + .posts-nav-box. + * + * @package AKSRT + */ +(function () { + 'use strict'; + + function qs(sel, root) { + return (root || document).querySelector(sel); + } + + function qsa(sel, root) { + return Array.prototype.slice.call((root || document).querySelectorAll(sel)); + } + + function ajaxUrl() { + if (window.aksrtData && window.aksrtData.ajaxUrl) return window.aksrtData.ajaxUrl; + if (window.ajaxurl) return window.ajaxurl; + return '/wp-admin/admin-ajax.php'; + } + + function toast(msg, type) { + if (window.aksrt && typeof window.aksrt.toast === 'function') { + window.aksrt.toast(msg, type || 'info'); + return; + } + // eslint-disable-next-line no-alert + window.alert(msg); + } + + function initModal() { + var modal = qs('#post-navs-sz'); + if (!modal) return; + + function openModal(e) { + if (e) e.preventDefault(); + modal.classList.add('is-open', 'show'); + modal.setAttribute('aria-hidden', 'false'); + } + + function closeModal(e) { + if (e) e.preventDefault(); + modal.classList.remove('is-open', 'show'); + modal.setAttribute('aria-hidden', 'true'); + } + + qsa('[data-toggle="modal"][data-target="#post-navs-sz"], a[href="#post-navs-sz"]').forEach(function (el) { + el.addEventListener('click', openModal); + }); + // Title [设置] uses data-target. + qsa('[data-target="#post-navs-sz"]').forEach(function (el) { + el.addEventListener('click', openModal); + }); + qsa('[data-dismiss="modal"]', modal).forEach(function (btn) { + btn.addEventListener('click', closeModal); + }); + modal.addEventListener('click', function (e) { + if (e.target === modal) closeModal(e); + }); + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape' && modal.classList.contains('is-open')) closeModal(e); + }); + } + + function initDragSort() { + var left = qs('#cat-sortable'); + var right = qs('#cat-sortable-ok'); + if (!left || !right) return; + + var dragEl = null; + + function onDragStart(e) { + dragEl = e.currentTarget; + dragEl.classList.add('is-dragging'); + try { + e.dataTransfer.effectAllowed = 'move'; + e.dataTransfer.setData('text/plain', dragEl.getAttribute('data-id') || ''); + } catch (err) { /* IE */ } + } + + function onDragEnd() { + if (dragEl) dragEl.classList.remove('is-dragging'); + dragEl = null; + qsa('.cat-group').forEach(function (g) { + g.classList.remove('is-drop-target'); + }); + } + + function bindItem(el) { + el.setAttribute('draggable', 'true'); + el.addEventListener('dragstart', onDragStart); + el.addEventListener('dragend', onDragEnd); + } + + qsa('.sortable-cat-item', left).forEach(bindItem); + qsa('.sortable-cat-item', right).forEach(bindItem); + + [left, right].forEach(function (group) { + group.addEventListener('dragover', function (e) { + e.preventDefault(); + group.classList.add('is-drop-target'); + if (!dragEl) return; + var after = getDragAfterElement(group, e.clientY); + if (after == null) { + group.appendChild(dragEl); + } else { + group.insertBefore(dragEl, after); + } + }); + group.addEventListener('dragleave', function () { + group.classList.remove('is-drop-target'); + }); + group.addEventListener('drop', function (e) { + e.preventDefault(); + group.classList.remove('is-drop-target'); + }); + }); + + function getDragAfterElement(container, y) { + var els = qsa('.sortable-cat-item:not(.is-dragging)', container); + var closest = { offset: Number.NEGATIVE_INFINITY, element: null }; + els.forEach(function (child) { + var box = child.getBoundingClientRect(); + var offset = y - box.top - box.height / 2; + if (offset < 0 && offset > closest.offset) { + closest = { offset: offset, element: child }; + } + }); + return closest.element; + } + } + + function initSave() { + var form = qs('#aksrt-posts-navs-settings'); + if (!form) return; + + form.addEventListener('submit', function (e) { + e.preventDefault(); + var ids = []; + qsa('#cat-sortable-ok .sortable-cat-item').forEach(function (item) { + var id = item.getAttribute('data-id'); + if (id) ids.push(id); + }); + if (!ids.length) { + toast('请选择需要显示的分类', 'danger'); + return; + } + + var fd = new FormData(form); + fd.set('action', 'aksrt_post_navs_settings'); + // PHP accepts array; append each. + fd.delete('navs_show_cat_id'); + ids.forEach(function (id) { + fd.append('navs_show_cat_id[]', id); + }); + + var btn = qs('button[type="submit"]', form); + if (btn) btn.disabled = true; + + fetch(ajaxUrl(), { + method: 'POST', + body: fd, + credentials: 'same-origin', + }) + .then(function (r) { return r.json(); }) + .then(function (data) { + if (!data || data.error) { + toast((data && data.msg) || '保存失败', 'danger'); + return; + } + toast(data.msg || '保存成功', 'success'); + if (data.reload) { + window.setTimeout(function () { window.location.reload(); }, 400); + } + }) + .catch(function () { + toast('网络错误', 'danger'); + }) + .finally(function () { + if (btn) btn.disabled = false; + }); + }); + } + + /** + * Build simple TOC into .posts-nav-box if section-navs hasn't already. + * Uses category h2 headings inside .posts-navs. + */ + function initFallbackToc() { + var box = qs('.posts-nav-box'); + var root = qs('.posts-navs[data-nav]'); + if (!box || !root) return; + if (box.querySelector('.posts-nav-lists, .aksrt-section-nav, .toc-list')) return; + + var headings = qsa('.title-theme h2, h2', root); + if (headings.length < 1) return; + + var title = box.getAttribute('data-title') || '导航目录'; + var list = ''; + headings.forEach(function (h, i) { + if (!h.id) h.id = 'navs-sec-' + i; + list += '
  • ' + (h.textContent || '').trim() + '
  • '; + }); + + box.innerHTML = + '
    ' + title + '
    ' + + '
    ' + + '
    '; + + qsa('a[href^="#"]', box).forEach(function (a) { + a.addEventListener('click', function (e) { + var id = a.getAttribute('href'); + var el = id ? document.getElementById(id.replace(/^#/, '')) : null; + if (!el) return; + e.preventDefault(); + var top = el.getBoundingClientRect().top + window.pageYOffset - 90; + window.scrollTo({ top: top, behavior: 'smooth' }); + }); + }); + + function highlight() { + var y = window.pageYOffset + 120; + var current = headings[0]; + headings.forEach(function (h) { + if (h.offsetTop <= y) current = h; + }); + qsa('li', box).forEach(function (li) { + var a = li.querySelector('a'); + if (a && current && a.getAttribute('href') === '#' + current.id) { + li.classList.add('active'); + } else { + li.classList.remove('active'); + } + }); + } + window.addEventListener('scroll', highlight, { passive: true }); + highlight(); + } + + function init() { + initModal(); + initDragSort(); + initSave(); + // Defer so section-navs.js can own TOC first. + window.setTimeout(initFallbackToc, 50); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/assets/js/user-auth-page.js b/assets/js/user-auth-page.js new file mode 100644 index 00000000..8de307e5 --- /dev/null +++ b/assets/js/user-auth-page.js @@ -0,0 +1,64 @@ +/** + * User auth apply form AJAX. + * + * @package AKSRT + */ +(function () { + 'use strict'; + + function ajaxUrl() { + if (window.aksrtData && window.aksrtData.ajaxUrl) return window.aksrtData.ajaxUrl; + return '/wp-admin/admin-ajax.php'; + } + + function toast(msg, type) { + if (window.aksrt && typeof window.aksrt.toast === 'function') { + window.aksrt.toast(msg, type || 'info'); + return; + } + // eslint-disable-next-line no-alert + window.alert(msg); + } + + function init() { + var form = document.querySelector('.aksrt-user-auth-form'); + if (!form) return; + + form.addEventListener('submit', function (e) { + e.preventDefault(); + var btn = form.querySelector('button[type="submit"]'); + if (btn) btn.disabled = true; + var fd = new FormData(form); + if (!fd.get('action')) fd.set('action', 'aksrt_user_auth_apply'); + + fetch(ajaxUrl(), { + method: 'POST', + body: fd, + credentials: 'same-origin', + }) + .then(function (r) { return r.json(); }) + .then(function (data) { + if (!data || data.error) { + toast((data && data.msg) || '提交失败', 'danger'); + return; + } + toast(data.msg || '提交成功', 'success'); + if (data.reload) { + window.setTimeout(function () { window.location.reload(); }, 500); + } + }) + .catch(function () { + toast('网络错误', 'danger'); + }) + .finally(function () { + if (btn) btn.disabled = false; + }); + }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/functions.php b/functions.php index da58522d..5a9f176e 100644 --- a/functions.php +++ b/functions.php @@ -249,6 +249,18 @@ if (class_exists('\AKSRT\Core\CommentActions')) { if (class_exists('\AKSRT\Core\GoRedirect')) { \AKSRT\Core\GoRedirect::get_instance()->init(); } +if (class_exists('\AKSRT\Core\LinksSubmit')) { + \AKSRT\Core\LinksSubmit::get_instance()->init(); +} +if (class_exists('\AKSRT\Core\LinksPageMeta')) { + \AKSRT\Core\LinksPageMeta::get_instance()->init(); +} +if (class_exists('\AKSRT\Core\PostsNavs')) { + \AKSRT\Core\PostsNavs::get_instance()->init(); +} +if (class_exists('\AKSRT\User\AuthApply')) { + \AKSRT\User\AuthApply::get_instance()->init(); +} if (class_exists('\AKSRT\Messages\SiteNotice')) { \AKSRT\Messages\SiteNotice::get_instance()->init(); } diff --git a/header.php b/header.php index 115573c1..628799c9 100644 --- a/header.php +++ b/header.php @@ -303,11 +303,32 @@ if (!empty($aksrt_bg_styles)) { + ' . $checkin . ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + } + if (function_exists('aksrt_header_user_wallet_mini')) { + echo '
    ' . aksrt_header_user_wallet_mini(get_current_user_id()) . '
    '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } else { + ?>
    + +
    @@ -326,7 +347,7 @@ if (!empty($aksrt_bg_styles)) { - + @@ -370,6 +391,16 @@ if (!empty($aksrt_bg_styles)) { + + render_social_buttons(__('快速登录', 'aksrt')); + if ($oauth_html !== '') { + echo '
    ' . $oauth_html . '
    '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + } + ?> + @@ -385,6 +416,8 @@ if (!empty($aksrt_bg_styles)) { + + diff --git a/inc/bootstrap.php b/inc/bootstrap.php index 79b751a0..2996a21e 100644 --- a/inc/bootstrap.php +++ b/inc/bootstrap.php @@ -43,6 +43,8 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-coupon-order.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-credits.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-null.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-ajax-list.php'; +require_once AKSRT_DIR . '/inc/helpers/aksrt-links.php'; +require_once AKSRT_DIR . '/inc/helpers/aksrt-header-userbox.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-identity.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-rewards.php'; require_once AKSRT_DIR . '/inc/helpers/menu-decoration.php'; diff --git a/inc/core-hooks/part-2.php b/inc/core-hooks/part-2.php index 7ab3b95b..9664fed8 100644 --- a/inc/core-hooks/part-2.php +++ b/inc/core-hooks/part-2.php @@ -7,12 +7,11 @@ defined('ABSPATH') || exit; add_filter('show_admin_bar', '__return_false'); // ============================================================ -// 隐藏不需要的默认菜单项 +// WordPress 链接管理(网址导航 / 书签) +// zibll enables link_manager; AKSRT keeps it for page-links. // ============================================================ -add_action('admin_menu', function () { - remove_menu_page('link-manager.php'); -}); +add_filter('pre_option_link_manager_enabled', '__return_true'); // ============================================================ // Gravatar 头像源替换(4层拦截:get_avatar_url / pre_get_avatar_data / avatar_defaults / ob_start 兜底) diff --git a/inc/helpers/aksrt-header-userbox.php b/inc/helpers/aksrt-header-userbox.php new file mode 100644 index 00000000..442ec454 --- /dev/null +++ b/inc/helpers/aksrt-header-userbox.php @@ -0,0 +1,161 @@ + 0 ? $user_id : get_current_user_id(); + if ($user_id <= 0 || !class_exists('\AKSRT\Credits\Signin')) { + return ''; + } + $cfg = class_exists('\AKSRT\Settings\Settings') + ? \AKSRT\Settings\Settings::get_signin_reward_settings() + : ['enabled' => false]; + if (empty($cfg['enabled'])) { + return ''; + } + + // Lightweight today status via private API if available. + $signed = false; + try { + $signin = \AKSRT\Credits\Signin::get_instance(); + // Prefer public method if present. + if (method_exists($signin, 'is_signed_today')) { + $signed = (bool) $signin->is_signed_today($user_id); + } else { + // Fallback: direct meta/table probe via rest_today logic is heavy; use option day key. + $day = wp_date('Y-m-d'); + $signed = (bool) get_user_meta($user_id, 'aksrt_signin_' . $day, true); + } + } catch (\Throwable $e) { + $signed = false; + } + + if ($signed) { + return ''; + } + + return ''; + } +endif; + +if (!function_exists('aksrt_header_user_wallet_mini')) : + /** + * Mini wallet strip: credits + cash balance. + */ + function aksrt_header_user_wallet_mini(int $user_id = 0): string { + $user_id = $user_id > 0 ? $user_id : get_current_user_id(); + if ($user_id <= 0) { + return ''; + } + $credits = function_exists('aksrt_user_credits') ? (int) aksrt_user_credits($user_id) : 0; + $currency = class_exists('\AKSRT\Settings\Settings') + ? \AKSRT\Settings\Settings::get_currency_name() + : __('积分', 'aksrt'); + $wallet = 0.0; + if (class_exists('\AKSRT\Credits\Balance')) { + $wallet = (float) \AKSRT\Credits\Balance::balance($user_id); + } + $center = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url('assets') : '#'; + + $html = ''; + $html .= ' ' + . esc_html($currency) . ' ' . esc_html(number_format_i18n($credits)) . ''; + $html .= ' ' + . esc_html__('余额', 'aksrt') . ' ' . esc_html(number_format_i18n($wallet, 2)) . ''; + $html .= ''; + return $html; + } +endif; + +if (!function_exists('aksrt_header_user_badges_row')) : + /** + * Identity / medal chips under profile. + */ + function aksrt_header_user_badges_row(int $user_id = 0): string { + $user_id = $user_id > 0 ? $user_id : get_current_user_id(); + if ($user_id <= 0 || !function_exists('aksrt_render_user_identity_chips')) { + return ''; + } + $chips = aksrt_render_user_identity_chips($user_id, 'card'); + if ($chips === '') { + return ''; + } + return '
    ' . $chips . '
    '; + } +endif; + +if (!function_exists('aksrt_header_payvip_bar')) : + /** + * VIP status / open CTA bar. + */ + function aksrt_header_payvip_bar(int $user_id = 0): string { + $user_id = $user_id > 0 ? $user_id : get_current_user_id(); + if ($user_id <= 0) { + return ''; + } + $badge = function_exists('aksrt_render_vip_badge') ? aksrt_render_vip_badge($user_id, 'md') : ''; + if ($badge !== '') { + return '
    ' . $badge . '
    '; + } + // Guest-style open VIP for non-members if VIP feature on. + $vip_url = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url('vip') : '#'; + $enabled = true; + if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_vip_settings')) { + $vs = \AKSRT\Settings\Settings::get_vip_settings(); + $enabled = !empty($vs['enabled']) || !empty($vs['vip1_enabled']) || !empty($vs['vip2_enabled']); + } + if (!$enabled) { + return ''; + } + return '
    ' + . '' + . '' + . esc_html__('开通会员 尊享会员权益', 'aksrt') + . '
    '; + } +endif; + +if (!function_exists('aksrt_render_signout_modal')) : + /** + * Confirm logout modal markup (once per page). + */ + function aksrt_render_signout_modal(): void { + static $done = false; + if ($done || !is_user_logged_in()) { + return; + } + $done = true; + $logout = wp_logout_url(home_url('/')); + ?> + + create_go_link($url); + } + return $url; + } +endif; + +if (!function_exists('aksrt_get_page_links_settings')) : + /** + * Read page_links_* meta for a page. + * + * @return array + */ + function aksrt_get_page_links_settings(int $post_id = 0): array { + if ($post_id <= 0) { + $post_id = (int) get_queried_object_id(); + } + $cats = get_post_meta($post_id, 'page_links_category', true); + if (!is_array($cats)) { + $cats = $cats !== '' && $cats !== null ? array_filter(array_map('intval', (array) $cats)) : []; + } else { + $cats = array_values(array_filter(array_map('intval', $cats))); + } + $search_types = get_post_meta($post_id, 'page_links_search_types', true); + if (!is_array($search_types)) { + $search_types = $search_types ? array_values(array_filter(array_map('sanitize_key', (array) $search_types))) : []; + } else { + $search_types = array_values(array_filter(array_map('sanitize_key', $search_types))); + } + $submit_cats = get_post_meta($post_id, 'page_links_submit_cats', true); + if (!is_array($submit_cats)) { + $submit_cats = $submit_cats !== '' && $submit_cats !== null ? array_filter(array_map('intval', (array) $submit_cats)) : []; + } else { + $submit_cats = array_values(array_filter(array_map('intval', $submit_cats))); + } + + $style = sanitize_key((string) get_post_meta($post_id, 'page_links_style', true)); + if (!in_array($style, ['card', 'bigcard', 'image', 'simple'], true)) { + $style = 'card'; + } + $orderby = sanitize_key((string) get_post_meta($post_id, 'page_links_orderby', true)); + if (!in_array($orderby, ['name', 'updated', 'rating', 'rand', 'id', 'length'], true)) { + $orderby = 'name'; + } + $order = strtoupper((string) get_post_meta($post_id, 'page_links_order', true)); + if (!in_array($order, ['ASC', 'DESC'], true)) { + $order = 'ASC'; + } + $position = sanitize_key((string) get_post_meta($post_id, 'page_links_content_position', true)); + if (!in_array($position, ['top', 'bottom'], true)) { + $position = 'top'; + } + + return [ + 'content_s' => (bool) get_post_meta($post_id, 'page_links_content_s', true), + 'content_position' => $position, + 'search_s' => (bool) get_post_meta($post_id, 'page_links_search_s', true), + 'search_types' => $search_types, + 'category' => $cats, + 'style' => $style, + 'limit' => max(0, (int) get_post_meta($post_id, 'page_links_limit', true)), + 'orderby' => $orderby, + 'order' => $order, + 'go_s' => (bool) get_post_meta($post_id, 'page_links_go_s', true), + 'blank_s' => (bool) get_post_meta($post_id, 'page_links_blank_s', true), + // Default nofollow ON (zibll default true). + 'nofollow_s' => get_post_meta($post_id, 'page_links_nofollow_s', true) === '' + ? true + : (bool) get_post_meta($post_id, 'page_links_nofollow_s', true), + 'submit_s' => (bool) get_post_meta($post_id, 'page_links_submit_s', true), + 'submit_sign_s' => get_post_meta($post_id, 'page_links_submit_sign_s', true) === '' + ? true + : (bool) get_post_meta($post_id, 'page_links_submit_sign_s', true), + 'submit_cats' => $submit_cats, + 'submit_title' => (string) (get_post_meta($post_id, 'page_links_submit_title', true) ?: __('申请入驻', 'aksrt')), + 'submit_dec' => (string) get_post_meta($post_id, 'page_links_submit_dec', true), + ]; + } +endif; + +if (!function_exists('aksrt_links_box')) : + /** + * Render bookmark cards (zibll zib_links_box). + * + * @param array $links Bookmark rows from get_bookmarks(). + */ + function aksrt_links_box($links = [], string $type = 'card', bool $nofollow = true, bool $go_link = false, bool $blank_s = false): string { + if (empty($links) || !is_array($links)) { + return ''; + } + $type = in_array($type, ['card', 'bigcard', 'image', 'simple'], true) ? $type : 'card'; + $placeholder = get_template_directory_uri() . '/assets/img/placeholder.svg'; + $html = ''; + $i = 0; + + foreach ($links as $raw) { + $link = (array) $raw; + if (empty($link['href']) && !empty($link['link_url'])) { + $link['href'] = $link['link_url']; + } + if (empty($link['title']) && !empty($link['link_name'])) { + $link['title'] = $link['link_name']; + } + if (empty($link['src']) && !empty($link['link_image'])) { + $link['src'] = $link['link_image']; + } + if (empty($link['desc']) && !empty($link['link_description'])) { + $link['desc'] = $link['link_description']; + } + if ($blank_s) { + $link['blank'] = true; + } + if (empty($link['blank']) && !empty($link['link_target'])) { + $link['blank'] = true; + } + + $href = !empty($link['href']) ? (string) $link['href'] : ''; + $title = !empty($link['title']) ? (string) $link['title'] : ''; + if ($href === '' || $title === '') { + continue; + } + + if ($go_link || !empty($link['go_link'])) { + $href = aksrt_go_link($href, true); + } else { + $href = esc_url($href); + } + + $src = !empty($link['src']) ? esc_url((string) $link['src']) : ''; + $blank = !empty($link['blank']) ? ' target="_blank"' : ''; + $dec = !empty($link['desc']) ? (string) $link['desc'] : ''; + $rel = $nofollow ? ' rel="nofollow noopener noreferrer"' : ' rel="noopener noreferrer"'; + $img_src = $src !== '' ? $src : $placeholder; + $img = ''; + $data_dec = $dec !== '' + ? ' title="' . esc_attr($title) . '" data-content="' . esc_attr($dec) . '"' + : ' data-content="' . esc_attr($title) . '"'; + + if ($type === 'bigcard') { + $html .= ''; + } elseif ($type === 'image') { + $html .= '' . $img . ''; + } elseif ($type === 'simple') { + $sc = $i === 0 ? '' : 'icon-spot'; + $html .= '' . esc_html($title) . ''; + } else { + $html .= ''; + } + $i++; + } + + return $html; + } +endif; + +if (!function_exists('aksrt_page_links_html')) : + /** + * Build the multi/single category links block for a page. + */ + function aksrt_page_links_html(int $post_id = 0): string { + $post_id = $post_id > 0 ? $post_id : (int) get_queried_object_id(); + $s = aksrt_get_page_links_settings($post_id); + $cats = $s['category']; + if (empty($cats)) { + if (current_user_can('manage_links')) { + return ''; + } + return ''; + } + + $limit = (int) $s['limit']; + $orderby = (string) $s['orderby']; + $order = (string) $s['order']; + $style = (string) $s['style']; + $nofollow = (bool) $s['nofollow_s']; + $go_s = (bool) $s['go_s']; + $blank_s = (bool) $s['blank_s']; + $submit_s = (bool) $s['submit_s']; + $submit_title = (string) $s['submit_title']; + $is_mobile = wp_is_mobile(); + $links_con = ''; + + if (count($cats) > 1) { + $link_cat_con = ''; + $link_cat_nav = ''; + foreach ($cats as $cat_id) { + $args = [ + 'orderby' => $orderby, + 'order' => $order, + 'limit' => $limit > 0 ? $limit : -1, + 'category' => (int) $cat_id, + ]; + $_link = aksrt_links_box(get_bookmarks($args), $style, $nofollow, $go_s, $blank_s); + if (!$_link) { + continue; + } + $term = get_term((int) $cat_id, 'link_category'); + if (!$term || is_wp_error($term)) { + continue; + } + $_id = 'links-' . (int) $cat_id; + $link_cat_con .= ''; + $link_cat_nav .= '
  • ' . esc_html($term->name) . '
  • '; + } + + if ($link_cat_con === '') { + $links_con = ''; + } else { + $submit_nav = $submit_s + ? '' + : ''; + $submit_con = $submit_s + ? '' + : ''; + $offset_top = $is_mobile ? '-8' : ''; + $offset_bottom = $is_mobile ? '20' : '-30'; + $links_con = '
    ' + . '
    ' . $link_cat_con . $submit_con . '
    '; + } + } else { + $cat_id = (int) $cats[0]; + $args = [ + 'orderby' => $orderby, + 'order' => $order, + 'limit' => $limit > 0 ? $limit : -1, + 'category' => $cat_id, + ]; + $submit_con = $submit_s + ? '' + : ''; + $links_con = '' . $submit_con; + } + + if ($links_con === '') { + if (current_user_can('manage_links')) { + return ''; + } + return ''; + } + + return ''; + } +endif; + +if (!function_exists('aksrt_link_get_search_cover')) : + /** + * Search cover with multi-engine tabs (zibll zib_link_get_search_cover). + */ + function aksrt_link_get_search_cover(int $post_id = 0): string { + $post_id = $post_id > 0 ? $post_id : (int) get_queried_object_id(); + $s = aksrt_get_page_links_settings($post_id); + if (empty($s['search_s']) || empty($s['search_types'])) { + return ''; + } + + $search_types_args = [ + 'self' => [ + 'name' => __('站内', 'aksrt'), + 'input_name' => 's', + 'url' => home_url('/'), + ], + 'baidu' => [ + 'name' => __('百度', 'aksrt'), + 'input_name' => 'wd', + 'url' => 'https://www.baidu.com/s', + ], + 'google' => [ + 'name' => __('谷歌', 'aksrt'), + 'input_name' => 'q', + 'url' => 'https://www.google.com/search', + ], + 'bing' => [ + 'name' => __('必应', 'aksrt'), + 'input_name' => 'q', + 'url' => 'https://cn.bing.com/search', + ], + 'sogou' => [ + 'name' => __('搜狗', 'aksrt'), + 'input_name' => 'query', + 'url' => 'https://www.sogou.com/web', + ], + '360' => [ + 'name' => '360', + 'input_name' => 'q', + 'url' => 'https://www.so.com/s', + ], + ]; + + $type_nav = ''; + $type_con = ''; + $i = 0; + foreach ($s['search_types'] as $k) { + if (!isset($search_types_args[$k])) { + continue; + } + $cfg = $search_types_args[$k]; + $active = $i === 0 ? ' active' : ''; + $pane_id = 'link-page-' . $post_id . '-' . $k; + $type_nav .= '
  • ' + . esc_html($cfg['name']) . '
  • '; + $type_con .= '
    ' + . '
    ' + . '
    ' + . '' + . '' + . '
    ' . esc_html(sprintf(__('%s搜索', 'aksrt'), $cfg['name'])) . '
    ' + . '
    ' + . '
    '; + $i++; + } + if ($type_nav === '') { + return ''; + } + + $title = get_the_title($post_id); + $img = ''; + $thumb_id = get_post_thumbnail_id($post_id); + if ($thumb_id) { + $image = wp_get_attachment_image_src($thumb_id, 'full'); + $img = !empty($image[0]) ? (string) $image[0] : ''; + } + if ($img === '') { + $img = (string) get_post_meta($post_id, 'page_header_cover_img', true); + } + if ($img === '' && class_exists('\AKSRT\Settings\Settings')) { + $img = (string) \AKSRT\Settings\Settings::get_value('page_header_cover_img', ''); + } + if ($img === '') { + $img = get_template_directory_uri() . '/assets/img/placeholder.svg'; + } + $src = get_template_directory_uri() . '/assets/img/placeholder.svg'; + + $search = ''; + + return ''; + } +endif; + +if (!function_exists('aksrt_submit_links_modal')) : + /** + * Frontend submit modal markup (zibll zib_submit_links_modal). + * + * @param array{title?:string,dec?:string,sign?:bool,cats?:int[]} $args + */ + function aksrt_submit_links_modal(array $args = []): string { + $defaults = [ + 'title' => __('申请入驻', 'aksrt'), + 'dec' => '', + 'sign' => true, + 'cats' => [], + ]; + $args = array_merge($defaults, $args); + $title = (string) $args['title']; + $header = $title !== '' + ? '
    ' + . '' + . esc_html($title) . '
    ' + : ''; + + $body = ''; + if (!empty($args['dec'])) { + $body .= '
    ' . wp_kses_post((string) $args['dec']) . '
    '; + } + + $need_login = !empty($args['sign']) && !is_user_logged_in(); + if ($need_login) { + $sign_url = function_exists('aksrt_get_login_url') + ? aksrt_get_login_url() + : (function_exists('aksrt_get_sign_page_url') ? aksrt_get_sign_page_url('login') : wp_login_url()); + $body .= '
    '; + $body .= '
    ' . esc_html__('请先登录', 'aksrt') . '
    '; + $body .= '

    '; + if (class_exists('\AKSRT\Auth\OAuth') && method_exists(\AKSRT\Auth\OAuth::get_instance(), 'render_social_buttons')) { + $social = (string) \AKSRT\Auth\OAuth::get_instance()->render_social_buttons(__('使用以下方式快速登录', 'aksrt')); + if ($social !== '') { + $body .= ''; + } + } + $body .= '
    '; + } else { + $term_args = [ + 'taxonomy' => 'link_category', + 'hide_empty' => false, + ]; + if (!empty($args['cats']) && is_array($args['cats'])) { + $term_args['include'] = array_map('intval', $args['cats']); + $term_args['orderby'] = 'include'; + } + $terms = get_terms($term_args); + $cats_options = ''; + if (!is_wp_error($terms) && !empty($terms)) { + foreach ($terms as $item) { + $cats_options .= ''; + } + } + $cats_field = $cats_options !== '' + ? '
    ' . esc_html__('网站类别', 'aksrt') . '
    ' + . '
    ' + : ''; + + $placeholder_upload = get_template_directory_uri() . '/assets/img/placeholder.svg'; + $body .= ''; + } + + return ''; + } +endif; diff --git a/src/AKSRT/Admin/Native/UsersMenus.php b/src/AKSRT/Admin/Native/UsersMenus.php index 7a579e85..f3ea77ac 100644 --- a/src/AKSRT/Admin/Native/UsersMenus.php +++ b/src/AKSRT/Admin/Native/UsersMenus.php @@ -550,6 +550,18 @@ class UsersMenus { } elseif ($status === 'verified') { delete_user_meta($user_id, 'aksrt_user_verify_reason'); } + // Promote apply payload to public auth info when approved. + if ($status === 'verified') { + $apply = get_user_meta($user_id, 'aksrt_user_verify_apply', true); + if (is_array($apply)) { + update_user_meta($user_id, 'aksrt_user_verify_info', [ + 'name' => (string) ($apply['name'] ?? ''), + 'desc' => (string) ($apply['desc'] ?? ''), + 'type' => (string) ($apply['type'] ?? ''), + 'time' => current_time('Y-m-d H:i'), + ]); + } + } $log = get_option('aksrt_verify_review_log', []); $log = is_array($log) ? $log : []; $log[] = [ diff --git a/src/AKSRT/Core/LinksPageMeta.php b/src/AKSRT/Core/LinksPageMeta.php new file mode 100644 index 00000000..fc6aa66b --- /dev/null +++ b/src/AKSRT/Core/LinksPageMeta.php @@ -0,0 +1,302 @@ +ID; + if (!$this->is_links_template($post_id)) { + echo '

    ' . esc_html__('将页面模板设为「AKSRT - 网址导航」后在此配置分类、样式与提交模块。', 'aksrt') . '

    '; + return; + } + + $s = function_exists('aksrt_get_page_links_settings') + ? aksrt_get_page_links_settings($post_id) + : []; + + wp_nonce_field('aksrt_page_links_meta', 'aksrt_page_links_meta_nonce'); + + $terms = get_terms([ + 'taxonomy' => 'link_category', + 'hide_empty' => false, + ]); + if (is_wp_error($terms)) { + $terms = []; + } + + $selected_cats = array_map('intval', (array) ($s['category'] ?? [])); + $submit_cats = array_map('intval', (array) ($s['submit_cats'] ?? [])); + $search_types = (array) ($s['search_types'] ?? []); + $engine_options = [ + 'self' => __('站内', 'aksrt'), + 'baidu' => __('百度', 'aksrt'), + 'google' => __('谷歌', 'aksrt'), + 'bing' => __('必应', 'aksrt'), + 'sogou' => __('搜狗', 'aksrt'), + '360' => '360', + ]; + ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + is_links_template($post_id) && empty($_POST['page_links_category']) && empty($_POST['page_links_submit_s'])) { + // Still allow save if user just switched template in same request. + $tpl = isset($_POST['page_template']) ? (string) wp_unslash($_POST['page_template']) : ''; + if ($tpl !== 'templates/pages/page-links.php') { + return; + } + } + + $bool_keys = [ + 'page_links_content_s', + 'page_links_search_s', + 'page_links_go_s', + 'page_links_blank_s', + 'page_links_nofollow_s', + 'page_links_submit_s', + 'page_links_submit_sign_s', + ]; + foreach ($bool_keys as $key) { + update_post_meta($post_id, $key, isset($_POST[$key]) ? 1 : 0); + } + + $position = isset($_POST['page_links_content_position']) ? sanitize_key((string) wp_unslash($_POST['page_links_content_position'])) : 'top'; + if (!in_array($position, ['top', 'bottom'], true)) { + $position = 'top'; + } + update_post_meta($post_id, 'page_links_content_position', $position); + + $style = isset($_POST['page_links_style']) ? sanitize_key((string) wp_unslash($_POST['page_links_style'])) : 'card'; + if (!in_array($style, ['card', 'bigcard', 'image', 'simple'], true)) { + $style = 'card'; + } + update_post_meta($post_id, 'page_links_style', $style); + + $orderby = isset($_POST['page_links_orderby']) ? sanitize_key((string) wp_unslash($_POST['page_links_orderby'])) : 'name'; + if (!in_array($orderby, ['name', 'updated', 'rating', 'rand', 'id', 'length'], true)) { + $orderby = 'name'; + } + update_post_meta($post_id, 'page_links_orderby', $orderby); + + $order = isset($_POST['page_links_order']) ? strtoupper(sanitize_text_field(wp_unslash((string) $_POST['page_links_order']))) : 'ASC'; + update_post_meta($post_id, 'page_links_order', $order === 'DESC' ? 'DESC' : 'ASC'); + + $limit = isset($_POST['page_links_limit']) ? max(0, (int) $_POST['page_links_limit']) : 0; + update_post_meta($post_id, 'page_links_limit', $limit); + + $cats = isset($_POST['page_links_category']) ? array_values(array_filter(array_map('intval', (array) wp_unslash($_POST['page_links_category'])))) : []; + update_post_meta($post_id, 'page_links_category', $cats); + + $submit_cats = isset($_POST['page_links_submit_cats']) ? array_values(array_filter(array_map('intval', (array) wp_unslash($_POST['page_links_submit_cats'])))) : []; + update_post_meta($post_id, 'page_links_submit_cats', $submit_cats); + + $engines = isset($_POST['page_links_search_types']) ? array_values(array_filter(array_map('sanitize_key', (array) wp_unslash($_POST['page_links_search_types'])))) : []; + $allowed = ['self', 'baidu', 'google', 'bing', 'sogou', '360']; + $engines = array_values(array_intersect($engines, $allowed)); + update_post_meta($post_id, 'page_links_search_types', $engines); + + $title = isset($_POST['page_links_submit_title']) ? sanitize_text_field(wp_unslash((string) $_POST['page_links_submit_title'])) : ''; + update_post_meta($post_id, 'page_links_submit_title', $title); + + $dec = isset($_POST['page_links_submit_dec']) ? wp_kses_post(wp_unslash((string) $_POST['page_links_submit_dec'])) : ''; + update_post_meta($post_id, 'page_links_submit_dec', $dec); + } +} diff --git a/src/AKSRT/Core/LinksSubmit.php b/src/AKSRT/Core/LinksSubmit.php new file mode 100644 index 00000000..50a1de5e --- /dev/null +++ b/src/AKSRT/Core/LinksSubmit.php @@ -0,0 +1,123 @@ + 1, 'ys' => 'danger', 'msg' => __('安全校验失败,请刷新后重试', 'aksrt')]); + } + + if (isset($_COOKIE['aksrt_links_submit_time'])) { + wp_send_json(['error' => 1, 'ys' => 'danger', 'msg' => __('操作过于频繁,请稍候再试', 'aksrt')]); + } + + $link_name = isset($_POST['link_name']) ? sanitize_text_field(wp_unslash((string) $_POST['link_name'])) : ''; + $link_url = isset($_POST['link_url']) ? esc_url_raw(wp_unslash((string) $_POST['link_url'])) : ''; + $link_desc = isset($_POST['link_description']) ? sanitize_text_field(wp_unslash((string) $_POST['link_description'])) : ''; + $cat_raw = isset($_POST['link_category']) ? wp_unslash($_POST['link_category']) : []; + + if ($link_name === '') { + wp_send_json(['error' => 1, 'ys' => 'danger', 'msg' => __('请填写网站名称', 'aksrt')]); + } + if ($link_url === '' || !wp_http_validate_url($link_url)) { + wp_send_json(['error' => 1, 'ys' => 'danger', 'msg' => __('网站地址格式错误', 'aksrt')]); + } + + // Optional logo upload. + $logo_url = ''; + if (!empty($_FILES['image']['name'])) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + require_once ABSPATH . 'wp-admin/includes/media.php'; + require_once ABSPATH . 'wp-admin/includes/image.php'; + $attachment_id = media_handle_upload('image', 0); + if (is_wp_error($attachment_id)) { + wp_send_json(['error' => 1, 'ys' => 'danger', 'msg' => $attachment_id->get_error_message()]); + } + $src = wp_get_attachment_image_src((int) $attachment_id, 'medium'); + $logo_url = !empty($src[0]) ? (string) $src[0] : (string) wp_get_attachment_url((int) $attachment_id); + } + + $categories = []; + if (is_array($cat_raw)) { + $categories = array_values(array_filter(array_map('intval', $cat_raw))); + } elseif ($cat_raw !== '' && $cat_raw !== null) { + $categories = [absint($cat_raw)]; + } + + // Duplicate URL guard. + global $wpdb; + $exists = (int) $wpdb->get_var( + $wpdb->prepare( + "SELECT COUNT(1) FROM {$wpdb->links} WHERE link_url = %s", + $link_url + ) + ); + if ($exists > 0) { + wp_send_json(['error' => 1, 'ys' => 'danger', 'msg' => __('您的链接已提交,请勿重复提交', 'aksrt')]); + } + + if (!function_exists('wp_insert_link')) { + require_once ABSPATH . 'wp-admin/includes/bookmark.php'; + } + + $linkdata = [ + 'link_name' => $link_name, + 'link_url' => $link_url, + 'link_description' => $link_desc, + 'link_image' => $logo_url, + // Non-admins create invisible links pending review (zibll parity). + 'link_visible' => current_user_can('manage_links') ? 'Y' : 'N', + 'link_category' => $categories, + ]; + + $links_id = wp_insert_link($linkdata, true); + if (is_wp_error($links_id)) { + wp_send_json(['error' => 1, 'ys' => 'danger', 'msg' => $links_id->get_error_message()]); + } + + // 30s client throttle cookie. + if (!headers_sent()) { + setcookie('aksrt_links_submit_time', (string) time(), time() + 30, COOKIEPATH ?: '/', COOKIE_DOMAIN, is_ssl(), true); + } + + $msg = $linkdata['link_visible'] === 'Y' + ? __('提交成功', 'aksrt') + : __('提交成功,等待管理员处理', 'aksrt'); + + /** + * Fires after a frontend link submission is stored. + * + * @param array $linkdata Sanitized bookmark data. + * @param int $links_id New link ID. + */ + do_action('aksrt_frontend_links_submit_success', $linkdata, (int) $links_id); + + wp_send_json(['error' => 0, 'msg' => $msg, 'reload' => true]); + } +} diff --git a/src/AKSRT/Core/PostsNavs.php b/src/AKSRT/Core/PostsNavs.php new file mode 100644 index 00000000..4021fb78 --- /dev/null +++ b/src/AKSRT/Core/PostsNavs.php @@ -0,0 +1,77 @@ + 1, 'ys' => 'danger', 'msg' => __('权限不足', 'aksrt')]); + } + if (!check_ajax_referer('aksrt_post_navs_settings', '_wpnonce', false)) { + wp_send_json(['error' => 1, 'ys' => 'danger', 'msg' => __('安全校验失败', 'aksrt')]); + } + + $page_id = isset($_POST['page_id']) ? absint($_POST['page_id']) : 0; + if ($page_id <= 0 || get_post_type($page_id) !== 'page') { + wp_send_json(['error' => 1, 'ys' => 'danger', 'msg' => __('无效页面', 'aksrt')]); + } + + $cat_ids = []; + if (isset($_POST['navs_show_cat_id'])) { + $raw = wp_unslash($_POST['navs_show_cat_id']); + if (is_array($raw)) { + $cat_ids = array_values(array_filter(array_map('intval', $raw))); + } elseif (is_string($raw) && $raw !== '') { + $decoded = json_decode($raw, true); + if (is_array($decoded)) { + $cat_ids = array_values(array_filter(array_map('intval', $decoded))); + } else { + $cat_ids = array_values(array_filter(array_map('intval', explode(',', $raw)))); + } + } + } + + if (empty($cat_ids)) { + wp_send_json(['error' => 1, 'ys' => 'danger', 'msg' => __('请选择需要显示的分类', 'aksrt')]); + } + + if (isset($_POST['navs_page_name'])) { + update_post_meta($page_id, 'navs_page_name', sanitize_text_field(wp_unslash((string) $_POST['navs_page_name']))); + } + if (isset($_POST['navs_page_desc'])) { + update_post_meta($page_id, 'navs_page_desc', sanitize_text_field(wp_unslash((string) $_POST['navs_page_desc']))); + } + update_post_meta($page_id, 'navs_show_cat_id', $cat_ids); + + wp_send_json([ + 'error' => 0, + 'msg' => __('保存成功', 'aksrt'), + 'reload'=> true, + 'ids' => $cat_ids, + ]); + } +} diff --git a/src/AKSRT/Core/ScriptsPart1b.php b/src/AKSRT/Core/ScriptsPart1b.php index 47f1ab73..18bcd8a0 100644 --- a/src/AKSRT/Core/ScriptsPart1b.php +++ b/src/AKSRT/Core/ScriptsPart1b.php @@ -129,6 +129,27 @@ trait ScriptsPart1b { } } + // Header user-box density (check-in / wallet / signout modal) + $hub_css = get_template_directory() . '/assets/css/frontend/nav/header-userbox.css'; + if (file_exists($hub_css)) { + wp_enqueue_style( + 'aksrt-header-userbox', + $base_uri . '/assets/css/frontend/nav/header-userbox.css', + [$core_dep], + (string) filemtime($hub_css) + ); + } + $hub_js = get_template_directory() . '/assets/js/header-userbox.js'; + if (file_exists($hub_js)) { + wp_enqueue_script( + 'aksrt-header-userbox', + $base_uri . '/assets/js/header-userbox.js', + [], + (string) filemtime($hub_js), + true + ); + } + // Print stylesheet — only load on singular pages if (is_singular()) { wp_enqueue_style( @@ -381,6 +402,85 @@ trait ScriptsPart1b { ); } + // 网址导航页(zibll links.php) + if (is_page_template('templates/pages/page-links.php')) { + $links_css = get_template_directory() . '/assets/css/routes/links.css'; + if (file_exists($links_css)) { + wp_enqueue_style( + 'aksrt-route-links', + $base_uri . '/assets/css/routes/links.css', + ['aksrt-tokens'], + (string) filemtime($links_css) + ); + } + $links_js = get_template_directory() . '/assets/js/links-page.js'; + if (file_exists($links_js)) { + wp_enqueue_script( + 'aksrt-links-page', + $base_uri . '/assets/js/links-page.js', + ['jquery'], + (string) filemtime($links_js), + true + ); + } + } + + // 文章导航页(zibll postsnavs.php) + if (is_page_template('templates/pages/page-posts-navs.php')) { + $pn_css = get_template_directory() . '/assets/css/routes/posts-navs.css'; + if (file_exists($pn_css)) { + wp_enqueue_style( + 'aksrt-route-posts-navs', + $base_uri . '/assets/css/routes/posts-navs.css', + ['aksrt-tokens'], + (string) filemtime($pn_css) + ); + } + $section_navs = get_template_directory() . '/assets/js/section-navs.js'; + if (file_exists($section_navs)) { + wp_enqueue_script( + 'aksrt-section-navs', + $base_uri . '/assets/js/section-navs.js', + ['jquery'], + (string) filemtime($section_navs), + true + ); + } + $pn_js = get_template_directory() . '/assets/js/posts-navs-page.js'; + if (file_exists($pn_js)) { + wp_enqueue_script( + 'aksrt-posts-navs-page', + $base_uri . '/assets/js/posts-navs-page.js', + ['jquery'], + (string) filemtime($pn_js), + true + ); + } + } + + // 用户身份认证页(zibll user-auth.php) + if (is_page_template('templates/pages/page-user-auth.php')) { + $ua_css = get_template_directory() . '/assets/css/routes/user-auth.css'; + if (file_exists($ua_css)) { + wp_enqueue_style( + 'aksrt-route-user-auth', + $base_uri . '/assets/css/routes/user-auth.css', + ['aksrt-tokens'], + (string) filemtime($ua_css) + ); + } + $ua_js = get_template_directory() . '/assets/js/user-auth-page.js'; + if (file_exists($ua_js)) { + wp_enqueue_script( + 'aksrt-user-auth-page', + $base_uri . '/assets/js/user-auth-page.js', + [], + (string) filemtime($ua_js), + true + ); + } + } + // 交易中心共享壳层(购物车 / 订单 / 收益) $is_commerce_center = is_page_template('templates/pages/page-orders.php') || is_page_template('templates/pages/page-cart.php') diff --git a/src/AKSRT/Core/Templates.php b/src/AKSRT/Core/Templates.php index f4e1f2bf..e653710f 100644 --- a/src/AKSRT/Core/Templates.php +++ b/src/AKSRT/Core/Templates.php @@ -46,6 +46,8 @@ class Templates { '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-links.php' => __('AKSRT - 网址导航', 'aksrt'), + 'templates/pages/page-user-auth.php' => __('AKSRT - 用户身份认证', 'aksrt'), 'templates/pages/page-orders.php' => __('AKSRT - 订单中心', 'aksrt'), 'templates/pages/page-revenue.php' => __('AKSRT - 收益中心', 'aksrt'), 'templates/pages/page-cart.php' => __('AKSRT - 购物车', 'aksrt'), diff --git a/src/AKSRT/User/AuthApply.php b/src/AKSRT/User/AuthApply.php new file mode 100644 index 00000000..f6107796 --- /dev/null +++ b/src/AKSRT/User/AuthApply.php @@ -0,0 +1,207 @@ + + * } + */ + public static function settings(): array { + $all = class_exists('\AKSRT\Settings\Settings') + ? (array) \AKSRT\Settings\Settings::get_all() + : []; + $enabled = !isset($all['user_verify_enabled']) || !empty($all['user_verify_enabled']); + $types_raw = (string) ($all['user_verify_apply_option'] ?? "real_name|实名认证\norganization|企业认证"); + $types = []; + foreach (preg_split('/\r\n|\r|\n/', $types_raw) as $line) { + $line = trim($line); + if ($line === '') { + continue; + } + $parts = array_map('trim', explode('|', $line, 2)); + $slug = sanitize_key($parts[0] ?? ''); + $label = $parts[1] ?? $parts[0]; + if ($slug === '') { + continue; + } + $types[] = ['slug' => $slug, 'label' => (string) $label]; + } + return [ + 'enabled' => $enabled, + 'allow_apply' => !isset($all['user_verify_allow_apply']) || !empty($all['user_verify_allow_apply']), + 'public_display' => !isset($all['user_verify_public_display']) || !empty($all['user_verify_public_display']), + 'badge_text' => (string) ($all['user_verify_badge_text'] ?? __('已认证', 'aksrt')), + 'limit_level' => max(0, (int) ($all['user_verify_limit_level'] ?? 0)), + 'desc' => (string) ($all['user_verify_desc'] ?? ''), + 'disable_desc' => (string) ($all['user_verify_disable_desc'] ?? ''), + 'types' => $types, + ]; + } + + public static function get_status(int $user_id): string { + if ($user_id <= 0) { + return ''; + } + return sanitize_key((string) get_user_meta($user_id, self::STATUS_META, true)); + } + + public static function is_verified(int $user_id): bool { + return self::get_status($user_id) === 'verified'; + } + + /** + * @return array + */ + public static function get_auth_info(int $user_id): array { + $info = get_user_meta($user_id, self::INFO_META, true); + return is_array($info) ? $info : []; + } + + /** + * @return array + */ + public static function get_apply_payload(int $user_id): array { + $apply = get_user_meta($user_id, self::APPLY_META, true); + return is_array($apply) ? $apply : []; + } + + public function ajax_apply(): void { + if (!is_user_logged_in()) { + wp_send_json(['error' => 1, 'msg' => __('请先登录', 'aksrt')]); + } + if (!check_ajax_referer('aksrt_user_auth_apply', '_wpnonce', false)) { + wp_send_json(['error' => 1, 'msg' => __('安全校验失败', 'aksrt')]); + } + + $user_id = get_current_user_id(); + $s = self::settings(); + if (!$s['enabled'] || !$s['allow_apply']) { + wp_send_json(['error' => 1, 'msg' => __('暂不能申请身份认证', 'aksrt')]); + } + if (self::is_verified($user_id)) { + wp_send_json(['error' => 1, 'msg' => __('您已是认证用户', 'aksrt')]); + } + if (self::get_status($user_id) === 'pending') { + wp_send_json(['error' => 1, 'msg' => __('您的申请正在审核中', 'aksrt')]); + } + if ($s['limit_level'] > 0 && class_exists('\AKSRT\User\Level')) { + $lv = (int) Level::get_level($user_id); + if ($lv < $s['limit_level']) { + wp_send_json([ + 'error' => 1, + 'msg' => sprintf( + /* translators: %d: required level */ + __('等级不足,需达到 LV.%d 后申请', 'aksrt'), + $s['limit_level'] + ), + ]); + } + } + + $name = isset($_POST['name']) ? sanitize_text_field(wp_unslash((string) $_POST['name'])) : ''; + $desc = isset($_POST['desc']) ? sanitize_textarea_field(wp_unslash((string) $_POST['desc'])) : ''; + $msg = isset($_POST['msg']) ? sanitize_textarea_field(wp_unslash((string) $_POST['msg'])) : ''; + $type = isset($_POST['type']) ? sanitize_key((string) wp_unslash($_POST['type'])) : ''; + if ($name === '') { + wp_send_json(['error' => 1, 'msg' => __('请填写认证身份名称', 'aksrt')]); + } + + $images = []; + if (!empty($_FILES['image']['name'])) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + require_once ABSPATH . 'wp-admin/includes/media.php'; + require_once ABSPATH . 'wp-admin/includes/image.php'; + + $files = $_FILES['image']; + // Normalize multi-file upload. + $names = $files['name']; + if (!is_array($names)) { + $names = [$names]; + $tmp = [ + 'name' => $names, + 'type' => [$files['type']], + 'tmp_name' => [$files['tmp_name']], + 'error' => [$files['error']], + 'size' => [$files['size']], + ]; + $files = $tmp; + } + $count = min(3, count($files['name'])); + for ($i = 0; $i < $count; $i++) { + if (empty($files['name'][$i]) || (int) $files['error'][$i] !== UPLOAD_ERR_OK) { + continue; + } + $file = [ + 'name' => $files['name'][$i], + 'type' => $files['type'][$i], + 'tmp_name' => $files['tmp_name'][$i], + 'error' => $files['error'][$i], + 'size' => $files['size'][$i], + ]; + $_FILES['aksrt_auth_img'] = $file; + $aid = media_handle_upload('aksrt_auth_img', 0); + if (!is_wp_error($aid)) { + $url = wp_get_attachment_url((int) $aid); + if ($url) { + $images[] = $url; + } + } + } + unset($_FILES['aksrt_auth_img']); + } + + $payload = [ + 'name' => $name, + 'desc' => $desc, + 'msg' => $msg, + 'type' => $type, + 'img' => $images, + 'time' => current_time('Y-m-d H:i'), + ]; + update_user_meta($user_id, self::APPLY_META, $payload); + update_user_meta($user_id, self::STATUS_META, 'pending'); + delete_user_meta($user_id, self::REASON_META); + + /** + * @param int $user_id + * @param array $payload + */ + do_action('aksrt_user_auth_apply_submitted', $user_id, $payload); + + wp_send_json([ + 'error' => 0, + 'msg' => __('提交成功,请等待管理员审核', 'aksrt'), + 'reload' => true, + ]); + } +} diff --git a/templates/pages/page-links.php b/templates/pages/page-links.php new file mode 100644 index 00000000..4dcf05ea --- /dev/null +++ b/templates/pages/page-links.php @@ -0,0 +1,82 @@ + +
    +
    +
    +

    ' . esc_html(get_the_title()) . '

    '; + } + } + + if (function_exists('aksrt_link_get_search_cover')) { + echo aksrt_link_get_search_cover($post_id); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + $links_html = function_exists('aksrt_page_links_html') ? aksrt_page_links_html($post_id) : ''; + $content_s = !empty($s['content_s']); + $position = (string) ($s['content_position'] ?? 'top'); + + if ($position !== 'top') { + echo $links_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + if ($content_s) { + echo '
    '; + the_content(); + wp_link_pages([ + 'before' => '

    ', + 'after' => '

    ', + ]); + echo '
    '; + } + + if ($position === 'top') { + echo $links_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + endwhile; + + if (comments_open() || get_comments_number()) { + comments_template(); + } + ?> +
    + + +
    + (string) ($s['submit_title'] ?? __('申请入驻', 'aksrt')), + 'dec' => (string) ($s['submit_dec'] ?? ''), + 'sign' => !empty($s['submit_sign_s']), + 'cats' => (array) ($s['submit_cats'] ?? []), + ]); +} + +get_footer(); diff --git a/templates/pages/page-posts-navs.php b/templates/pages/page-posts-navs.php index 28e84ebe..2d371d28 100644 --- a/templates/pages/page-posts-navs.php +++ b/templates/pages/page-posts-navs.php @@ -1,65 +1,228 @@ ID : (int) get_queried_object_id(); -$categories = get_categories(['hide_empty' => true, 'orderby' => 'count', 'order' => 'DESC', 'number' => 20]); -$tags = get_tags(['hide_empty' => true, 'orderby' => 'count', 'order' => 'DESC', 'number' => 40]); - -if (function_exists('aksrt_archive_open_shell')) { - aksrt_archive_open_shell([ - 'main_class' => 'site-main container content-shell content-shell--page page-content-default', - 'has_sidebar' => false, - 'page_kind' => 'posts-navs', - ]); +$show_cat_id = get_post_meta($post_id, 'navs_show_cat_id', true); +if (!is_array($show_cat_id)) { + $show_cat_id = $show_cat_id !== '' && $show_cat_id !== null + ? array_values(array_filter(array_map('intval', (array) $show_cat_id))) + : []; +} else { + $show_cat_id = array_values(array_filter(array_map('intval', $show_cat_id))); } + +$page_desc = (string) get_post_meta($post_id, 'navs_page_desc', true); +$page_name = (string) get_post_meta($post_id, 'navs_page_name', true); +$title = get_the_title($post_id); +$page_name = $page_name !== '' ? $page_name : $title; + +$all_cats = get_categories([ + 'orderby' => 'term_group', + 'order' => 'ASC', + 'hide_empty' => false, +]); + +$can_admin = is_user_logged_in() && current_user_can('manage_options'); + +get_header(); ?> -
    - esc_html(get_the_title() ?: __('文章导航', 'aksrt')), + + +
    +
    +
    +

    + + + + + [] + + + +

    +
    +
    + +
    +
    + true]); + } elseif (function_exists('aksrt_get_share_html')) { + echo aksrt_get_share_html(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + ?> +
    +
    +
    +
    +
    + +
    +
    +
    +
    + (int) $c_id, + 'numberposts' => -1, + 'post_status' => 'publish', + 'orderby' => 'date', + 'order' => 'DESC', + 'suppress_filters' => false, ]); - ?> + if (empty($posts)) { + continue; + } + $section_id = 'navs-cat-' . (int) $c_id; + echo '
    '; + echo '

    ' . esc_html($the_cat->name) . '

    '; + echo '
      '; + foreach ($posts as $p) { + $time_tip = get_the_time('Y年m月d日 H:i', $p); + $time_ago = human_time_diff(get_the_time('U', $p), current_time('timestamp')) . __('前', 'aksrt'); + $views = function_exists('aksrt_get_post_views') ? (int) aksrt_get_post_views($p->ID) : 0; + $likes = (int) get_post_meta($p->ID, 'like', true); + if ($likes <= 0) { + $likes = (int) get_post_meta($p->ID, 'aksrt_like', true); + } + $post_tip = sprintf( + /* translators: 1: views 2: likes */ + __('阅读(%1$s) 点赞(%2$s)', 'aksrt'), + (string) $views, + (string) $likes + ); + $subtitle = (string) get_post_meta($p->ID, 'subtitle', true); + if ($subtitle === '') { + $subtitle = (string) get_post_meta($p->ID, 'aksrt_subtitle', true); + } + echo '
    • '; + echo '
    • '; + } + echo '
    '; + } + } elseif ($can_admin) { + echo '
    '; + echo '

    ' . esc_html__('尚未选择分类,请点击标题旁「设置」配置要展示的分类。', 'aksrt') . '

    '; + echo '
    '; + } else { + echo '
    '; + echo '

    ' . esc_html__('暂无导航内容', 'aksrt') . '

    '; + echo '
    '; + } + ?> +
    +
    +
    + +
    -
    - -
    -

    -
    - - - name); ?> - count); ?> - - + +
    - - - -
    -

    -
    - - name); ?> - +
    +
    +

    +
    + '), '>'); + echo '' + . esc_html($c_name) + . '' . (int) $the_cat->count . ''; + } + ?>
    -
    - +
    +
    + + + + + + false]); -} +endif; get_footer(); diff --git a/templates/pages/page-user-auth.php b/templates/pages/page-user-auth.php new file mode 100644 index 00000000..62a6ef8e --- /dev/null +++ b/templates/pages/page-user-auth.php @@ -0,0 +1,203 @@ + true, 'allow_apply' => true]; +$user_id = get_current_user_id(); +$status = $user_id && class_exists(AuthApply::class) ? AuthApply::get_status($user_id) : ''; +$info = $user_id && class_exists(AuthApply::class) ? AuthApply::get_auth_info($user_id) : []; +$apply = $user_id && class_exists(AuthApply::class) ? AuthApply::get_apply_payload($user_id) : []; +$login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url(get_permalink()) : wp_login_url(get_permalink()); + +get_header(); +?> +
    +
    +
    + +
    +
    + '

    ', + 'after' => '

    ', + ]); + ?> +
    +
    + + +
    + +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    + +
    + + +
    + +
    +
    + +
    +
    +
    +
    +

    + +

    +
    +
    + +
    +
    +
    + +
    + + +
    + + +
    + + +
    + +
    + +
    + +
    + +
    +
    + +
    + +
    + + 0 && class_exists('\AKSRT\User\Level')) { + $lv = (int) \AKSRT\User\Level::get_level($user_id); + if ($lv < $limit) { + $blocked_level = true; + } + } + ?> + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + +
    + + ' . esc_html(sprintf(__('原因:%s', 'aksrt'), $reason)) . '
    '; + } + ?> +
    + + +
    + +
    + +
    +
    + +
    + +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    + + + +
    +
    + + +
    + + +
    + + +
    +