Skip to content

Commit 1e18383

Browse files
committed
New Product widget
1 parent cb6cb91 commit 1e18383

File tree

6 files changed

+244
-10
lines changed

6 files changed

+244
-10
lines changed

includes/class-main.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,5 +218,6 @@ public function register_widgets() {
218218
register_widget( '\WebberZone\Knowledge_Base\Widgets\Articles_Widget' );
219219
register_widget( '\WebberZone\Knowledge_Base\Widgets\Sections_Widget' );
220220
register_widget( '\WebberZone\Knowledge_Base\Widgets\Breadcrumb_Widget' );
221+
register_widget( '\WebberZone\Knowledge_Base\Widgets\Products_Widget' );
221222
}
222223
}

includes/frontend/class-display.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@ public static function get_categories_list( $term_id, $level = 0, $args = array(
506506
);
507507

508508
$args = wp_parse_args( $args, $defaults );
509+
$args = Helpers::sanitize_args( $args );
509510

510511
// Get Knowledge Base Sections.
511512
$sections = self::fetch_terms(
@@ -548,6 +549,74 @@ public static function get_categories_list( $term_id, $level = 0, $args = array(
548549
return $output;
549550
}
550551

552+
/**
553+
* Get top-level sections for a given product.
554+
*
555+
* @since 2.3.0
556+
*
557+
* @param int $product_id Product term ID.
558+
* @return array Array of section term objects.
559+
*/
560+
public static function get_sections_by_product( $product_id ) {
561+
global $wpdb;
562+
563+
if ( empty( $product_id ) ) {
564+
return array();
565+
}
566+
567+
$section_ids = $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
568+
$wpdb->prepare(
569+
"SELECT term_id FROM $wpdb->termmeta WHERE meta_key = %s AND meta_value = %d",
570+
'product_id',
571+
$product_id
572+
)
573+
);
574+
575+
if ( empty( $section_ids ) ) {
576+
return array();
577+
}
578+
579+
$terms = get_terms(
580+
array(
581+
'taxonomy' => 'wzkb_category',
582+
'hide_empty' => false,
583+
'parent' => 0,
584+
'include' => $section_ids,
585+
)
586+
);
587+
588+
return is_array( $terms ) ? $terms : array();
589+
}
590+
591+
/**
592+
* Get a hierarchical list of sections for a given product.
593+
*
594+
* @since 2.3.0
595+
*
596+
* @param int $product_id Product term ID.
597+
* @param array $args Arguments for display.
598+
* @param int $level Level of the loop (for indentation/nesting).
599+
* @return string HTML output.
600+
*/
601+
public static function get_product_sections_list( $product_id, $args = array(), $level = 0 ) {
602+
$sections = self::get_sections_by_product( $product_id );
603+
$output = '';
604+
605+
if ( ! empty( $sections ) ) {
606+
$output .= '<ul class="wzkb_product_sections wzkb_ul_level_' . (int) $level . '">';
607+
++$level;
608+
foreach ( $sections as $section ) {
609+
$output .= '<li>';
610+
$output .= '<a href="' . esc_url( get_term_link( $section ) ) . '">' . esc_html( $section->name ) . '</a>';
611+
$output .= self::get_categories_list( $section->term_id, $level, $args );
612+
$output .= '</li>';
613+
}
614+
$output .= '</ul>';
615+
}
616+
617+
return $output;
618+
}
619+
551620
/**
552621
* Render the category-based view of the knowledge base.
553622
*

includes/functions.php

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
use WebberZone\Knowledge_Base\Frontend\Media_Handler;
99
use WebberZone\Knowledge_Base\Frontend\Related;
1010
use WebberZone\Knowledge_Base\Util\Helpers;
11+
use WebberZone\Knowledge_Base\Frontend\Display;
12+
use WebberZone\Knowledge_Base\Frontend\Breadcrumbs;
13+
use WebberZone\Knowledge_Base\Frontend\Search;
1114

1215
// If this file is called directly, abort.
1316
if ( ! defined( 'WPINC' ) ) {
@@ -25,7 +28,7 @@
2528
* @return string Knowledge Base output.
2629
*/
2730
function wzkb_knowledge( $args = array() ) {
28-
return \WebberZone\Knowledge_Base\Frontend\Display::get_knowledge_base( $args );
31+
return Display::get_knowledge_base( $args );
2932
}
3033

3134
/**
@@ -37,7 +40,7 @@ function wzkb_knowledge( $args = array() ) {
3740
* @return string HTML output with the categories.
3841
*/
3942
function wzkb_categories_list( $term_id, $level = 0, $args = array() ) {
40-
return \WebberZone\Knowledge_Base\Frontend\Display::get_categories_list( $term_id, $level, $args );
43+
return Display::get_categories_list( $term_id, $level, $args );
4144
}
4245

4346

@@ -50,7 +53,7 @@ function wzkb_categories_list( $term_id, $level = 0, $args = array() ) {
5053
* @return string|bool Formatted shortcode output. False if not a WZKB post type archive or post.
5154
*/
5255
function wzkb_get_breadcrumb( $args = array() ) {
53-
return \WebberZone\Knowledge_Base\Frontend\Breadcrumbs::get_breadcrumb( $args );
56+
return Breadcrumbs::get_breadcrumb( $args );
5457
}
5558

5659
/**
@@ -72,7 +75,7 @@ function wzkb_breadcrumb( $args = array() ) {
7275
* @return string String when retrieving, null when displaying or if searchform.php exists.
7376
*/
7477
function wzkb_get_search_form() {
75-
return \WebberZone\Knowledge_Base\Frontend\Search::get_search_form();
78+
return Search::get_search_form();
7679
}
7780

7881
/**
@@ -211,3 +214,27 @@ function wzkb_get_kb_url() {
211214
function wzkb_the_kb_url() {
212215
echo wzkb_get_kb_url(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
213216
}
217+
218+
/**
219+
* Get a hierarchical list of KB sections for a given product.
220+
*
221+
* @param int $product_id Product term ID.
222+
* @param array $args Display arguments.
223+
* @return string HTML output.
224+
*/
225+
function wzkb_get_product_sections_list( $product_id, $args = array() ) {
226+
if ( empty( $product_id ) ) {
227+
return '';
228+
}
229+
return Display::get_product_sections_list( $product_id, $args );
230+
}
231+
232+
/**
233+
* Echo a hierarchical list of KB sections for a given product.
234+
*
235+
* @param int $product_id Product term ID.
236+
* @param array $args Display arguments.
237+
*/
238+
function wzkb_the_product_sections_list( $product_id, $args = array() ) {
239+
echo wzkb_get_product_sections_list( $product_id, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
240+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?php
2+
/**
3+
* WZ Knowledge Base Products Widget class.
4+
*
5+
* @package WebberZone\Knowledge_Base
6+
*/
7+
8+
namespace WebberZone\Knowledge_Base\Widgets;
9+
10+
use WebberZone\Knowledge_Base\Frontend\Display;
11+
12+
if ( ! defined( 'WPINC' ) ) {
13+
die;
14+
}
15+
16+
/**
17+
* Create a WordPress Products Widget for WZ Knowledge Base.
18+
*
19+
* @since 2.3.0
20+
*/
21+
class Products_Widget extends \WP_Widget {
22+
/**
23+
* Register widget with WordPress.
24+
*/
25+
public function __construct() {
26+
parent::__construct(
27+
'widget_wzkb_products',
28+
__( 'WZKB Products', 'knowledgebase' ),
29+
array(
30+
'description' => __( 'Display sections under a selected product for the Knowledge Base.', 'knowledgebase' ),
31+
'customize_selective_refresh' => true,
32+
'show_instance_in_rest' => true,
33+
)
34+
);
35+
}
36+
37+
/**
38+
* Back-end widget form.
39+
*
40+
* @see WP_Widget::form()
41+
*
42+
* @param array $instance Previously saved values from database.
43+
*/
44+
public function form( $instance ) {
45+
$title = isset( $instance['title'] ) ? $instance['title'] : '';
46+
$product_id = isset( $instance['product_id'] ) ? (int) $instance['product_id'] : 0;
47+
$depth = isset( $instance['depth'] ) ? (int) $instance['depth'] : 0;
48+
49+
$products = get_terms(
50+
array(
51+
'taxonomy' => 'wzkb_product',
52+
'hide_empty' => false,
53+
)
54+
);
55+
?>
56+
<p>
57+
<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
58+
<?php esc_html_e( 'Title:', 'knowledgebase' ); ?>
59+
<input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" />
60+
</label>
61+
</p>
62+
<p>
63+
<label for="<?php echo esc_attr( $this->get_field_id( 'product_id' ) ); ?>">
64+
<?php esc_html_e( 'Product:', 'knowledgebase' ); ?>
65+
<select class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'product_id' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'product_id' ) ); ?>">
66+
<option value="0"><?php esc_html_e( 'Select a product', 'knowledgebase' ); ?></option>
67+
<?php foreach ( $products as $product ) : ?>
68+
<option value="<?php echo esc_attr( (string) $product->term_id ); ?>" <?php selected( $product_id, $product->term_id ); ?>><?php echo esc_html( $product->name ); ?></option>
69+
<?php endforeach; ?>
70+
</select>
71+
</label>
72+
</p>
73+
<p>
74+
<label for="<?php echo esc_attr( $this->get_field_id( 'depth' ) ); ?>">
75+
<?php esc_html_e( 'Max Depth (0 for unlimited):', 'knowledgebase' ); ?>
76+
<input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'depth' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'depth' ) ); ?>" type="number" value="<?php echo esc_attr( (string) $depth ); ?>" min="0" />
77+
</label>
78+
</p>
79+
<?php
80+
}
81+
82+
/**
83+
* Sanitize widget form values as they are saved.
84+
*
85+
* @see WP_Widget::update()
86+
*
87+
* @param array $new_instance Values just sent to be saved.
88+
* @param array $old_instance Previously saved values from database.
89+
* @return array Updated safe values to be saved.
90+
*/
91+
public function update( $new_instance, $old_instance ) {
92+
$instance = array();
93+
$instance['title'] = sanitize_text_field( $new_instance['title'] );
94+
$instance['product_id'] = isset( $new_instance['product_id'] ) ? (int) $new_instance['product_id'] : 0;
95+
$instance['depth'] = isset( $new_instance['depth'] ) ? (int) $new_instance['depth'] : 0;
96+
return $instance;
97+
}
98+
99+
/**
100+
* Front-end display of widget.
101+
*
102+
* @see WP_Widget::widget()
103+
*
104+
* @param array $args Widget arguments.
105+
* @param array $instance Saved values from database.
106+
*/
107+
public function widget( $args, $instance ) {
108+
if ( ! isset( $args['widget_id'] ) ) {
109+
$args['widget_id'] = $this->id;
110+
}
111+
112+
// Return if not a WZKB post type archive or single page.
113+
if ( ! is_post_type_archive( 'wz_knowledgebase' ) && ! is_singular( 'wz_knowledgebase' ) && ! is_tax( 'wzkb_category' ) && ! is_tax( 'wzkb_tag' ) ) {
114+
return;
115+
}
116+
117+
$title = ( ! empty( $instance['title'] ) ) ? $instance['title'] : '';
118+
$title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
119+
$product_id = ! empty( $instance['product_id'] ) ? (int) $instance['product_id'] : 0;
120+
$depth = ! empty( $instance['depth'] ) ? (int) $instance['depth'] : 0;
121+
122+
if ( ! $product_id ) {
123+
return;
124+
}
125+
126+
$output = '';
127+
$output .= $args['before_widget'];
128+
if ( ! empty( $title ) ) {
129+
$output .= $args['before_title'] . esc_html( $title ) . $args['after_title'];
130+
}
131+
$output .= Display::get_product_sections_list( $product_id, array( 'depth' => $depth ) );
132+
$output .= $args['after_widget'];
133+
134+
echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
135+
}
136+
}

includes/widgets/class-sections-widget.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,12 @@ public function form( $instance ) {
5656
</p>
5757
<p>
5858
<label for="<?php echo esc_attr( $this->get_field_id( 'term_id' ) ); ?>">
59-
<?php esc_html_e( 'Term ID (enter a number)', 'knowledgebase' ); ?>: <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'term_id' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'term_id' ) ); ?>" type="text" value="<?php echo esc_attr( $term_id ); ?>" />
59+
<?php esc_html_e( 'Section ID (enter a number)', 'knowledgebase' ); ?>: <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'term_id' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'term_id' ) ); ?>" type="text" value="<?php echo esc_attr( $term_id ); ?>" />
6060
</label>
6161
</p>
6262
<p>
6363
<label for="<?php echo esc_attr( $this->get_field_id( 'depth' ) ); ?>">
64-
<?php esc_html_e( 'Depth', 'knowledgebase' ); ?>: <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'depth' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'depth' ) ); ?>" type="text" value="<?php echo esc_attr( $depth ); ?>" />
64+
<?php esc_html_e( 'Max Depth (0 for unlimited)', 'knowledgebase' ); ?>: <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'depth' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'depth' ) ); ?>" type="text" value="<?php echo esc_attr( $depth ); ?>" />
6565
</label>
6666
</p>
6767
<p>

