Created
September 14, 2021 18:12
-
-
Save gzalinski/7279a38fd611e9e41b2abf4a59e923a7 to your computer and use it in GitHub Desktop.
Get product categories with setting and display in list/menu/dropdown
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Class XC_ProductCategories | |
* @usage $cat_list = new XC_ProductCategories(); | |
* @usage $cat_list->render($attributes); | |
*/ | |
class XC_ProductCategories { | |
public $defaults | |
= array( | |
'hasCount' => true, | |
'hasImage' => false, | |
'hasEmpty' => false, | |
'template' => 'list', //list/dropdown/menu | |
'isHierarchical' => true, | |
'depth' => 999, | |
'parentID' => 0 | |
); | |
function render( $attributes = '' ) { | |
$attributes = ! $attributes ? $this->defaults : array_replace( $this->defaults, $attributes ); | |
$uid = uniqid( 'product-categories-' ); | |
$categories = $this->get_categories( $attributes ); | |
if ( empty( $categories ) ) { | |
return ''; | |
} | |
$output = '<div class="xc-product-categories">'; | |
if ( $attributes['template'] == 'list' ) { | |
$output .= $this->renderList( $categories, $attributes, $uid ); | |
} elseif ( $attributes['template'] == 'menu' ) { | |
$output .= $this->renderMenu( $categories, $attributes, $uid ); | |
} elseif ( $attributes['template'] == 'dropdown' ) { | |
$output .= $this->renderDropdown( $categories, $attributes, $uid ); | |
} | |
$output .= '</div>'; | |
return $output; | |
} | |
/** | |
* Get categories (terms) from the db. | |
* | |
* @param array $attributes Block attributes. Default empty array. | |
* | |
* @return array | |
*/ | |
protected function get_categories( $attributes ) { | |
$args = [ | |
'hide_empty' => ! $attributes['hasEmpty'], | |
'pad_counts' => true, | |
'hierarchical' => $attributes['isHierarchical'], | |
'child_of' => $attributes['parentID'], | |
]; | |
$categories = get_terms( 'product_cat', $args ); | |
if ( ! is_array( $categories ) || empty( $categories ) ) { | |
return []; | |
} | |
// This ensures that no categories with a product count of 0 is rendered. | |
if ( ! $attributes['hasEmpty'] ) { | |
$categories = array_filter( | |
$categories, | |
function ( $category ) { | |
return 0 !== $category->count; | |
} | |
); | |
} | |
return $attributes['isHierarchical'] ? $this->build_category_tree( $categories, $attributes ) : $categories; | |
} | |
/** | |
* Build hierarchical tree of categories. | |
* | |
* @param array $categories List of terms. | |
* | |
* @return array | |
*/ | |
protected function build_category_tree( $categories, $attributes ) { | |
$categories_by_parent = []; | |
foreach ( $categories as $category ) { | |
if ( ! isset( $categories_by_parent[ 'cat-' . $category->parent ] ) ) { | |
$categories_by_parent[ 'cat-' . $category->parent ] = []; | |
} | |
$categories_by_parent[ 'cat-' . $category->parent ][] = $category; | |
} | |
$tree = $categories_by_parent[ 'cat-' . $attributes['parentID'] ]; | |
unset( $categories_by_parent[ 'cat-' . $attributes['parentID'] ] ); | |
foreach ( $tree as $category ) { | |
if ( ! empty( $categories_by_parent[ 'cat-' . $category->term_id ] ) ) { | |
$category->children = $this->fill_category_children( $categories_by_parent[ 'cat-' . $category->term_id ], | |
$categories_by_parent ); | |
} | |
} | |
return $tree; | |
} | |
/** | |
* Build hierarchical tree of categories by appending children in the tree. | |
* | |
* @param array $categories List of terms. | |
* @param array $categories_by_parent List of terms grouped by parent. | |
* | |
* @return array | |
*/ | |
protected function fill_category_children( $categories, $categories_by_parent ) { | |
foreach ( $categories as $category ) { | |
if ( ! empty( $categories_by_parent[ 'cat-' . $category->term_id ] ) ) { | |
$category->children = $this->fill_category_children( $categories_by_parent[ 'cat-' . $category->term_id ], $categories_by_parent ); | |
} | |
} | |
return $categories; | |
} | |
/** | |
* Render the category list as a dropdown. | |
* | |
* @param array $categories List of terms. | |
* @param array $attributes Block attributes. Default empty array. | |
* @param int $uid Unique ID for the rendered block, used for HTML IDs. | |
* | |
* @return string Rendered output. | |
*/ | |
protected function renderDropdown( $categories, $attributes, $uid ) { | |
$aria_label = empty( $attributes['hasCount'] ) | |
? | |
__( 'List of categories', 'woocommerce' ) | |
: | |
__( 'List of categories with their product counts', 'woocommerce' ); | |
$output = ' | |
<div class="xc-product-categories__dropdown"> | |
<label | |
class="screen-reader-text" | |
for="' . esc_attr( $uid ) . '-select" | |
> | |
' . esc_html__( 'Select a category', 'woocommerce' ) . ' | |
</label> | |
<select aria-label="' . esc_attr( $aria_label ) . '" id="' . esc_attr( $uid ) . '-select"> | |
<option value="false" hidden> | |
' . esc_html__( 'Select a category', 'woocommerce' ) . ' | |
</option> | |
' . $this->renderDropdownOptions( $categories, $attributes, $uid ) . ' | |
</select> | |
</div> | |
<button | |
type="button" | |
class="xc-product-categories__button" | |
aria-label="' . esc_html__( 'Go to category', 'woocommerce' ) . '" | |
onclick="const url = document.getElementById( \'' . esc_attr( $uid ) . '-select\' ).value; if ( \'false\' !== url ) document.location.href = url;" | |
> | |
<svg | |
aria-hidden="true" | |
role="img" | |
focusable="false" | |
class="dashicon dashicons-arrow-right-alt2" | |
xmlns="http://www.w3.org/2000/svg" | |
width="20" | |
height="20" | |
viewBox="0 0 20 20" | |
> | |
<path d="M6 15l5-5-5-5 1-2 7 7-7 7z" /> | |
</svg> | |
</button> | |
'; | |
return $output; | |
} | |
/** | |
* Render dropdown options list. | |
* | |
* @param array $categories List of terms. | |
* @param array $attributes Block attributes. Default empty array. | |
* @param int $uid Unique ID for the rendered block, used for HTML IDs. | |
* @param int $depth Current depth. | |
* | |
* @return string Rendered output. | |
*/ | |
protected function renderDropdownOptions( $categories, $attributes, $uid, $depth = 0 ) { | |
$output = ''; | |
$max_depth = $attributes['depth']; | |
if ( $depth > $max_depth ) { | |
return; | |
} | |
foreach ( $categories as $category ) { | |
$output .= ' | |
<option value="' . esc_attr( get_term_link( $category->term_id, 'product_cat' ) ) . '"> | |
' . str_repeat( '-', $depth ) . ' | |
' . esc_html( $category->name ) . ' | |
' . $this->getCount( $category, $attributes ) . ' | |
</option> | |
' . ( ! empty( $category->children ) ? $this->renderDropdownOptions( $category->children, $attributes, $uid, $depth + 1 ) : '' ) . ' | |
'; | |
} | |
return $output; | |
} | |
/** | |
* Render the category list as a list. | |
* | |
* @param array $categories List of terms. | |
* @param array $attributes Block attributes. Default empty array. | |
* @param int $uid Unique ID for the rendered block, used for HTML IDs. | |
* @param int $depth Current depth. | |
* | |
* @return string Rendered output. | |
*/ | |
protected function renderMenu( $categories, $attributes, $uid, $depth = 0 ) { | |
$max_depth = $attributes['depth']; | |
if ( $depth > $max_depth ) { | |
return; | |
} | |
$classes = [ | |
'xc-product-categories-menu', | |
'xc-product-categories-menu--depth-' . absint( $depth ), | |
]; | |
if ( ! empty( $attributes['hasImage'] ) ) { | |
$classes[] = 'xc-product-categories-menu--has-images'; | |
} | |
$output = '<ul class="' . esc_attr( implode( ' ', $classes ) ) . '">' . $this->renderMenuItems( $categories, $attributes, $uid, $depth ) | |
. '</ul>'; | |
return $output; | |
} | |
/** | |
* Render a list of terms. | |
* | |
* @param array $categories List of terms. | |
* @param array $attributes Block attributes. Default empty array. | |
* @param int $uid Unique ID for the rendered block, used for HTML IDs. | |
* @param int $depth Current depth. | |
* | |
* @return string Rendered output. | |
*/ | |
protected function renderMenuItems( $categories, $attributes, $uid, $depth = 0 ) { | |
$output = ''; | |
foreach ( $categories as $category ) { | |
$has_children = ! empty( $category->children ); | |
$class = 'xc-product-categories-menu-item'; | |
$class .= $has_children ? ' has-children' : ''; | |
if ( $has_children && $depth == 0 ) { | |
$item_action = '<button class="btn-toggle-category" aria-expanded="false"><i class="icon-down"></i></button>'; | |
} else { | |
$item_action = $this->getCount( $category, $attributes ); | |
} | |
$output .= ' | |
<li class="' . $class . '"> | |
<div class="xc-product-categories-menu-item-wrap"> | |
<a href="' . esc_attr( get_term_link( $category->term_id, 'product_cat' ) ) . '"> | |
' . $this->get_image_html( $category, $attributes ) . esc_html( $category->name ) . ' | |
</a> | |
' . $item_action . ' | |
</div> | |
' . ( $has_children ? $this->renderMenu( $category->children, $attributes, $uid, | |
$depth + 1 ) : '' ) . ' | |
</li> | |
'; | |
} | |
return $output; | |
} | |
/** | |
* Render the category list as a list. | |
* | |
* @param array $categories List of terms. | |
* @param array $attributes Block attributes. Default empty array. | |
* @param int $uid Unique ID for the rendered block, used for HTML IDs. | |
* @param int $depth Current depth. | |
* | |
* @return string Rendered output. | |
*/ | |
protected function renderList( $categories, $attributes, $uid, $depth = 0 ) { | |
$max_depth = $attributes['depth']; | |
if ( $depth > $max_depth ) { | |
return; | |
} | |
$classes = [ | |
'xc-product-categories-list', | |
'xc-product-categories-list--depth-' . absint( $depth ), | |
]; | |
if ( ! empty( $attributes['hasImage'] ) ) { | |
$classes[] = 'xc-product-categories-list--has-images'; | |
} | |
$output = '<ul class="' . esc_attr( implode( ' ', $classes ) ) . '">' . $this->renderListItems( $categories, $attributes, $uid, $depth ) | |
. '</ul>'; | |
return $output; | |
} | |
/** | |
* Render a list of terms. | |
* | |
* @param array $categories List of terms. | |
* @param array $attributes Block attributes. Default empty array. | |
* @param int $uid Unique ID for the rendered block, used for HTML IDs. | |
* @param int $depth Current depth. | |
* | |
* @return string Rendered output. | |
*/ | |
protected function renderListItems( $categories, $attributes, $uid, $depth = 0 ) { | |
$output = ''; | |
foreach ( $categories as $category ) { | |
$has_children = ! empty( $category->children ); | |
$class = 'xc-product-categories-list-item'; | |
$class .= $has_children ? ' has-children' : ''; | |
$output .= ' | |
<li class="' . $class . '"> | |
<a href="' . esc_attr( get_term_link( $category->term_id, 'product_cat' ) ) . '"> | |
' . $this->get_image_html( $category, $attributes ) . esc_html( $category->name ) . ' | |
</a> | |
' . $this->getCount( $category, $attributes ) . ' | |
' . ( $has_children ? $this->renderList( $category->children, $attributes, $uid, | |
$depth + 1 ) : '' ) . ' | |
</li> | |
'; | |
} | |
return $output; | |
} | |
/** | |
* Returns the category image html | |
* | |
* @param \WP_Term $category Term object. | |
* @param array $attributes Block attributes. Default empty array. | |
* @param string $size Image size, defaults to 'woocommerce_thumbnail'. | |
* | |
* @return string | |
*/ | |
public function get_image_html( $category, $attributes, $size = 'woocommerce_thumbnail' ) { | |
if ( empty( $attributes['hasImage'] ) ) { | |
return ''; | |
} | |
$image_id = get_term_meta( $category->term_id, 'thumbnail_id', true ); | |
if ( ! $image_id ) { | |
return '<span class="xc-product-categories-list-item__image xc-product-categories-list-item__image--placeholder">' | |
. wc_placeholder_img( 'woocommerce_thumbnail' ) . '</span>'; | |
} | |
return '<span class="xc-product-categories-list-item__image">' . wp_get_attachment_image( $image_id, 'woocommerce_thumbnail' ) | |
. '</span>'; | |
} | |
/** | |
* Get the count, if displaying. | |
* | |
* @param object $category Term object. | |
* @param array $attributes Block attributes. Default empty array. | |
* | |
* @return string | |
*/ | |
protected function getCount( $category, $attributes ) { | |
if ( empty( $attributes['hasCount'] ) ) { | |
return ''; | |
} | |
if ( $attributes['template'] == 'dropdown' ) { | |
return '(' . absint( $category->count ) . ')'; | |
} | |
$screen_reader_text = sprintf( | |
/* translators: %s number of products in cart. */ | |
_n( '%d product', '%d products', absint( $category->count ), 'woocommerce' ), | |
absint( $category->count ) | |
); | |
return '<span class="xc-item-count">' | |
. '<span aria-hidden="true">' . absint( $category->count ) . '</span>' | |
. '<span class="screen-reader-text">' . esc_html( $screen_reader_text ) . '</span>' | |
. '</span>'; | |
} | |
} | |
// $XC_ProductCategories = new XC_ProductCategories(); | |
// $ProductCategories = $XC_ProductCategories->render( [ 'template' => 'menu', 'parentID' => 2934, 'depth' => 2 ] ); | |
// echo $ProductCategories; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment