$taxonomies, 'hide_empty' => false, 'number' => $per_page, 'offset' => max( 0, (int) $atts['offset'] ) + ( ( $page - 1 ) * $per_page ), 'orderby' => airowp_query_sanitize_term_orderby( (string) $atts['orderBy'] ), 'order' => 'ASC' === strtoupper( (string) $atts['order'] ) ? 'ASC' : 'DESC', ); // Search (term name LIKE). $search = (string) $atts['search']; if ( ! empty( $atts['bindSearchTo'] ) && isset( $context['params'][ $atts['bindSearchTo'] ] ) ) { $bound = $context['params'][ $atts['bindSearchTo'] ]; $search = is_scalar( $bound ) ? (string) $bound : ''; } if ( '' !== $search ) { $args['name__like'] = $search; } /** This filter is documented in src/blocks/query/render-posts.php */ $args = apply_filters( 'airowp_query_args', $args, $atts, $context ); $query_id = isset( $context['query_id'] ) ? (string) $context['query_id'] : ''; if ( '' !== $query_id ) { /** This filter is documented in src/blocks/query/render-posts.php */ $args = apply_filters( 'airowp_query_' . $query_id . '_args', $args, $atts, $context ); } $terms = get_terms( $args ); if ( is_wp_error( $terms ) ) { $terms = array(); } // Build count query from post-filter $args so all developer-added // constraints (taxonomy, meta, search, etc.) carry through to the count. // We strip the pagination args (number/offset) and ordering (orderby/order) // because they'd artificially cap or reshape the count. Filters that add // 'fields', 'child_of', or 'parent' to $args intentionally flow through — // they constrain the result set in ways counts should reflect. $count_args = $args; unset( $count_args['number'], $count_args['offset'], $count_args['orderby'], $count_args['order'] ); $count_args['fields'] = 'count'; $total_terms = (int) get_terms( $count_args ); $items_html = ''; $item_index = 0; foreach ( (array) $terms as $term ) { if ( ! $term instanceof WP_Term ) { continue; } $items_html .= airowp_query_render_item( (string) $context['inner_html'], array( 'airo-wp/currentItemId' => (int) $term->term_id, 'airo-wp/currentItemType' => 'term:' . $term->taxonomy, 'index' => $item_index, 'airo-wp/itemIndex' => $item_index, ), $atts['itemTagName'] ); ++$item_index; } $totals = airowp_query_paginate_totals( $total_terms, (int) $atts['offset'], $per_page ); $state = array( 'totalItems' => $totals['totalItems'], 'totalPages' => $totals['totalPages'], 'page' => $page, ); airowp_query_set_last_state( $query_id, $state ); return array( 'html' => airowp_query_wrap( $items_html, $atts, $context, $context['wrapper_attrs'] ?? null ), 'items_html' => $items_html, 'totalPages' => $state['totalPages'], 'totalItems' => $state['totalItems'], ); } /** * Whitelist orderby values acceptable to get_terms(). * * @param string $orderby Requested orderby value. * @return string Safe get_terms() orderby value. */ function airowp_query_sanitize_term_orderby( $orderby ) { $allowed = array( 'name', 'slug', 'term_group', 'term_id', 'id', 'description', 'parent', 'count', 'include', 'slug__in', 'meta_value', 'meta_value_num', 'none' ); if ( 'date' === $orderby ) { return 'name'; // Terms don't have a date; default to alphabetical. } if ( 'title' === $orderby ) { return 'name'; } return in_array( $orderby, $allowed, true ) ? $orderby : 'name'; } endif;