PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk All 48 releases
← All changes | app/Modules/MCP/Tools/ProductTools.php +30 -10 1.5.1 → 1.6.5 View file →
@@ -5,8 +5,9 @@
5 5 use FluentCart\App\Helpers\Helper;
6 6 use FluentCart\App\Models\Product;
7 7 use FluentCart\App\Models\ProductVariation;
8 8 use FluentCart\App\Models\OrderItem;
9 +use FluentCart\App\Modules\MCP\Support\AdvancedSearch;
9 10 use FluentCart\App\Modules\MCP\Support\MCPHelper;
10 11 use FluentCart\App\Modules\MCP\Support\PermissionGate;
11 12
12 13 /**
@@ -30,9 +31,9 @@
30 31 {
31 32 return [
32 33 'fluent-cart/list-products' => [
33 34 'label' => __('List Products', 'fluent-cart'),
34 - 'description' => __('Find and filter products. Compact rows: title, status, price range, variation count, fulfillment, stock status. Use get-product for full detail and per-variation stock. Price filters are in store currency, not cents.', 'fluent-cart'),
35 + 'description' => __('Find and filter products. Compact rows: title, status, price range, variation count, fulfillment, stock status. Use get-product for full detail and per-variation stock. Price filters are in store currency, not cents. For conditions these flat filters cannot express (OR groups, order/variation counts, available stock quantity, taxonomy terms) pass advanced_filters — call get-search-schema entity=products first (Pro).', 'fluent-cart'),
35 36 'input_schema' => [
36 37 'type' => 'object',
37 38 'properties' => [
38 39 'search' => ['type' => 'string', 'description' => 'Matches product title.'],
@@ -42,8 +43,9 @@
42 43 'stock_status' => ['type' => 'string', 'enum' => ['in-stock', 'out-of-stock']],
43 44 'category' => ['type' => 'string', 'description' => 'Category term slug.'],
44 45 'min_price' => ['type' => 'number', 'description' => 'Minimum price in store currency.'],
45 46 'max_price' => ['type' => 'number', 'description' => 'Maximum price in store currency.'],
47 + 'advanced_filters' => ['type' => 'array', 'items' => ['type' => ['object', 'array']], 'description' => 'Pro: condition groups {property, operator, value} — outer array = OR groups, inner = AND. Call get-search-schema entity=products FIRST for properties/operators/format. AND-combines with the other filters here. An empty array means no advanced filter.'],
46 48 'sort_by' => ['type' => 'string', 'enum' => ['id', 'title', 'date'], 'default' => 'date'],
47 49 'sort_type' => ['type' => 'string', 'enum' => ['ASC', 'DESC'], 'default' => 'DESC'],
48 50 'page' => ['type' => 'integer', 'default' => 1],
49 51 'per_page' => ['type' => 'integer', 'default' => 15, 'description' => 'Max 100.'],
@@ -102,12 +104,26 @@
102 104 public static function listProducts($params = [])
103 105 {
104 106 $paging = MCPHelper::pagination($params);
105 107
106 - // Product model adds a global scope pinning post_type to the canonical
107 - // CPT (fluent-products) — don't re-add it here, a wrong literal would
108 - // AND against the scope and match nothing.
109 - $query = Product::query()->with('detail');
108 + // advanced_filters routes through the admin filter engine (validated
109 + // first — a bad condition errors, never silently drops); the named
110 + // filters below then AND onto the same query either way.
111 + $advWarnings = [];
112 + if (!empty($params['advanced_filters'])) {
113 + $built = AdvancedSearch::buildQuery('products', $params['advanced_filters']);
114 + if (is_wp_error($built)) {
115 + return $built;
116 + }
117 + $query = $built['query'];
118 + $advWarnings = $built['warnings'];
119 + } else {
120 + // Product model adds a global scope pinning post_type to the canonical
121 + // CPT (fluent-products) — don't re-add it here, a wrong literal would
122 + // AND against the scope and match nothing.
123 + $query = Product::query();
124 + }
125 + $query->with('detail');
110 126
111 127 if (!empty($params['search'])) {
112 128 $query->where('post_title', 'LIKE', '%' . sanitize_text_field($params['search']) . '%');
113 129 }
@@ -167,8 +183,13 @@
167 183 foreach (MCPHelper::paginatorItems($paginator) as $product) {
168 184 $rows[] = self::formatRow($product);
169 185 }
170 186
187 + $meta = MCPHelper::pagingMeta($paginator);
188 + if ($advWarnings) {
189 + $meta['warnings'] = $advWarnings;
190 + }
191 +
171 192 return MCPHelper::envelope(
172 193 sprintf(
173 194 /* translators: %d: number of matching products */
174 195 _n('%d product found.', '%d products found.', $total, 'fluent-cart'),
@@ -174,9 +195,9 @@
174 195 _n('%d product found.', '%d products found.', $total, 'fluent-cart'),
175 196 $total
176 197 ),
177 198 ['products' => $rows],
178 - MCPHelper::pagingMeta($paginator)
199 + $meta
179 200 );
180 201 }
181 202
182 203 private static function formatRow($product)
@@ -237,10 +258,9 @@
237 258 'variation_type' => $detail ? $detail->variation_type : null,
238 259 'stock_status' => $detail ? $detail->stock_availability : null,
239 260 'price_range' => self::priceRange($detail),
240 261 'variations' => self::variations($product),
241 - 'categories' => self::terms($product, 'categories'),
242 - 'tags' => self::terms($product, 'tags'),
262 + 'categories' => self::terms($product),
243 263 'created_at' => MCPHelper::toIso8601($product->post_date_gmt ? $product->post_date_gmt : $product->post_date),
244 264 ];
245 265
246 266 if (in_array('sales', $include, true)) {
@@ -286,12 +306,12 @@
286 306 }
287 307 return $out;
288 308 }
289 309
290 - private static function terms($product, $which)
310 + private static function terms($product)
291 311 {
292 312 try {
293 - $terms = $which === 'tags' ? $product->getTags() : $product->getCategories();
313 + $terms = $product->getCategories();
294 314 } catch (\Throwable $e) {
295 315 return [];
296 316 }
297 317 $out = [];