PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.5.3
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.5.3
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
fluent-cart / app / Hooks / Handlers / AttributesHandler.php

AttributesHandler.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.5.3, at app/Hooks/Handlers/AttributesHandler.php

207 lines 9.6 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\Hooks\Handlers;
4
5 use FluentCart\App\Models\AttributeGroup;
6 use FluentCart\App\Services\Filter\AttrGroupFilter;
7 use FluentCart\App\Services\Filter\AttrTermFilter;
8 use FluentCart\App\Services\Permission\PermissionManager;
9 use FluentCart\App\Vite;
10 use FluentCart\Database\Seeder\AttributeSeeder;
11 use FluentCart\Framework\Support\Arr;
12
13 class AttributesHandler
14 {
15 public function register()
16 {
17 // Seed the system-template attribute groups (Color, Size, …) once the
18 // attribute tables are migrated. Fires from DBMigrator::migrate() on
19 // activation/migrate; the seeder is idempotent (early-returns when any
20 // group already exists) so re-runs never duplicate merchant data.
21 add_action('fluent_cart/after_migrate', [AttributeSeeder::class, 'seed']);
22
23 add_action('fluent_cart/loading_app', function () {
24 Vite::enqueueScript('fluent_cart_attributes', 'attributes/attributes.js');
25 });
26
27 // Hide the Attributes submenu item by default on FluentCart admin
28 // pages. useNavigationMenuUpdateService toggles it back on whenever the
29 // active route's active_menu is 'products', matching the behavior of
30 // the existing "↳ Inventory" child.
31 add_action('admin_enqueue_scripts', function () {
32 wp_register_style('fluent-cart-admin', false);
33 wp_enqueue_style('fluent-cart-admin');
34 wp_add_inline_style('fluent-cart-admin', '
35 .toplevel_page_fluent-cart li.fluent_cart_attributes {
36 display: none;
37 }
38 ');
39 });
40
41 // Show Attributes under Products in the WP admin left sidebar.
42 add_action('fluent_cart/admin_submenu_added', function () {
43 global $submenu;
44 if (!isset($submenu['fluent-cart'])) {
45 return;
46 }
47
48 $capability = 'manage_options';
49 if (!current_user_can('manage_options')) {
50 $capability = PermissionManager::ADMIN_CAP;
51 }
52
53 $entry = [
54 __('↳ Attributes', 'fluent-cart'),
55 $capability,
56 'admin.php?page=fluent-cart#/attributes',
57 '',
58 'fluent_cart_attributes',
59 ];
60
61 $newSubmenu = [];
62 foreach ($submenu['fluent-cart'] as $key => $item) {
63 $newSubmenu[$key] = $item;
64 if ($key === 'products' && !isset($newSubmenu['attributes'])) {
65 $newSubmenu['attributes'] = $entry;
66 }
67 }
68 if (!isset($newSubmenu['attributes'])) {
69 $newSubmenu['attributes'] = $entry;
70 }
71 $submenu['fluent-cart'] = $newSubmenu;
72 }, 999); // late priority: insert Attributes right after Products AFTER the
73 // Inventory child is placed, so Attributes sits above Inventory.
74
75 add_filter('fluent_cart/admin_filter_options', function ($filterOptions, $args) {
76 $filterOptions['attr_groups_filter_options'] = AttrGroupFilter::getTableFilterOptions();
77 $filterOptions['attr_terms_filter_options'] = AttrTermFilter::getTableFilterOptions();
78 return $filterOptions;
79 }, 10, 2);
80
81 add_filter('fluent_cart/admin_table_saved_views', function ($tableConfig, $args) {
82 $filterOptions = Arr::get($args, 'filterOptions', []);
83 $tableConfig['attr_groups_table'] = ['filters' => Arr::get($filterOptions, 'attr_groups_filter_options', [])];
84 $tableConfig['attr_terms_table'] = ['filters' => Arr::get($filterOptions, 'attr_terms_filter_options', [])];
85 return $tableConfig;
86 }, 10, 2);
87
88 add_filter('fluent_cart/products_filter_options', function ($options) {
89 $options['attributes'] = [
90 'label' => __('Attributes', 'fluent-cart'),
91 'value' => 'attributes',
92 'children' => [
93 [
94 'label' => __('Attribute Term', 'fluent-cart'),
95 'value' => 'attribute_term',
96 'filter_type' => 'custom',
97 'type' => 'cascading_select',
98 'callback' => static function ($query, $item) {
99 $groupId = intval(Arr::get($item, 'value.group_id', 0));
100 $termIds = array_filter(array_map('intval', (array) Arr::get($item, 'value.term_ids', [])));
101 if (!$groupId) {
102 return;
103 }
104 $operator = Arr::get($item, 'operator', 'in');
105 $match = Arr::get($item, 'value.match', 'any');
106
107 if (empty($termIds)) {
108 // Group selected, no terms: match products that have this attribute at all
109 if ($operator === 'not_in') {
110 $query->whereDoesntHave('variants', function ($variantQuery) use ($groupId) {
111 $variantQuery->whereHas('attrRelations', function ($relQuery) use ($groupId) {
112 $relQuery->where('group_id', $groupId);
113 });
114 });
115 } else {
116 $query->whereHas('variants', function ($variantQuery) use ($groupId) {
117 $variantQuery->whereHas('attrRelations', function ($relQuery) use ($groupId) {
118 $relQuery->where('group_id', $groupId);
119 });
120 });
121 }
122 return;
123 }
124
125 if ($operator === 'not_in') {
126 $query->whereDoesntHave('variants', function ($variantQuery) use ($termIds, $groupId) {
127 $variantQuery->whereHas('attrRelations', function ($relQuery) use ($termIds, $groupId) {
128 $relQuery->where('group_id', $groupId)->whereIn('term_id', $termIds);
129 });
130 });
131 } elseif ($match === 'all') {
132 // AND: product must have a variant for each individual term
133 foreach ($termIds as $termId) {
134 $query->whereHas('variants', function ($variantQuery) use ($termId, $groupId) {
135 $variantQuery->whereHas('attrRelations', function ($relQuery) use ($termId, $groupId) {
136 $relQuery->where('group_id', $groupId)->where('term_id', $termId);
137 });
138 });
139 }
140 } else {
141 // OR: product must have a variant carrying any of the terms
142 $query->whereHas('variants', function ($variantQuery) use ($termIds, $groupId) {
143 $variantQuery->whereHas('attrRelations', function ($relQuery) use ($termIds, $groupId) {
144 $relQuery->where('group_id', $groupId)->whereIn('term_id', $termIds);
145 });
146 });
147 }
148 },
149 ],
150 ],
151 ];
152 return $options;
153 });
154
155 // Attribute groups for the cascading picker (left panel)
156 add_filter('fluent_cart/advanced_filter_options_attr_groups', function ($options, $args) {
157 $search = Arr::get($args, 'search', '');
158 $includeIds = array_filter(array_map('intval', (array) Arr::get($args, 'include_ids', [])));
159
160 $query = AttributeGroup::query()->orderBy('title', 'asc');
161
162 if ($search) {
163 $query->where('title', 'like', '%' . $search . '%');
164 }
165
166 if (!empty($includeIds)) {
167 $query->orWhereIn('id', $includeIds);
168 }
169
170 return $query->limit(200)->get()->map(function ($group) {
171 return ['id' => $group->id, 'title' => $group->title];
172 })->values()->toArray();
173 }, 10, 2);
174
175 // Terms for the selected group (right panel)
176 add_filter('fluent_cart/advanced_filter_options_attr_terms', function ($options, $args) {
177 $groupId = intval(Arr::get($args, 'parent_id', 0));
178 $search = sanitize_text_field(Arr::get($args, 'search', ''));
179 $includeIds = array_filter(array_map('intval', (array) Arr::get($args, 'include_ids', [])));
180
181 if (!$groupId) {
182 return [];
183 }
184
185 /** @var AttributeGroup $group */
186 $group = AttributeGroup::query()->find($groupId);
187 if (!$group) {
188 return [];
189 }
190
191 $query = $group->terms()->orderBy('serial', 'asc');
192
193 if ($search) {
194 $query->where('title', 'like', '%' . $search . '%');
195 }
196
197 if (!empty($includeIds)) {
198 $query->orWhereIn('id', $includeIds);
199 }
200
201 return $query->limit(50)->get()->map(function ($term) {
202 return ['id' => $term->id, 'title' => $term->title];
203 })->values()->toArray();
204 }, 10, 2);
205 }
206 }
207