简码 [faq_item]
if (!defined('ABSPATH')) exit;
add_action('add_meta_boxes', function(){
add_meta_box('qa_faq_box','Product Q&A','qa_faq_box_cb','product','normal','default');
});
function qa_faq_box_cb($post){
wp_nonce_field('qa_faq_save','qa_faq_nonce');
$title = get_post_meta($post->ID,'_qa_title',true);
$desc = get_post_meta($post->ID,'_qa_desc',true);
$items = get_post_meta($post->ID,'_qa_items',true);
if(!is_array($items)) $items=[];
echo '<div class="qa-admin-wrap">';
echo '<div class="qa-admin-row"><label>Section Title</label><input type="text" name="qa_title" value="'.esc_attr($title ?: 'Questions & Answers').'" /></div>';
echo '<div class="qa-admin-row"><label>Section Description</label>';
wp_editor($desc,'qa_desc',['textarea_name'=>'qa_desc','media_buttons'=>false,'teeny'=>true,'quicktags'=>true,'editor_height'=>140]);
echo '</div><div class="qa-admin-items" id="qa-admin-items">';
foreach($items as $i=>$it){
$q = isset($it['q'])?$it['q']:'';
$a = isset($it['a'])?$it['a']:'';
$eid = 'qa_item_a_'.$i.'_'.wp_generate_uuid4();
echo '<div class="qa-admin-item" data-index="'.$i.'">';
echo '<div class="qa-admin-item-head"><strong>FAQ #'.($i+1).'</strong><button type="button" class="button-link-delete qa-remove">Remove</button></div>';
echo '<div class="qa-admin-item-body"><label>Question</label><input type="text" name="qa_items_q[]" value="'.esc_attr($q).'" />';
echo '<label>Answer (Rich Text)</label>';
wp_editor($a,$eid,['textarea_name'=>'qa_items_a[]','media_buttons'=>false,'teeny'=>true,'quicktags'=>true,'editor_height'=>120]);
echo '</div></div>';
}
echo '</div><div class="qa-admin-actions"><button type="button" class="button button-primary" id="qa-add-item">Add FAQ</button></div></div>';
}
add_action('admin_enqueue_scripts', function($hook){
if(!in_array($hook,['post.php','post-new.php'])) return;
$s = get_current_screen(); if(!$s || $s->post_type!=='product') return;
wp_enqueue_editor();
wp_enqueue_script('jquery');
add_action('admin_head', function(){
echo '<style>
.qa-admin-wrap{padding:10px 0}
.qa-admin-row>label{display:block;font-weight:600;margin-bottom:6px}
.qa-admin-row input[type=text]{width:100%}
.qa-admin-items .qa-admin-item{border:1px solid #e5e5e5;border-radius:6px;margin-bottom:12px;background:#fff}
.qa-admin-item-head{display:flex;justify-content:space-between;align-items:center;padding:10px 12px;border-bottom:1px solid #eee;background:#fafafa}
.qa-admin-item-body{padding:12px}
.qa-admin-item-body label{display:block;margin:8px 0 6px;font-weight:600}
.qa-admin-item-body input[type=text]{width:100%}
.qa-admin-actions{margin-top:8px}
</style>';
});
wp_add_inline_script('jquery', <<<'JS'
jQuery(function($){
var $wrap = $("#qa-admin-items"), idx = $wrap.children(".qa-admin-item").length;
$("#qa-add-item").on("click", function(e){
e.preventDefault();
var uid = Math.random().toString(36).slice(2);
var id = "qa_item_a_" + idx + "_" + uid;
var $item = $('<div class="qa-admin-item" data-index="'+idx+'">\
<div class="qa-admin-item-head"><strong>FAQ #'+(idx+1)+'</strong><button type="button" class="button-link-delete qa-remove">Remove</button></div>\
<div class="qa-admin-item-body">\
<label>Question</label><input type="text" name="qa_items_q[]" value="" />\
<label>Answer (Rich Text)</label><textarea id="'+id+'" name="qa_items_a[]" rows="6"></textarea>\
</div></div>');
$wrap.append($item);
initEditor(id);
idx++;
});
$wrap.on("click", ".qa-remove", function(e){
e.preventDefault();
var $row = $(this).closest(".qa-admin-item");
var ta = $row.find("textarea").attr("id");
if (typeof tinymce !== "undefined" && tinymce.get(ta)) tinymce.remove(tinymce.get(ta));
$row.remove();
});
function initEditor(id){
if (window.wp && wp.editor && typeof wp.editor.initialize === 'function'){
var s = (wp.editor.getDefaultSettings ? wp.editor.getDefaultSettings() : {});
if (!s.tinymce) s.tinymce = {};
s.mediaButtons = false;
s.quicktags = true;
s.tinymce.wpautop = true;
s.tinymce.menubar = false;
s.tinymce.toolbar1 = 'bold italic | link unlink | bullist numlist | undo redo';
s.tinymce.toolbar2 = '';
s.tinymce.height = 140;
try{ wp.editor.initialize(id, s); }catch(e){}
waitForTinymce(id,function(){ if (window.switchEditors) switchEditors.go(id,'tmce'); });
return;
}
setTimeout(function(){
try{
if (typeof quicktags === 'function') quicktags({id:id});
if (typeof QTags !== 'undefined' && typeof QTags._buttonsInit === 'function') QTags._buttonsInit();
}catch(e){}
try{
if (typeof tinymce !== 'undefined'){
var baseKey=null, baseCfg=null;
if (typeof tinyMCEPreInit !== 'undefined' && tinyMCEPreInit.mceInit){
if (tinyMCEPreInit.mceInit.qa_desc){ baseCfg = jQuery.extend(true, {}, tinyMCEPreInit.mceInit.qa_desc); }
else { for (var k in tinyMCEPreInit.mceInit){ baseKey=k; break; } if (baseKey) baseCfg=jQuery.extend(true, {}, tinyMCEPreInit.mceInit[baseKey]); }
}
if (!baseCfg) baseCfg={};
baseCfg.selector = '#'+id;
baseCfg.wpautop = true;
baseCfg.menubar = false;
baseCfg.height = 140;
tinymce.init(baseCfg);
}
}catch(e){}
waitForTinymce(id,function(){ if (window.switchEditors) switchEditors.go(id,'tmce'); });
},20);
}
function waitForTinymce(id, cb){
var n=0, t=setInterval(function(){
var ed = (window.tinymce && tinymce.get(id)) || null;
if (ed || n++>40){ clearInterval(t); if (ed && cb) cb(ed); }
},50);
}
});
JS
);
});
add_action('save_post_product', function($post_id){
if(!isset($_POST['qa_faq_nonce'])||!wp_verify_nonce($_POST['qa_faq_nonce'],'qa_faq_save')) return;
if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if(!current_user_can('edit_post',$post_id)) return;
$title = isset($_POST['qa_title']) ? sanitize_text_field($_POST['qa_title']) : '';
$desc = isset($_POST['qa_desc']) ? wp_kses_post($_POST['qa_desc']) : '';
$qs = isset($_POST['qa_items_q']) && is_array($_POST['qa_items_q']) ? array_values($_POST['qa_items_q']) : [];
$as = isset($_POST['qa_items_a']) && is_array($_POST['qa_items_a']) ? array_values($_POST['qa_items_a']) : [];
$items=[]; $n=max(count($qs),count($as));
for($i=0;$i<$n;$i++){
$q = isset($qs[$i]) ? sanitize_text_field($qs[$i]) : '';
$a = isset($as[$i]) ? wp_kses_post($as[$i]) : '';
if($q!=='' || $a!=='') $items[]=['q'=>$q,'a'=>$a];
}
update_post_meta($post_id,'_qa_title',$title ?: 'Questions & Answers');
update_post_meta($post_id,'_qa_desc',$desc);
update_post_meta($post_id,'_qa_items',$items);
});
if (!shortcode_exists('qa_faq')) {
add_shortcode('qa_faq', function($atts,$content=''){
$a = shortcode_atts(['title'=>'','desc'=>'','use_product'=>'1'],$atts,'qa_faq');
$id = 'qa_'.wp_generate_uuid4();
$GLOBALS['__qa_items']=[]; do_shortcode($content); $items_sc=$GLOBALS['__qa_items'];
$items=[]; $title='Questions & Answers'; $desc='';
if($a['use_product']==='1' && function_exists('is_product') && is_product()){
$pid=get_queried_object_id();
$items = get_post_meta($pid,'_qa_items',true); if(!is_array($items)) $items=[];
$title = get_post_meta($pid,'_qa_title',true) ?: $title;
$desc = get_post_meta($pid,'_qa_desc',true) ?: '';
}
if(count($items_sc)) $items=$items_sc;
if($a['title']!=='') $title=$a['title'];
if($a['desc']!=='') $desc =$a['desc'];
$limit=6;
$html = '<div class="qa-wrap" id="'.esc_attr($id).'">';
$html .= '<div class="qa-head"><h2 class="qa-title">'.esc_html($title).'</h2>';
if($desc!==''){ $html.='<div class="qa-desc">'.wp_kses_post($desc).'</div>'; }
$html .= '</div><div class="qa-box"><div class="qa-acc">';
foreach($items as $i=>$it){
$q = isset($it['q'])?$it['q']:'';
$a_content = isset($it['a'])?$it['a']:'';
$html .= '<div class="qa-item'.($i>=$limit?' qa-hidden':'').'">';
$html .= '<button type="button" class="qa-q" aria-expanded="false"><span class="qa-q-text">'.esc_html($q).'</span><span class="qa-arrow" aria-hidden="true"></span></button>';
$html .= '<div class="qa-a" hidden><div class="qa-a-inner">'.do_shortcode(wp_kses_post($a_content)).'</div></div></div>';
}
$html .= '</div>';
if(count($items)>$limit){ $html.='<div class="qa-viewall-wrap"><button class="qa-viewall" data-qa-open="'.esc_attr($id).'">View All ></button></div>'; }
$html .= '</div>';
if(count($items)>0){
$html .= '<div class="qa-modal" id="'.esc_attr($id).'__modal" aria-hidden="true"><div class="qa-overlay" data-qa-close></div><aside class="qa-panel"><div class="qa-modal-header"><div class="qa-modal-title">'.esc_html($title).'</div><button class="qa-close" data-qa-close aria-label="Close">×</button></div><div class="qa-modal-body"><div class="qa-acc qa-acc-modal">';
foreach($items as $it){
$q = isset($it['q'])?$it['q']:'';
$a_content = isset($it['a'])?$it['a']:'';
$html .= '<div class="qa-item"><button type="button" class="qa-q" aria-expanded="false"><span class="qa-q-text">'.esc_html($q).'</span><span class="qa-arrow" aria-hidden="true"></span></button><div class="qa-a" hidden><div class="qa-a-inner">'.do_shortcode(wp_kses_post($a_content)).'</div></div></div>';
}
$html .= '</div></div></aside></div>';
}
$html .= '</div>';
qa_faq_assets_once();
return $html;
});
add_shortcode('faq_item', function($atts,$content=''){
$q = isset($atts['q']) ? $atts['q'] : '';
if(!isset($GLOBALS['__qa_items'])||!is_array($GLOBALS['__qa_items'])) $GLOBALS['__qa_items']=[];
$GLOBALS['__qa_items'][]=['q'=>$q,'a'=>$content];
return '';
});
}
function qa_faq_assets_once(){
static $done=false; if($done) return; $done=true;
add_action('wp_footer', function(){
$css = '
.qa-wrap{max-width:1000px;margin:40px auto}
.qa-title{font-family:Marcellus,serif;font-size:40px;line-height:1.2;margin:0 0 12px;text-align:center}
.qa-desc{max-width:900px;margin:0 auto 24px;text-align:center;opacity:.9}
.qa-box{background:#fcf9f2;padding:20px 50px;border-radius:12px}
.qa-item{border-bottom:1px solid #eaeaea;padding:15px 0}
.qa-item:last-child{border-bottom:none}
.qa-wrap button,.qa-wrap .qa-q,.qa-wrap .qa-viewall,.qa-wrap .qa-close,.qa-wrap input[type="button"],.qa-wrap input[type="submit"],.qa-wrap .elementor-button{background-color:transparent!important;color:inherit!important;border:0 none!important;border-radius:0!important;font-family:"Satoshi-Medium","Satoshi",Arial,sans-serif!important;font-weight:600!important;box-shadow:none!important;padding:0!important}
.qa-q{width:100%;display:flex;align-items:center;justify-content:space-between;gap:16px;background:transparent;border:0;cursor:pointer;border-radius:0!important;color:inherit;padding:0}
.qa-q:hover,.qa-q:focus{background:transparent!important;color:inherit!important;box-shadow:none!important;outline:none!important}
.qa-q:focus-visible{outline:none!important;box-shadow:none!important}
.qa-q-text{font-family:"Satoshi-Medium","Satoshi","Helvetica Neue",Arial,sans-serif;font-size:18px;line-height:1.5;text-align:left}
.qa-arrow{width:18px;height:18px;position:relative;flex:0 0 18px}
.qa-arrow:before,.qa-arrow:after{content:"";position:absolute;left:0;right:0;margin:auto;width:10px;height:2px;background:#222;top:6px;transform-origin:center}
.qa-arrow:after{transform:rotate(90deg)}
.qa-item.open .qa-arrow:after{opacity:0}
.qa-a-inner{font-size:16px;line-height:1.8}
.qa-a-inner p{margin:0 0 10px}
.qa-a-inner ul,.qa-a-inner ol{margin:0 0 12px 1.25em}
.qa-viewall-wrap{text-align:center;margin-top:10px}
.qa-viewall{background:none;border:0;font-size:14px;text-decoration:underline;cursor:pointer}
.qa-hidden{display:none}
.qa-modal{position:fixed;inset:0;z-index:999999;display:none}
.qa-modal[aria-hidden="false"]{display:block}
.qa-overlay{position:absolute;inset:0;background:rgba(0,0,0,.45)}
.qa-panel{position:absolute;top:0;right:0;width:40vw;height:100vh;background:#fff;box-shadow:-10px 0 30px rgba(0,0,0,.15);transform:translateX(100%);transition:transform .3s ease}
.qa-modal[aria-hidden="false"] .qa-panel{transform:translateX(0)}
.qa-modal-header{display:flex;align-items:center;justify-content:space-between;padding:18px 20px;border-bottom:1px solid #eee}
.qa-modal-title{font-family:Marcellus,serif;font-size:24px}
.qa-close{font-size:28px;line-height:1;background:none;border:0;cursor:pointer}
.qa-modal-body{height:calc(100vh - 61px);overflow:auto;padding:12px 24px}
@media(max-width:768px){
.qa-title{font-size:28px}
.qa-wrap{max-width:100%;margin:24px auto}
.qa-box{padding:20px 10px;border-radius:8px}
.qa-item{padding:15px 0}
.qa-q-text{font-size:16px}
.qa-a-inner{font-size:14px}
.qa-panel{left:0;right:0;width:100vw;height:80vh;bottom:0;top:auto;border-radius:12px 12px 0 0;transform:translateY(100%)}
.qa-modal[aria-hidden="false"] .qa-panel{transform:translateY(0)}
.qa-modal-body{height:calc(80vh - 61px)}
}
body.qa-modal-open{overflow:hidden}
';
$js = '(function(){
function toggleItem(btn){
var item=btn.closest(".qa-item"); var ans=item.querySelector(".qa-a");
var expanded=btn.getAttribute("aria-expanded")==="true";
btn.setAttribute("aria-expanded",expanded?"false":"true");
item.classList.toggle("open",!expanded);
if(ans){ if(expanded){ans.setAttribute("hidden","hidden");} else {ans.removeAttribute("hidden");} }
}
document.addEventListener("click",function(e){
var q=e.target.closest(".qa-q"); if(q){ e.preventDefault(); toggleItem(q); return; }
var openBtn=e.target.closest("[data-qa-open]"); if(openBtn){ var id=openBtn.getAttribute("data-qa-open"); var m=document.getElementById(id+"__modal"); if(m){ m.setAttribute("aria-hidden","false"); document.body.classList.add("qa-modal-open"); } return; }
if(e.target.closest("[data-qa-close]")||e.target.classList.contains("qa-overlay")){ var m=e.target.closest(".qa-modal")||document.querySelector(".qa-modal[aria-hidden=\'false\']"); if(m){ m.setAttribute("aria-hidden","true"); document.body.classList.remove("qa-modal-open"); } return; }
});
document.addEventListener("keydown",function(e){ if(e.key==="Escape"){ var m=document.querySelector(".qa-modal[aria-hidden=\'false\']"); if(m){ m.setAttribute("aria-hidden","true"); document.body.classList.remove("qa-modal-open"); } }});
})();';
echo '<style>'.$css.'</style><script>'.$js.'</script>';
});
}