| @@ -41,9 +41,17 @@ | ||
| 41 | 41 | $status = ["post_status" => ["column" => "post_status", "operator" => "in", "value" => ["publish"]]]; |
| 42 | 42 | $allowOutOfStock = $request->get('allow_out_of_stock', false) == true; |
| 43 | 43 | $cursor = $request->get('cursor', null); |
| 44 | 44 | $orderType = $request->get('order_type', 'DESC'); |
| 45 | - $with = $request->get('with', []); | |
| 45 | + // The storefront never chooses its own eager loads. This route is public | |
| 46 | + // (frontend_routes.php, PublicPolicy), and forwarding the request's `with` | |
| 47 | + // let an anonymous caller walk arbitrary relation chains — e.g. | |
| 48 | + // `?with[]=orderItems.order.customer.wpUser` reached the WordPress users | |
| 49 | + // table. The two relations below are the ones the appends applied after | |
| 50 | + // this call already touch (`thumbnail` reads `detail`, `has_subscription` | |
| 51 | + // reads `variants`), so the response shape is unchanged and they are now | |
| 52 | + // eager loaded instead of lazily fetched per row. | |
| 53 | + $with = ['detail', 'variants']; | |
| 46 | 54 | |
| 47 | 55 | // Shortcode filter params from AJAX |
| 48 | 56 | $includeIds = array_slice(array_values(array_filter(array_map('intval', (array) $request->get('include_ids', [])), function ($id) { return $id > 0; })), 0, 100); |
| 49 | 57 | $excludeIds = array_slice(array_values(array_filter(array_map('intval', (array) $request->get('exclude_ids', [])), function ($id) { return $id > 0; })), 0, 100); |
| @@ -70,15 +78,37 @@ | ||
| 70 | 78 | ]; |
| 71 | 79 | |
| 72 | 80 | $products = ShopResource::get($params); |
| 73 | 81 | |
| 82 | + $collection = $products['products']->getCollection(); | |
| 83 | + | |
| 84 | + // The appended has_subscription accessor reads $product->variants during | |
| 85 | + // serialization, so variants reach the response even without with[]=variants. | |
| 86 | + // Eager-load them once for the whole page (single query) so the sensitive | |
| 87 | + // fields can be hidden before serialization. | |
| 88 | + $collection->loadMissing('variants'); | |
| 89 | + | |
| 74 | 90 | $products['products']->setCollection( |
| 75 | - $products['products']->getCollection()->transform(function ($product) { | |
| 91 | + $collection->transform(function ($product) { | |
| 76 | 92 | $product->setAppends(['view_url', 'has_subscription', 'thumbnail']); |
| 77 | 93 | $product->makeHidden(['post_content']); |
| 78 | 94 | if ($product->detail !== null) { |
| 79 | 95 | $product->detail->makeHidden(['item_cost', 'editing_stage', 'stock', 'manage_stock', 'manage_cost', 'settings']); |
| 80 | 96 | } |
| 97 | + $product->variants->each(function ($variant) { | |
| 98 | + $variant->makeHidden(['item_cost', 'manage_cost', 'manage_stock', 'total_stock', 'available', 'committed', 'on_hold']); | |
| 99 | + | |
| 100 | + // other_info is a JSON blob that makeHidden cannot reach into; strip its | |
| 101 | + // admin-only keys while keeping the storefront display fields (payment_type, | |
| 102 | + // repeat_interval, trial_days, billing_summary, weight/dimensions). | |
| 103 | + $info = $variant->other_info; | |
| 104 | + if (is_array($info)) { | |
| 105 | + foreach (['tax_class', 'tax_exempt', 'package_slug', 'bundle_child_ids', 'variation_type', 'is_bundle_product'] as $internalKey) { | |
| 106 | + unset($info[$internalKey]); | |
| 107 | + } | |
| 108 | + $variant->other_info = $info; | |
| 109 | + } | |
| 110 | + }); | |
| 81 | 111 | return $product; |
| 82 | 112 | }) |
| 83 | 113 | ); |
| 84 | 114 | |
| @@ -195,11 +225,12 @@ | ||
| 195 | 225 | |
| 196 | 226 | $perPage = $request->get('per_page', 10); |
| 197 | 227 | $products['total'] = $total; |
| 198 | 228 | $products['last_page'] = max((int)ceil($total / $perPage), 1); |
| 229 | + $hideExcerpt = filter_var($request->get('hide_excerpt', false), FILTER_VALIDATE_BOOLEAN); | |
| 199 | 230 | ob_start(); |
| 200 | 231 | if (($products['total'])) { |
| 201 | - (new ProductListRenderer(Arr::get($products, 'products.products')))->renderProductList(); | |
| 232 | + (new ProductListRenderer(Arr::get($products, 'products.products'), null, null, ['hide_excerpt' => $hideExcerpt]))->renderProductList(); | |
| 202 | 233 | } else { |
| 203 | 234 | ProductRenderer::renderNoProductFound(); |
| 204 | 235 | } |
| 205 | 236 | |
| @@ -319,9 +350,9 @@ | ||
| 319 | 350 | 'show_thumbnail' => $showThumbnail, |
| 320 | 351 | ]))->renderResultItems($products); |
| 321 | 352 | |
| 322 | 353 | $view = ob_get_clean(); |
| 323 | - return $this->response->json([ | |
| 354 | + return $this->response->sendSuccess([ | |
| 324 | 355 | 'htmlView' => $view |
| 325 | 356 | ]); |
| 326 | 357 | } |
| 327 | 358 | } |