PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
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 1.2.0 All 47 releases
fluent-cart / app / Http / Controllers / AttributesController.php

AttributesController.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.4, at app/Http/Controllers/AttributesController.php

226 lines 8.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Http\Controllers;
4
5 use FluentCart\App\Http\Controllers\Controller;
6 use FluentCart\App\Models\AttributeGroup;
7 use FluentCart\App\Models\AttributeTerm;
8 use FluentCart\Framework\Http\Request\Request;
9 use FluentCart\Framework\Support\Arr;
10 use FluentCart\Api\Resource\AttrGroupResource;
11 use FluentCart\Api\Resource\AttrTermResource;
12 use FluentCart\App\Http\Requests\AttrGroupRequest;
13 use FluentCart\App\Http\Requests\AttrTermRequest;
14 use FluentCart\App\Http\Requests\AttrTermUpdateRequest;
15 use FluentCart\App\Services\Filter\AttrGroupFilter;
16 use FluentCart\App\Services\Filter\AttrTermFilter;
17
18 class AttributesController extends Controller
19 {
20 public function getGroup(Request $request, $group_id): array
21 {
22 return ['group' => AttrGroupResource::find($group_id, $request->all())];
23 }
24
25 public function getGroups(Request $request)
26 {
27 return ['groups' => AttrGroupFilter::fromRequest($request)->paginate()];
28 }
29
30 /**
31 * Two-pass payload for the Advanced Variation library picker.
32 *
33 * Pass 1: groups list (capped at 200) plus a per-group terms_count from a
34 * single batched aggregate. No terms are eager-loaded — at scale (200
35 * groups times hundreds of terms each) the combined payload could exceed
36 * memory limits and stall the admin request.
37 *
38 * Pass 2: the picker calls GET attr/group/{id}/terms on expand to fetch
39 * the full paginated term list for the specific group the merchant
40 * clicked. This trades one big upfront response for one small expansion
41 * round-trip per group the user actually interacts with — the usual
42 * pattern at expected catalog sizes (most stores expand 1-3 groups, not
43 * all 200).
44 */
45 public function getLibrary(Request $request): array
46 {
47 $totalGroups = (int) AttributeGroup::query()->count();
48
49 // Match the Attributes library sidebar: groups follow the merchant's
50 // manual drag-order (fct_atts_groups.serial), so the product editor's
51 // option-name dropdown lists them in the same order the merchant arranged,
52 // not alphabetically. id ASC is the deterministic tie-breaker.
53 $groups = AttributeGroup::query()
54 ->orderBy('serial', 'ASC')
55 ->orderBy('id', 'ASC')
56 ->limit(200)
57 ->get();
58
59 // Stamp the authoritative terms_count from a single batched aggregate so
60 // the picker can show "Color - 20 terms" upfront without a second query
61 // per group. The full term list is fetched on expand via getTerms().
62 if ($groups->isNotEmpty()) {
63 $groupIds = $groups->pluck('id')->all();
64 $counts = AttributeTerm::query()
65 ->whereIn('group_id', $groupIds)
66 ->selectRaw('group_id, COUNT(*) AS c')
67 ->groupBy('group_id')
68 ->get()
69 ->keyBy('group_id');
70
71 $groups->each(function ($group) use ($counts) {
72 $row = $counts->get($group->id);
73 $group->terms_count = $row ? (int) $row->c : 0;
74 });
75 }
76
77 return [
78 'groups' => $groups,
79 'total_groups' => $totalGroups,
80 'cap' => 200,
81 ];
82 }
83
84 public function createGroup(AttrGroupRequest $request)
85 {
86 $data = $request->getSafe($request->sanitize());
87 $arg = Arr::only($data, ['title', 'slug', 'settings']);
88 $isCreated = AttrGroupResource::create($arg);
89
90 if (is_wp_error($isCreated)) {
91 return $isCreated;
92 }
93 return $this->response->sendSuccess($isCreated);
94 }
95
96 public function updateGroup(AttrGroupRequest $request, $group_id)
97 {
98 $data = $request->getSafe($request->sanitize());
99 $arg = Arr::only($data, ['title', 'settings']);
100 $isUpdated = AttrGroupResource::update($arg, $group_id);
101
102 if (is_wp_error($isUpdated)) {
103 return $isUpdated;
104 }
105 return $this->response->sendSuccess($isUpdated);
106 }
107
108 public function deleteGroup(Request $request, $group_id)
109 {
110 $isDeleted = AttrGroupResource::delete($group_id);
111
112 if (is_wp_error($isDeleted)) {
113 return $isDeleted;
114 }
115 return $this->response->sendSuccess($isDeleted);
116 }
117
118 public function getTerms(Request $request, $group_id): array
119 {
120 /** @var AttrTermFilter $filter */
121 $filter = AttrTermFilter::fromRequest($request);
122 $filter->setGroupId((int) $group_id);
123 return ['terms' => $filter->paginate()];
124 }
125
126 public function createTerms(AttrTermRequest $request, $group_id)
127 {
128 $data = $request->getSafe($request->sanitize());
129
130 if (empty(Arr::get($data, 'terms', []))) {
131 return $this->response->sendError([
132 'message' => __('At least one term is required.', 'fluent-cart')
133 ], 422);
134 }
135
136 $created = AttrTermResource::create($data, ['group_id' => $group_id]);
137
138 if (is_wp_error($created)) {
139 return $created;
140 }
141 return $this->response->sendSuccess($created);
142 }
143
144 public function updateTerm(AttrTermUpdateRequest $request, $group_id, $term_id)
145 {
146 $data = $request->getSafe($request->sanitize());
147 $arg = Arr::only($data, ['title', 'settings']);
148 $isUpdated = AttrTermResource::update($arg, $term_id, ['group_id' => $group_id]);
149
150 if (is_wp_error($isUpdated)) {
151 return $isUpdated;
152 }
153 return $this->response->sendSuccess($isUpdated);
154 }
155
156 public function deleteTerm(Request $request, $group_id, $term_id)
157 {
158 $isDeleted = AttrTermResource::delete($term_id, ['group_id' => $group_id]);
159
160 if (is_wp_error($isDeleted)) {
161 return $isDeleted;
162 }
163 return $this->response->sendSuccess($isDeleted);
164 }
165
166 public function reorderTerms(Request $request, $group_id)
167 {
168 $result = AttrTermResource::reorder([
169 'group_id' => $group_id,
170 'ids' => $request->get('ids', []),
171 ]);
172
173 if (is_wp_error($result)) {
174 return $result;
175 }
176 return $this->response->sendSuccess($result);
177 }
178
179 public function reorderGroups(Request $request)
180 {
181 // Sanitize: coerce to a clean, deduped list of positive integer IDs. The
182 // dedupe keeps AttrGroupResource::reorder's ownership count check from
183 // false-tripping on duplicates. This is the single sanitization point —
184 // the resource trusts it.
185 $ids = array_values(array_unique(array_filter(
186 array_map('intval', (array) $request->get('ids', [])),
187 function ($id) {
188 return $id > 0;
189 }
190 )));
191
192 // Validate: a reorder is meaningless without IDs (also catches a payload
193 // where every entry was 0 / non-numeric and got stripped above).
194 if (empty($ids)) {
195 return $this->response->sendError([
196 'message' => __('No group IDs provided.', 'fluent-cart')
197 ], 422);
198 }
199
200 // Reject (do NOT silently truncate) an oversized payload so no group's
201 // position is lost — a full-list reorder assigns a dense 1..N serial and
202 // can't be split into batches. The attribute-group library is a small
203 // admin-managed set; the cap is far above any real catalog and bounds the
204 // whereIn ownership lookup in AttrGroupResource::reorder.
205 $maxGroups = (int) apply_filters('fluent_cart/attribute_groups/max_reorder', 500);
206 if (count($ids) > $maxGroups) {
207 return $this->response->sendError([
208 /* translators: %1$d: maximum number of groups that can be reordered in one request */
209 'message' => sprintf(
210 __('Too many groups in the reorder request (maximum %1$d).', 'fluent-cart'),
211 $maxGroups
212 )
213 ], 422);
214 }
215
216 $result = AttrGroupResource::reorder([
217 'ids' => $ids,
218 ]);
219
220 if (is_wp_error($result)) {
221 return $result;
222 }
223 return $this->response->sendSuccess($result);
224 }
225 }
226