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/Http/Controllers/AttributesController.php +142 -83 1.3.20 → 1.6.5 View file →
@@ -1,85 +1,117 @@
1 1 <?php
2 2
3 3 namespace FluentCart\App\Http\Controllers;
4 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;
5 10 use FluentCart\Api\Resource\AttrGroupResource;
6 11 use FluentCart\Api\Resource\AttrTermResource;
7 12 use FluentCart\App\Http\Requests\AttrGroupRequest;
8 13 use FluentCart\App\Http\Requests\AttrTermRequest;
9 -use FluentCart\Framework\Http\Request\Request;
10 -use FluentCart\Framework\Support\Arr;
14 +use FluentCart\App\Http\Requests\AttrTermUpdateRequest;
15 +use FluentCart\App\Services\Filter\AttrGroupFilter;
16 +use FluentCart\App\Services\Filter\AttrTermFilter;
11 17
12 18 class AttributesController extends Controller
13 19 {
20 + public function getGroup(Request $request, $group_id): array
21 + {
22 + return ['group' => AttrGroupResource::find($group_id, $request->all())];
23 + }
14 24
25 + public function getGroups(Request $request)
26 + {
27 + return ['groups' => AttrGroupFilter::fromRequest($request)->paginate()];
28 + }
29 +
15 30 /**
31 + * Two-pass payload for the Advanced Variation library picker.
16 32 *
17 - * @param Request $request
18 - * @param $group_id
19 - * @return array
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).
20 44 */
21 - public function getGroup(Request $request, $group_id): array
45 + public function getLibrary(Request $request): array
22 46 {
23 -
24 - return ['group' => AttrGroupResource::find($group_id, $request->all())];
25 - }
47 + $totalGroups = (int) AttributeGroup::query()->count();
26 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();
27 58
28 - public function getGroups(Request $request)
29 - {
30 -
31 - return ['groups' => AttrGroupResource::get($request->all())];
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 + ];
32 82 }
33 83
34 84 public function createGroup(AttrGroupRequest $request)
35 85 {
36 -
37 86 $data = $request->getSafe($request->sanitize());
38 - $arg = Arr::only($data, ['title', 'slug', 'settings', 'description']);
87 + $arg = Arr::only($data, ['title', 'slug', 'settings']);
39 88 $isCreated = AttrGroupResource::create($arg);
40 89
41 90 if (is_wp_error($isCreated)) {
42 - return $isCreated;
91 + return $isCreated;
43 92 }
44 93 return $this->response->sendSuccess($isCreated);
45 94 }
46 95
47 - /**
48 - * Update attribute group info
49 - *
50 - * @param AttrGroupRequest $request
51 - * @param $group_id
52 - * @return mixed
53 - */
54 96 public function updateGroup(AttrGroupRequest $request, $group_id)
55 97 {
56 -
57 98 $data = $request->getSafe($request->sanitize());
58 - $arg = Arr::only($data, ['title', 'slug', 'settings', 'description']);
59 - $isUpdated = AttrGroupResource::update($arg, $group_id);
99 + $arg = Arr::only($data, ['title', 'settings']);
100 + $isUpdated = AttrGroupResource::update($arg, $group_id);
60 101
61 102 if (is_wp_error($isUpdated)) {
62 - return $isUpdated;
103 + return $isUpdated;
63 104 }
64 105 return $this->response->sendSuccess($isUpdated);
65 -
66 106 }
67 107
68 - /**
69 - * Delete a group only if the terms of the group is unused
70 - *
71 - * @param Request $request
72 - * @param $group_id
73 - * @return mixed
74 - */
75 108 public function deleteGroup(Request $request, $group_id)
76 109 {
77 -
78 110 $isDeleted = AttrGroupResource::delete($group_id);
79 111
80 112 if (is_wp_error($isDeleted)) {
81 - return $isDeleted;
113 + return $isDeleted;
82 114 }
83 115 return $this->response->sendSuccess($isDeleted);
84 116 }
85 117
@@ -84,83 +116,110 @@
84 116 }
85 117
86 118 public function getTerms(Request $request, $group_id): array
87 119 {
88 -
89 - return ['terms' => AttrTermResource::get($request->all())];
120 + /** @var AttrTermFilter $filter */
121 + $filter = AttrTermFilter::fromRequest($request);
122 + $filter->setGroupId((int) $group_id);
123 + return ['terms' => $filter->paginate()];
90 124 }
91 125
92 - public function createTerm(AttrTermRequest $request, $group_id)
126 + public function createTerms(AttrTermRequest $request, $group_id)
93 127 {
94 -
95 128 $data = $request->getSafe($request->sanitize());
96 - $isCreated = AttrTermResource::create($data, ['group_id' => $group_id]);
97 129
98 - if (is_wp_error($isCreated)) {
99 - return $isCreated;
130 + if (empty(Arr::get($data, 'terms', []))) {
131 + return $this->response->sendError([
132 + 'message' => __('At least one term is required.', 'fluent-cart')
133 + ], 422);
100 134 }
101 - return $this->response->sendSuccess($isCreated);
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);
102 142 }
103 143
104 - /**
105 - *
106 - * @param AttrTermRequest $request
107 - * @param $group_id
108 - * @param $term_id
109 - * @return mixed
110 - */
111 - public function updateTerm(AttrTermRequest $request, $group_id, $term_id)
144 + public function updateTerm(AttrTermUpdateRequest $request, $group_id, $term_id)
112 145 {
113 -
114 146 $data = $request->getSafe($request->sanitize());
115 - $arg = Arr::only($data, ['title', 'slug', 'settings', 'serial', 'description']);
147 + $arg = Arr::only($data, ['title', 'settings']);
116 148 $isUpdated = AttrTermResource::update($arg, $term_id, ['group_id' => $group_id]);
117 149
118 150 if (is_wp_error($isUpdated)) {
119 - return $isUpdated;
151 + return $isUpdated;
120 152 }
121 153 return $this->response->sendSuccess($isUpdated);
122 154 }
123 155
124 - /**
125 - *
126 - * @param Request $request
127 - * @param $group_id
128 - * @param $term_id
129 - * @return mixed
130 - */
131 156 public function deleteTerm(Request $request, $group_id, $term_id)
132 157 {
133 -
134 158 $isDeleted = AttrTermResource::delete($term_id, ['group_id' => $group_id]);
135 159
136 160 if (is_wp_error($isDeleted)) {
137 - return $isDeleted;
161 + return $isDeleted;
138 162 }
139 163 return $this->response->sendSuccess($isDeleted);
140 164 }
141 165
142 - /**
143 - * Update the serial of a term only
144 - *
145 - * @param Request $request
146 - * @param $group_id
147 - * @param $term_id
148 - * @return mixed
149 - */
150 - public function changeTermSerial(Request $request, $group_id, $term_id)
166 + public function reorderTerms(Request $request, $group_id)
151 167 {
152 -
153 - $isUpdated = AttrTermResource::updateSerial([
154 - 'term_id' => $term_id,
168 + $result = AttrTermResource::reorder([
155 169 'group_id' => $group_id,
156 - 'move' => $request->get('direction', 'up'),
157 - ]);
170 + 'ids' => $request->get('ids', []),
171 + ]);
158 172
159 - if (is_wp_error($isUpdated)) {
160 - return $isUpdated;
173 + if (is_wp_error($result)) {
174 + return $result;
161 175 }
162 - return $this->response->sendSuccess($isUpdated);
176 + return $this->response->sendSuccess($result);
163 177 }
164 -}
165 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 + )));
166 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 +}