Skip to content

Commit 1bbac9c

Browse files
committed
Add custom walker for section hierarchy display and sync products from section term meta
1 parent 59dab42 commit 1bbac9c

12 files changed

Lines changed: 153 additions & 42 deletions

includes/admin/class-product-section-selector.php

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ class Product_Section_Selector {
4343
* Constructor.
4444
*/
4545
public function __construct() {
46+
Hook_Registry::add_filter( 'wp_terms_checklist_args', array( $this, 'use_hierarchical_section_walker' ), 10, 2 );
47+
4648
if ( 0 === (int) wzkb_get_option( 'multi_product', 0 ) ) {
4749
return;
4850
}
@@ -361,6 +363,7 @@ public function sync_sections_after_rest_save( \WP_Post $post, \WP_REST_Request
361363
$this->assign_section_terms( $post->ID, $request['meta']['_wzkb_section_ids'] );
362364
}
363365

366+
$this->sync_products_from_sections( $post->ID );
364367
$this->sync_product_meta( $post->ID );
365368
$this->sync_section_product_meta( $post->ID );
366369
}
@@ -382,18 +385,33 @@ public function maybe_sync_sections_during_save( $post_id, $post ) {
382385

383386
$this->is_syncing = true;
384387

385-
$section_ids = get_post_meta( $post_id, '_wzkb_section_ids', true );
388+
// Quick Edit saves taxonomy terms directly without updating post meta.
389+
// Read back what WordPress just saved so meta stays in sync, then let
390+
// sync_products_from_sections derive product assignments from sections.
391+
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- action detection only, no data used.
392+
$is_quick_edit = isset( $_POST['action'] ) && 'inline-save' === $_POST['action']; // phpcs:ignore WordPress.Security.NonceVerification.Missing
386393

387-
if ( empty( $section_ids ) ) {
394+
if ( $is_quick_edit ) {
388395
$current_terms = get_the_terms( $post_id, 'wzkb_category' );
389-
if ( ! empty( $current_terms ) && ! is_wp_error( $current_terms ) ) {
390-
$section_ids = array_map( 'absint', wp_list_pluck( $current_terms, 'term_id' ) );
391-
update_post_meta( $post_id, '_wzkb_section_ids', $section_ids );
396+
$section_ids = ( ! empty( $current_terms ) && ! is_wp_error( $current_terms ) )
397+
? array_map( 'absint', wp_list_pluck( $current_terms, 'term_id' ) )
398+
: array();
399+
update_post_meta( $post_id, '_wzkb_section_ids', $section_ids );
400+
} else {
401+
$section_ids = get_post_meta( $post_id, '_wzkb_section_ids', true );
402+
403+
if ( empty( $section_ids ) ) {
404+
$current_terms = get_the_terms( $post_id, 'wzkb_category' );
405+
if ( ! empty( $current_terms ) && ! is_wp_error( $current_terms ) ) {
406+
$section_ids = array_map( 'absint', wp_list_pluck( $current_terms, 'term_id' ) );
407+
update_post_meta( $post_id, '_wzkb_section_ids', $section_ids );
408+
}
392409
}
393410
}
394411

395412
try {
396413
$this->assign_section_terms( $post_id, $section_ids );
414+
$this->sync_products_from_sections( $post_id );
397415
$this->sync_product_meta( $post_id );
398416
$this->sync_section_product_meta( $post_id );
399417
} finally {
@@ -473,6 +491,47 @@ private function sync_product_meta( $post_id ) {
473491
update_post_meta( $post_id, '_wzkb_product_ids', array_values( $product_ids ) );
474492
}
475493

494+
/**
495+
* Merge product assignments derived from assigned sections.
496+
*
497+
* Each section can carry a `product_id` term meta. Any product referenced
498+
* this way is added to the post's wzkb_product taxonomy so the article is
499+
* always discoverable from its product. Existing product assignments are
500+
* preserved — this only ever adds, never removes.
501+
*
502+
* @param int $post_id Post ID.
503+
*/
504+
private function sync_products_from_sections( int $post_id ): void {
505+
$section_terms = get_the_terms( $post_id, 'wzkb_category' );
506+
if ( empty( $section_terms ) || is_wp_error( $section_terms ) ) {
507+
return;
508+
}
509+
510+
$derived_ids = array();
511+
foreach ( $section_terms as $section ) {
512+
$product_id = (int) get_term_meta( $section->term_id, 'product_id', true );
513+
if ( $product_id > 0 ) {
514+
$derived_ids[] = $product_id;
515+
}
516+
}
517+
518+
if ( empty( $derived_ids ) ) {
519+
return;
520+
}
521+
522+
$current_terms = get_the_terms( $post_id, 'wzkb_product' );
523+
$current_ids = ( ! empty( $current_terms ) && ! is_wp_error( $current_terms ) )
524+
? array_map( 'absint', wp_list_pluck( $current_terms, 'term_id' ) )
525+
: array();
526+
527+
$to_add = array_diff( $derived_ids, $current_ids );
528+
if ( empty( $to_add ) ) {
529+
return;
530+
}
531+
532+
wp_set_post_terms( $post_id, array_values( array_unique( array_merge( $current_ids, $derived_ids ) ) ), 'wzkb_product', false );
533+
}
534+
476535
/**
477536
* Backfill product_id term meta on sections that have none.
478537
*
@@ -500,8 +559,7 @@ private function sync_section_product_meta( int $post_id ): void {
500559
$product_id = (int) $product_terms[0]->term_id;
501560

502561
foreach ( $section_terms as $section ) {
503-
$existing_product_id = get_term_meta( $section->term_id, 'product_id', true );
504-
if ( '' === $existing_product_id ) {
562+
if ( 0 === (int) get_term_meta( $section->term_id, 'product_id', true ) ) {
505563
update_term_meta( $section->term_id, 'product_id', $product_id );
506564
}
507565
}
@@ -622,4 +680,21 @@ private function get_ui_strings(): array {
622680
'productOverflow' => esc_html__( 'Showing first %1$s products out of %2$s. Refine your search.', 'knowledgebase' ),
623681
);
624682
}
683+
684+
/**
685+
* Inject Walker_Section_Checklist for the wzkb_category checklist (Quick Edit and post editor).
686+
*
687+
* @param array $args wp_terms_checklist() arguments.
688+
* @param int $post_id Post ID (unused).
689+
* @return array
690+
*/
691+
public function use_hierarchical_section_walker( array $args, $post_id ): array {
692+
if ( ! isset( $args['taxonomy'] ) || 'wzkb_category' !== $args['taxonomy'] ) {
693+
return $args;
694+
}
695+
696+
$args['walker'] = new Walker_Section_Checklist();
697+
698+
return $args;
699+
}
625700
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
/**
3+
* Custom walker for wzkb_category checkbox lists with hierarchical labels.
4+
*
5+
* @since 3.0.0
6+
*
7+
* @package WebberZone\\Knowledge_Base\\Admin
8+
*/
9+
10+
namespace WebberZone\Knowledge_Base\Admin;
11+
12+
// If this file is called directly, abort.
13+
if ( ! defined( 'WPINC' ) ) {
14+
die;
15+
}
16+
17+
/**
18+
* Custom walker for wzkb_category checkbox lists (Quick Edit, term metaboxes).
19+
*
20+
* Mirrors the label format used by Walker_Category_Dropdown: shows the full
21+
* product > section hierarchy path via wzkb_get_term_hierarchy_path() so
22+
* child terms are unambiguously identified without relying on visual context.
23+
*
24+
* @since 3.0.0
25+
*/
26+
class Walker_Section_Checklist extends \Walker_Category_Checklist {
27+
28+
/**
29+
* Start element output.
30+
*
31+
* @param string $output Passed by reference.
32+
* @param object $data_object Term object.
33+
* @param int $depth Depth of term in tree.
34+
* @param array $args Walker args.
35+
* @param int $current_object_id Current post/object ID.
36+
*/
37+
public function start_el( &$output, $data_object, $depth = 0, $args = array(), $current_object_id = 0 ) {
38+
$term = $data_object;
39+
$taxonomy = ! empty( $args['taxonomy'] ) ? $args['taxonomy'] : 'category';
40+
$name = 'tax_input[' . $taxonomy . ']';
41+
42+
$args['popular_cats'] = ! empty( $args['popular_cats'] ) ? array_map( 'intval', $args['popular_cats'] ) : array();
43+
$class = in_array( $term->term_id, $args['popular_cats'], true ) ? ' class="popular-category"' : '';
44+
$args['selected_cats'] = ! empty( $args['selected_cats'] ) ? array_map( 'intval', $args['selected_cats'] ) : array();
45+
46+
$is_selected = in_array( $term->term_id, $args['selected_cats'], true );
47+
$li_attributes = $is_selected ? ' aria-checked="true"' : '';
48+
$checked = checked( $is_selected, true, false );
49+
$disabled = ! empty( $args['disabled'] ) ? ' disabled="disabled"' : '';
50+
$checkbox_id = 'in-' . $taxonomy . '-' . $term->term_id;
51+
$pad = str_repeat( '&nbsp;', $depth * 3 );
52+
$label = esc_html( wzkb_get_term_hierarchy_path( $term, true, ' > ' ) );
53+
54+
$output .= "\n<li id='{$taxonomy}-{$term->term_id}'{$class}{$li_attributes}>" .
55+
"<label class='selectit' for='{$checkbox_id}'>" .
56+
"<input value='{$term->term_id}' type='checkbox' name='{$name}[]' id='{$checkbox_id}'{$disabled}{$checked} /> " .
57+
$pad . $label .
58+
'</label>';
59+
}
60+
}

includes/admin/css/editor-sections-panel-rtl.css

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,6 @@
5454
margin: 3px 0;
5555
}
5656

57-
.wzkb-editor-sections__node--child {
58-
margin-top: 6px;
59-
}
60-
6157
.wzkb-editor-sections__empty {
6258
font-style: italic;
6359
margin: 0;

includes/admin/css/editor-sections-panel-rtl.min.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

includes/admin/css/editor-sections-panel.css

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,6 @@
5454
margin: 3px 0;
5555
}
5656

57-
.wzkb-editor-sections__node--child {
58-
margin-top: 6px;
59-
}
60-
6157
.wzkb-editor-sections__empty {
6258
font-style: italic;
6359
margin: 0;

includes/admin/css/editor-sections-panel.min.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

includes/admin/js/classic-sections-metabox.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,9 +293,9 @@
293293
};
294294

295295
const appendTree = (container, nodes, level) => {
296+
const prefix = level > 0 ? '\u00a0'.repeat( ( level - 1 ) * 3 ) + '\u21b3\u00a0' : '';
296297
nodes.forEach((node) => {
297298
const row = createElement('div', 'wzkb-classic-sections__node');
298-
row.style.marginLeft = `${level * 16}px`;
299299

300300
const checkbox = createElement('input');
301301
checkbox.type = 'checkbox';
@@ -310,7 +310,7 @@
310310
updateHiddenInputs();
311311
});
312312

313-
const label = createElement('span', 'wzkb-classic-sections__node-label', node.name);
313+
const label = createElement('span', 'wzkb-classic-sections__node-label', prefix + node.name);
314314
row.appendChild(checkbox);
315315
row.appendChild(label);
316316
container.appendChild(row);

0 commit comments

Comments
 (0)