__( 'FAQs', 'accordion-faq-plugin' ),
'priority' => 50,
'callback' => array( $this, 'render_tab' ),
);
return $tabs;
}
/**
* Render the product FAQ tab, honoring the per-product selection.
*
* @return void
*/
public function render_tab() {
global $product;
$product_id = 0;
if ( is_object( $product ) && method_exists( $product, 'get_id' ) ) {
$product_id = $product->get_id();
} elseif ( function_exists( 'get_the_ID' ) ) {
$product_id = (int) get_the_ID();
}
$args = array( 'search' => true );
$ids = $product_id ? (array) get_post_meta( $product_id, self::META_IDS, true ) : array();
$ids = array_values( array_filter( array_map( 'absint', $ids ) ) );
$cat = $product_id ? (string) get_post_meta( $product_id, self::META_CAT, true ) : '';
if ( ! empty( $ids ) ) {
$args['ids'] = $ids;
} elseif ( '' !== $cat ) {
$args['category'] = $cat;
} else {
/**
* Filter the fallback FAQ category slug shown on product pages when a
* product has no explicit selection.
*
* @param string $slug Category slug (empty = all FAQs).
* @param int $product_id Product ID.
*/
$args['category'] = apply_filters( 'accordion_faq_woo_category', '', $product_id );
}
$faq_html = accordion_faq()->renderer()->render( $args );
if ( '' === $faq_html ) {
return;
}
echo '
' . esc_html__( 'Frequently Asked Questions', 'accordion-faq-plugin' ) . '
';
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $faq_html is assembled from per-value escaped template partials and the_content-filtered post markup; re-escaping would corrupt valid HTML (embeds, etc.).
echo $faq_html;
}
/**
* Register the per-product FAQ metabox.
*
* @return void
*/
public function add_metabox() {
if ( ! class_exists( 'WooCommerce' ) ) {
return;
}
add_meta_box(
'accordion-faq-product',
__( 'Product FAQs', 'accordion-faq-plugin' ),
array( $this, 'render_metabox' ),
'product',
'side',
'default'
);
}
/**
* Render the metabox UI.
*
* @param \WP_Post $post Product post.
* @return void
*/
public function render_metabox( $post ) {
wp_nonce_field( self::NONCE, self::NONCE );
$selected_ids = array_map( 'absint', (array) get_post_meta( $post->ID, self::META_IDS, true ) );
$selected_cat = (string) get_post_meta( $post->ID, self::META_CAT, true );
$terms = get_terms( array( 'taxonomy' => Taxonomy::TAXONOMY, 'hide_empty' => false ) );
$faqs = get_posts(
array(
'post_type' => PostType::POST_TYPE,
'post_status' => 'publish',
'posts_per_page' => 200,
'orderby' => 'title',
'order' => 'ASC',
)
);
echo '' . esc_html__( 'Choose FAQs to show on this product. Leave empty to fall back to a category or the global default.', 'accordion-faq-plugin' ) . '
';
echo '
';
echo '
';
echo '
';
echo '' . esc_html__( 'Overrides the category. Ctrl/Cmd-click to select several.', 'accordion-faq-plugin' ) . '
';
echo '
';
}
/**
* Save the per-product FAQ selection.
*
* @param int $post_id Product ID.
* @param \WP_Post $post Product.
* @return void
*/
public function save( $post_id, $post ) {
if ( ! isset( $_POST[ self::NONCE ] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST[ self::NONCE ] ) ), self::NONCE ) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
$ids = isset( $_POST['afaq_product_ids'] ) ? array_map( 'absint', (array) wp_unslash( $_POST['afaq_product_ids'] ) ) : array();
$ids = array_values( array_filter( $ids ) );
if ( empty( $ids ) ) {
delete_post_meta( $post_id, self::META_IDS );
} else {
update_post_meta( $post_id, self::META_IDS, $ids );
}
$cat = isset( $_POST['afaq_product_cat'] ) ? sanitize_title( wp_unslash( $_POST['afaq_product_cat'] ) ) : '';
if ( '' === $cat ) {
delete_post_meta( $post_id, self::META_CAT );
} else {
update_post_meta( $post_id, self::META_CAT, $cat );
}
}
}