readme.txt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Contributors: Ajay, webberzone
33
Donate link: https://ajaydsouza.com/donate/
44
Tags: knowledge base, knowledgebase, FAQ, frequently asked questions, support, documentation
5-
Requires at least: 6.3
5+
Requires at least: 6.6
66
Tested up to: 6.8
77
Requires PHP: 7.4
88
Stable tag: 2.3.0
@@ -130,10 +130,11 @@ Completely rewritten. Several new features and enhancements.
130130

131131
* Features:
132132
* Introduced a new hierarchical Products taxonomy (`wzkb_product`) enabling multi-product support for articles and sections.
133-
* Migration wizard to map existing sections and articles to products, with dry-run and batch processing.
134-
* Product-based frontend templates that preserve section hierarchy.
135-
* Admin UI enhancements for managing products, sections, and migration.
133+
* Migration wizard to map existing sections and articles to products, with dry-run and batch processing.
134+
* Product-based frontend templates that preserve section hierarchy.
135+
* Admin UI enhancements for managing products, sections, and migration.
136136
* Setup Wizard to guide users through the initial setup process.
137+
* New Product widget to display the Sections for a specific Product.
137138

138139
* Modifications:
139140
* Standardized CSS class names to use consistent hyphenation (e.g., `wzkb_section` is now `wzkb-section`). If you have custom CSS targeting the old class names, you'll need to update your stylesheets.

0 commit comments

Comments
 (0)