| @@ -91,8 +91,24 @@ | ||
| 91 | 91 | $formattedTerms[] = &$term; |
| 92 | 92 | } |
| 93 | 93 | } |
| 94 | 94 | |
| 95 | + // $parents narrows the response to the requested parent terms — each | |
| 96 | + // requested node is returned at the top level with its subtree attached. | |
| 97 | + // The parameter was accepted but never read: callers passing parent ids | |
| 98 | + // (products/fetch-term-by-parent) got the full unconstrained tree back. | |
| 99 | + // null keeps the full tree, which is what every other caller passes. | |
| 100 | + if (!empty($parents)) { | |
| 101 | + $requestedTerms = []; | |
| 102 | + foreach ((array) $parents as $parentTermId) { | |
| 103 | + $parentTermId = (int) $parentTermId; | |
| 104 | + if (isset($termMap[$parentTermId])) { | |
| 105 | + $requestedTerms[] = $termMap[$parentTermId]; | |
| 106 | + } | |
| 107 | + } | |
| 108 | + $formattedTerms = $requestedTerms; | |
| 109 | + } | |
| 110 | + | |
| 95 | 111 | // Mark prefilled terms as selected |
| 96 | 112 | // Adds a selected key if the term ID is found in $prefilled. |
| 97 | 113 | if (!empty($prefilled)) { |
| 98 | 114 | foreach ($formattedTerms as &$term) { |
| @@ -143,8 +159,65 @@ | ||
| 143 | 159 | } |
| 144 | 160 | } |
| 145 | 161 | |
| 146 | 162 | return $termIds; |
| 163 | + } | |
| 164 | + | |
| 165 | + /** | |
| 166 | + * Parse a shortcode taxonomy attribute: "product-tags:1,2|product-brands:67". | |
| 167 | + * | |
| 168 | + * Returns [taxonomy => term ids]. A taxonomy is accepted only when it is | |
| 169 | + * registered on products (the same list the AJAX pager re-parses), and a | |
| 170 | + * term only when it belongs to that taxonomy, because the product query | |
| 171 | + * matches term ids without checking their taxonomy. A segment that resolves | |
| 172 | + * to no term is kept as an empty list so the caller can refuse to widen it. | |
| 173 | + */ | |
| 174 | + public static function parseShortcodeTaxonomies(string $raw): array | |
| 175 | + { | |
| 176 | + $taxonomies = static::getTaxonomies(); | |
| 177 | + | |
| 178 | + // Group the raw values first so a taxonomy repeated across segments | |
| 179 | + // still costs a single query. | |
| 180 | + $rawValues = []; | |
| 181 | + foreach (explode('|', $raw) as $segment) { | |
| 182 | + $parts = explode(':', $segment, 2); | |
| 183 | + $taxonomy = sanitize_key(trim($parts[0])); | |
| 184 | + if ($taxonomy === '') { | |
| 185 | + continue; | |
| 186 | + } | |
| 187 | + $rawValues[$taxonomy][] = isset($parts[1]) ? $parts[1] : ''; | |
| 188 | + } | |
| 189 | + | |
| 190 | + $parsed = []; | |
| 191 | + foreach ($rawValues as $taxonomy => $values) { | |
| 192 | + $parsed[$taxonomy] = isset($taxonomies[$taxonomy]) | |
| 193 | + ? static::resolveTermIds(implode(',', $values), $taxonomy) | |
| 194 | + : []; | |
| 195 | + } | |
| 196 | + | |
| 197 | + return $parsed; | |
| 198 | + } | |
| 199 | + | |
| 200 | + private static function resolveTermIds(string $values, string $taxonomy): array | |
| 201 | + { | |
| 202 | + $ids = array_values(array_unique(array_map('intval', array_filter( | |
| 203 | + array_map('trim', explode(',', $values)), | |
| 204 | + 'ctype_digit' | |
| 205 | + )))); | |
| 206 | + | |
| 207 | + if (!$ids) { | |
| 208 | + return []; | |
| 209 | + } | |
| 210 | + | |
| 211 | + // One query per taxonomy; `include` only returns terms of $taxonomy. | |
| 212 | + $termIds = get_terms([ | |
| 213 | + 'taxonomy' => $taxonomy, | |
| 214 | + 'include' => $ids, | |
| 215 | + 'fields' => 'ids', | |
| 216 | + 'hide_empty' => false, | |
| 217 | + ]); | |
| 218 | + | |
| 219 | + return is_wp_error($termIds) ? [] : array_map('intval', $termIds); | |
| 147 | 220 | } |
| 148 | 221 | |
| 149 | 222 | public static function addTaxonomyTerms(string $taxonomy, array $terms, array $args = []) |
| 150 | 223 | { |