PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.6
Yatra – Travel Booking & Tour Operator Software v3.0.2.6
3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 2.0.10 All 83 releases
yatra / app / Controllers / AttributeController.php

AttributeController.php in Yatra – Travel Booking & Tour Operator Software 3.0.2.6, at app/Controllers/AttributeController.php

943 lines 35.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Yatra\Controllers;
6
7 use WP_REST_Request;
8 use WP_REST_Response;
9 use WP_Error;
10 use Yatra\Services\AttributeService;
11
12 /**
13 * Attribute REST API Controller
14 *
15 * Handles REST API endpoints for trip attributes
16 *
17 * Endpoints:
18 * GET /yatra/v1/attributes - List attributes
19 * POST /yatra/v1/attributes - Create attribute
20 * GET /yatra/v1/attributes/{id} - Get single attribute
21 * PUT /yatra/v1/attributes/{id} - Update attribute
22 * DELETE /yatra/v1/attributes/{id} - Delete attribute
23 * POST /yatra/v1/attributes/orders - Update display orders
24 * GET /yatra/v1/attributes/search - Search attributes
25 * GET /yatra/v1/attributes/frontend - Get frontend attributes
26 * GET /yatra/v1/attributes/filterable - Get filterable attributes
27 * GET /yatra/v1/attributes/values/{id} - Get attribute values
28 * GET /yatra/v1/trips/{id}/attributes - Get trip attributes
29 * POST /yatra/v1/trips/{id}/attributes - Set trip attribute
30 * DELETE /yatra/v1/trips/{id}/attributes/{attr_id} - Remove trip attribute
31 */
32 class AttributeController extends BaseController
33 {
34 /**
35 * @var AttributeService
36 */
37 private $attributeService;
38
39 /**
40 * Constructor
41 */
42 public function __construct()
43 {
44 $this->rest_base = 'attributes';
45 $this->attributeService = new AttributeService();
46 }
47
48 /**
49 * Register REST API routes
50 */
51 public function register_routes(): void
52 {
53 if (defined('WP_DEBUG') && WP_DEBUG) {
54 }
55
56 // Standard CRUD routes
57 $this->registerCrudRoutes();
58
59 // Additional routes
60 register_rest_route($this->namespace, "/{$this->rest_base}/search", [
61 'methods' => 'GET',
62 'callback' => [$this, 'search'],
63 'permission_callback' => [$this, 'search_permissions_check'],
64 'args' => [
65 'q' => [
66 'required' => true,
67 'type' => 'string',
68 'minLength' => 2,
69 'sanitize_callback' => 'sanitize_text_field',
70 ],
71 ],
72 ]);
73
74 register_rest_route($this->namespace, "/{$this->rest_base}/frontend", [
75 'methods' => 'GET',
76 'callback' => [$this, 'get_frontend_attributes'],
77 'permission_callback' => '__return_true', // Public endpoint
78 ]);
79
80 register_rest_route($this->namespace, "/{$this->rest_base}/filterable", [
81 'methods' => 'GET',
82 'callback' => [$this, 'get_filterable_attributes'],
83 'permission_callback' => '__return_true', // Public endpoint
84 ]);
85
86 // Stats endpoint
87 register_rest_route($this->namespace, "/{$this->rest_base}/stats", [
88 'methods' => 'GET',
89 'callback' => [$this, 'getStats'],
90 'permission_callback' => [$this, 'get_permissions_check'],
91 ]);
92
93 register_rest_route($this->namespace, "/{$this->rest_base}/values/(?P<id>\d+)", [
94 'methods' => 'GET',
95 'callback' => [$this, 'get_attribute_values'],
96 'permission_callback' => '__return_true', // Public endpoint
97 'args' => [
98 'id' => [
99 'required' => true,
100 'type' => 'integer',
101 'validate_callback' => function($param) {
102 return is_numeric($param) && $param > 0;
103 },
104 ],
105 ],
106 ]);
107
108 register_rest_route($this->namespace, "/{$this->rest_base}/orders", [
109 'methods' => 'POST',
110 'callback' => [$this, 'update_display_orders'],
111 'permission_callback' => [$this, 'check_permission'],
112 'args' => [
113 'orders' => [
114 'required' => true,
115 'type' => 'array',
116 'validate_callback' => function($param) {
117 return is_array($param) && !empty($param);
118 },
119 ],
120 ],
121 ]);
122
123 register_rest_route($this->namespace, "/{$this->rest_base}/check-slug", [
124 'methods' => 'GET',
125 'callback' => [$this, 'check_slug'],
126 'permission_callback' => [$this, 'get_permissions_check'],
127 'args' => [
128 'slug' => [
129 'required' => true,
130 'type' => 'string',
131 'sanitize_callback' => 'sanitize_text_field',
132 ],
133 'exclude_id' => [
134 'required' => false,
135 'type' => 'integer',
136 'validate_callback' => function($param) {
137 return is_numeric($param) && $param > 0;
138 },
139 ],
140 ],
141 ]);
142
143 // Trip attribute routes
144 register_rest_route($this->namespace, "/trips/(?P<trip_id>\d+)/attributes", [
145 'methods' => 'GET',
146 'callback' => [$this, 'get_trip_attributes'],
147 'permission_callback' => '__return_true', // Public endpoint
148 'args' => [
149 'trip_id' => [
150 'required' => true,
151 'type' => 'integer',
152 'validate_callback' => function($param) {
153 return is_numeric($param) && $param > 0;
154 },
155 ],
156 ],
157 ]);
158
159 register_rest_route($this->namespace, "/trips/(?P<trip_id>\d+)/attributes", [
160 'methods' => 'POST',
161 'callback' => [$this, 'set_trip_attribute'],
162 'permission_callback' => [$this, 'update_permissions_check'],
163 'args' => [
164 'trip_id' => [
165 'required' => true,
166 'type' => 'integer',
167 'validate_callback' => function($param) {
168 return is_numeric($param) && $param > 0;
169 },
170 ],
171 'attribute_id' => [
172 'required' => true,
173 'type' => 'integer',
174 'validate_callback' => function($param) {
175 return is_numeric($param) && $param > 0;
176 },
177 ],
178 'value' => [
179 'required' => true,
180 'type' => 'string',
181 'sanitize_callback' => 'wp_kses_post',
182 ],
183 ],
184 ]);
185
186 register_rest_route($this->namespace, "/trips/(?P<trip_id>\d+)/attributes/(?P<attribute_id>\d+)", [
187 'methods' => 'DELETE',
188 'callback' => [$this, 'remove_trip_attribute'],
189 'permission_callback' => [$this, 'update_permissions_check'],
190 'args' => [
191 'trip_id' => [
192 'required' => true,
193 'type' => 'integer',
194 'validate_callback' => function($param) {
195 return is_numeric($param) && $param > 0;
196 },
197 ],
198 'attribute_id' => [
199 'required' => true,
200 'type' => 'integer',
201 'validate_callback' => function($param) {
202 return is_numeric($param) && $param > 0;
203 },
204 ],
205 ],
206 ]);
207 }
208
209 /**
210 * Get all attributes
211 */
212 public function get_items(WP_REST_Request $request): WP_REST_Response
213 {
214 try {
215 $params = $request->get_params();
216
217 // Get pagination parameters
218 $page = isset($params['page']) ? (int) $params['page'] : 1;
219 $perPage = isset($params['per_page']) ? (int) $params['per_page'] : 10;
220
221 // Build filters
222 $filters = [];
223 if (!empty($params['status'])) {
224 $filters['status'] = $params['status'];
225 }
226 if (!empty($params['field_type'])) {
227 $filters['field_type'] = $params['field_type'];
228 }
229 if (!empty($params['show_on_frontend'])) {
230 $filters['show_on_frontend'] = $params['show_on_frontend'];
231 }
232 if (!empty($params['show_in_filters'])) {
233 $filters['show_in_filters'] = $params['show_in_filters'];
234 }
235
236 // Add search filter
237 if (!empty($params['search'])) {
238 $filters['search'] = $params['search'];
239 }
240
241 // Add sorting
242 if (!empty($params['orderby'])) {
243 $filters['orderby'] = $params['orderby'];
244 }
245 if (!empty($params['order'])) {
246 $filters['order'] = $params['order'];
247 }
248
249 // Get paginated results
250 $result = $this->attributeService->paginate($page, $perPage, $filters);
251 $total = $this->attributeService->count($filters);
252
253 // Process icon fields and metadata for all attributes
254 foreach ($result as $attribute) {
255 // Parse metadata JSON and extract attribute properties
256 if (!empty($attribute->metadata)) {
257 $metadata = json_decode($attribute->metadata, true);
258 if (is_array($metadata)) {
259 // Add metadata fields as properties to the attribute object
260 $attribute->field_type = $metadata['field_type'] ?? null;
261 $attribute->required = $metadata['required'] ?? false;
262 $attribute->show_on_frontend = $metadata['show_on_frontend'] ?? false;
263 $attribute->show_in_filters = $metadata['show_in_filters'] ?? false;
264 $attribute->filter_type = $metadata['filter_type'] ?? null;
265 $attribute->searchable = $metadata['searchable'] ?? false;
266 $attribute->display_order = $metadata['display_order'] ?? 0;
267 $attribute->default_value = $metadata['default_value'] ?? null;
268 $attribute->placeholder = $metadata['placeholder'] ?? null;
269 $attribute->field_options = $metadata['field_options'] ?? null;
270 $attribute->validation_rules = $metadata['validation_rules'] ?? null;
271 }
272 }
273
274 if (!empty($attribute->icon)) {
275 $icon_data = maybe_unserialize($attribute->icon);
276 if (is_array($icon_data)) {
277 // Resolve image URLs for image type icons
278 if ($icon_data['type'] === 'image' && !empty($icon_data['value'])) {
279 $value = $icon_data['value'];
280 $image_url = '';
281
282 if (is_numeric($value)) {
283 $maybe_url = wp_get_attachment_image_url((int) $value, 'large');
284 if (!empty($maybe_url)) {
285 $image_url = $maybe_url;
286 }
287 } elseif (is_string($value) && filter_var($value, FILTER_VALIDATE_URL)) {
288 $image_url = $value;
289 }
290
291 $icon_data['value'] = $image_url;
292 }
293 $attribute->icon = $icon_data;
294 } else {
295 // Handle legacy string format
296 $attribute->icon = [
297 'type' => 'icon',
298 'value' => $attribute->icon
299 ];
300 }
301 } else {
302 $attribute->icon = null;
303 }
304 }
305
306 return $this->paginated_response($result, $total, $page, $perPage);
307
308 } catch (\Exception $e) {
309 return $this->error_response('Failed to retrieve attributes: ' . $e->getMessage(), 500);
310 }
311 }
312
313 /**
314 * Create attribute
315 */
316 public function create_item(WP_REST_Request $request)
317 {
318 try {
319 $data = $this->prepare_item_for_database($request);
320
321 // Debug logging for icon data
322 // Handle slug conflicts by generating unique slug if needed
323 $originalSlug = $data['slug'];
324 $slug = $originalSlug;
325 $counter = 1;
326
327 while ($this->attributeService->slugExists($slug)) {
328 $slug = $originalSlug . '-' . $counter;
329 $counter++;
330 }
331
332 $data['slug'] = $slug;
333
334 $attributeId = $this->attributeService->createAttribute($data);
335
336 if ($attributeId) {
337 $attribute = $this->attributeService->getById($attributeId);
338
339 // Parse metadata JSON and extract attribute properties
340 if (!empty($attribute->metadata)) {
341 $metadata = json_decode($attribute->metadata, true);
342 if (is_array($metadata)) {
343 // Add metadata fields as properties to the attribute object
344 $attribute->field_type = $metadata['field_type'] ?? null;
345 $attribute->required = $metadata['required'] ?? false;
346 $attribute->show_on_frontend = $metadata['show_on_frontend'] ?? false;
347 $attribute->show_in_filters = $metadata['show_in_filters'] ?? false;
348 $attribute->filter_type = $metadata['filter_type'] ?? null;
349 $attribute->searchable = $metadata['searchable'] ?? false;
350 $attribute->display_order = $metadata['display_order'] ?? 0;
351 $attribute->default_value = $metadata['default_value'] ?? null;
352 $attribute->placeholder = $metadata['placeholder'] ?? null;
353 $attribute->field_options = $metadata['field_options'] ?? null;
354 $attribute->validation_rules = $metadata['validation_rules'] ?? null;
355 }
356 }
357
358 return $this->success_response($attribute, 201);
359 }
360
361 return $this->error_response('Failed to create attribute', 500);
362
363 } catch (\Exception $e) {
364 return $this->error_response($e->getMessage(), 400);
365 }
366 }
367
368 /**
369 * Get single attribute
370 */
371 public function get_item(WP_REST_Request $request)
372 {
373 try {
374 $id = (int) $request->get_param('id');
375
376 $attribute = $this->attributeService->getById($id);
377
378 if (!$attribute) {
379 return $this->error_response('Attribute not found', 404);
380 }
381
382 // Parse metadata JSON and extract attribute properties
383 if (!empty($attribute->metadata)) {
384 $metadata = json_decode($attribute->metadata, true);
385 if (is_array($metadata)) {
386 // Add metadata fields as properties to the attribute object
387 $attribute->field_type = $metadata['field_type'] ?? null;
388 $attribute->required = $metadata['required'] ?? false;
389 $attribute->show_on_frontend = $metadata['show_on_frontend'] ?? false;
390 $attribute->show_in_filters = $metadata['show_in_filters'] ?? false;
391 $attribute->filter_type = $metadata['filter_type'] ?? null;
392 $attribute->searchable = $metadata['searchable'] ?? false;
393 $attribute->display_order = $metadata['display_order'] ?? 0;
394 $attribute->default_value = $metadata['default_value'] ?? null;
395 $attribute->placeholder = $metadata['placeholder'] ?? null;
396 $attribute->field_options = $metadata['field_options'] ?? null;
397 $attribute->validation_rules = $metadata['validation_rules'] ?? null;
398 }
399 }
400
401 // Process icon field - unserialize if it's serialized
402 if (!empty($attribute->icon)) {
403 $icon_data = maybe_unserialize($attribute->icon);
404 if (is_array($icon_data)) {
405 // Resolve image URLs for image type icons
406 if ($icon_data['type'] === 'image' && !empty($icon_data['value'])) {
407 $value = $icon_data['value'];
408 $image_url = '';
409
410 if (is_numeric($value)) {
411 $maybe_url = wp_get_attachment_image_url((int) $value, 'large');
412 if (!empty($maybe_url)) {
413 $image_url = $maybe_url;
414 }
415 } elseif (is_string($value) && filter_var($value, FILTER_VALIDATE_URL)) {
416 $image_url = $value;
417 }
418
419 $icon_data['value'] = $image_url;
420 }
421 $attribute->icon = $icon_data;
422 } else {
423 // Handle legacy string format
424 $attribute->icon = [
425 'type' => 'icon',
426 'value' => $attribute->icon
427 ];
428 }
429 } else {
430 $attribute->icon = null;
431 }
432
433 return $this->success_response($attribute);
434
435 } catch (\Exception $e) {
436 return $this->error_response('Failed to retrieve attribute: ' . $e->getMessage(), 500);
437 }
438 }
439
440 /**
441 * Update attribute
442 */
443 public function update_item(WP_REST_Request $request)
444 {
445 try {
446 $id = (int) $request->get_param('id');
447 $data = $this->prepare_item_for_database($request);
448
449 $result = $this->attributeService->updateAttribute($id, $data);
450
451 if ($result) {
452 $attribute = $this->attributeService->getById($id);
453
454 // Parse metadata JSON and extract attribute properties
455 if (!empty($attribute->metadata)) {
456 $metadata = json_decode($attribute->metadata, true);
457 if (is_array($metadata)) {
458 // Add metadata fields as properties to the attribute object
459 $attribute->field_type = $metadata['field_type'] ?? null;
460 $attribute->required = $metadata['required'] ?? false;
461 $attribute->show_on_frontend = $metadata['show_on_frontend'] ?? false;
462 $attribute->show_in_filters = $metadata['show_in_filters'] ?? false;
463 $attribute->filter_type = $metadata['filter_type'] ?? null;
464 $attribute->searchable = $metadata['searchable'] ?? false;
465 $attribute->display_order = $metadata['display_order'] ?? 0;
466 $attribute->default_value = $metadata['default_value'] ?? null;
467 $attribute->placeholder = $metadata['placeholder'] ?? null;
468 $attribute->field_options = $metadata['field_options'] ?? null;
469 $attribute->validation_rules = $metadata['validation_rules'] ?? null;
470 }
471 }
472
473 return $this->success_response($attribute);
474 }
475
476 return $this->error_response('Failed to update attribute', 500);
477
478 } catch (\Exception $e) {
479 return $this->error_response($e->getMessage(), 400);
480 }
481 }
482
483 /**
484 * Delete attribute
485 */
486 public function delete_item(WP_REST_Request $request)
487 {
488 try {
489 $id = (int) $request->get_param('id');
490
491 $result = $this->attributeService->deleteAttribute($id);
492
493 if ($result) {
494 return $this->success_response(['deleted' => true]);
495 }
496
497 return $this->error_response('Failed to delete attribute', 500);
498
499 } catch (\Exception $e) {
500 return $this->error_response($e->getMessage(), 400);
501 }
502 }
503
504 /**
505 * Get attribute values
506 */
507 public function get_attribute_values(WP_REST_Request $request)
508 {
509 try {
510 $id = (int) $request->get_param('id');
511
512 $values = $this->attributeService->getAttributeValues($id);
513
514 return $this->success_response($values);
515
516 } catch (\Exception $e) {
517 return $this->error_response('Failed to retrieve attribute values: ' . $e->getMessage(), 500);
518 }
519 }
520
521 /**
522 * Update display orders
523 */
524 public function update_orders(WP_REST_Request $request)
525 {
526 try {
527 $orders = $request->get_param('orders');
528
529 $result = $this->attributeService->updateDisplayOrders($orders);
530
531 if ($result) {
532 return $this->success_response(['updated' => true]);
533 }
534
535 return $this->error_response('Failed to update display orders', 500);
536
537 } catch (\Exception $e) {
538 return $this->error_response($e->getMessage(), 400);
539 }
540 }
541
542 /**
543 * Update display orders
544 */
545 public function update_display_orders(WP_REST_Request $request)
546 {
547 try {
548 $orders = $request->get_param('orders');
549
550 $result = $this->attributeService->updateDisplayOrders($orders);
551
552 if ($result) {
553 return $this->success_response(['message' => 'Display orders updated successfully']);
554 }
555
556 return $this->error_response('Failed to update display orders', 500);
557
558 } catch (\Exception $e) {
559 return $this->error_response($e->getMessage(), 400);
560 }
561 }
562
563 /**
564 * Check if slug exists and suggest unique slug if needed
565 */
566 public function check_slug(WP_REST_Request $request)
567 {
568 try {
569 $slug = sanitize_title($request->get_param('slug'));
570 $excludeId = $request->get_param('exclude_id');
571
572 if (empty($slug)) {
573 return $this->error_response('Slug is required', 400);
574 }
575
576 // Check if slug exists (excluding current attribute if editing)
577 $exists = $this->attributeService->slugExists($slug, $excludeId);
578
579 if (!$exists) {
580 return $this->success_response([
581 'exists' => false,
582 'suggested_slug' => $slug
583 ]);
584 }
585
586 // Generate unique slug
587 $originalSlug = $slug;
588 $counter = 1;
589
590 while ($this->attributeService->slugExists($slug, $excludeId)) {
591 $slug = $originalSlug . '-' . $counter;
592 $counter++;
593
594 // Prevent infinite loop
595 if ($counter > 100) {
596 break;
597 }
598 }
599
600 return $this->success_response([
601 'exists' => true,
602 'suggested_slug' => $slug
603 ]);
604
605 } catch (\Exception $e) {
606 return $this->error_response($e->getMessage(), 400);
607 }
608 }
609
610 /**
611 * Get trip attributes
612 */
613 public function get_trip_attributes(WP_REST_Request $request): WP_REST_Response
614 {
615 try {
616 $tripId = (int) $request->get_param('trip_id');
617
618 $attributes = $this->attributeService->getTripAttributes($tripId);
619
620 return $this->success_response($attributes);
621
622 } catch (\Exception $e) {
623 return $this->error_response('Failed to retrieve trip attributes: ' . $e->getMessage(), 500);
624 }
625 }
626
627 /**
628 * Set trip attribute
629 */
630 public function set_trip_attribute(WP_REST_Request $request)
631 {
632 try {
633 $tripId = (int) $request->get_param('trip_id');
634 $attributeId = (int) $request->get_param('attribute_id');
635 $value = $request->get_param('value');
636
637 $result = $this->attributeService->setTripAttribute($tripId, $attributeId, $value);
638
639 if ($result) {
640 return $this->success_response(['set' => true]);
641 }
642
643 return $this->error_response('Failed to set trip attribute', 500);
644
645 } catch (\Exception $e) {
646 return $this->error_response($e->getMessage(), 400);
647 }
648 }
649
650 /**
651 * Remove trip attribute
652 */
653 public function remove_trip_attribute(WP_REST_Request $request)
654 {
655 try {
656 $tripId = (int) $request->get_param('trip_id');
657 $attributeId = (int) $request->get_param('attribute_id');
658
659 $result = $this->attributeService->removeTripAttribute($tripId, $attributeId);
660
661 if ($result) {
662 return $this->success_response(['removed' => true]);
663 }
664
665 return $this->error_response('Failed to remove trip attribute', 500);
666
667 } catch (\Exception $e) {
668 return $this->error_response('Failed to remove trip attribute: ' . $e->getMessage(), 500);
669 }
670 }
671
672 /**
673 * Prepare item for database
674 */
675 private function prepare_item_for_database(WP_REST_Request $request): array
676 {
677 $data = [];
678
679 // Basic fields
680 if ($request->has_param('name')) {
681 $data['name'] = sanitize_text_field($request->get_param('name'));
682 }
683
684 if ($request->has_param('slug')) {
685 $data['slug'] = sanitize_title($request->get_param('slug'));
686 }
687
688 if ($request->has_param('description')) {
689 $data['description'] = wp_kses_post($request->get_param('description'));
690 }
691
692 // Handle icon field
693 if ($request->has_param('icon')) {
694 $icon = $request->get_param('icon');
695 if (is_array($icon)) {
696 // Sanitize icon array
697 $data['icon'] = [
698 'type' => isset($icon['type']) && in_array($icon['type'], ['icon', 'image'], true)
699 ? $icon['type']
700 : 'icon',
701 'value' => isset($icon['value'])
702 ? sanitize_text_field($icon['value'])
703 : '',
704 ];
705 } elseif (is_string($icon)) {
706 // Handle legacy string format
707 $data['icon'] = sanitize_text_field($icon);
708 }
709 }
710
711 if ($request->has_param('field_type')) {
712 $data['field_type'] = sanitize_text_field($request->get_param('field_type'));
713 }
714
715 if ($request->has_param('field_options')) {
716 $options = $request->get_param('field_options');
717 if (is_string($options)) {
718 $decoded = json_decode(trim($options), true);
719 $options = is_array($decoded) ? $decoded : [];
720 }
721 if (is_array($options)) {
722 $data['field_options'] = array_values(array_filter(array_map(static function ($row) {
723 if (!is_array($row)) {
724 return null;
725 }
726 $label = isset($row['label']) ? sanitize_text_field((string) $row['label']) : '';
727 $value = isset($row['value']) ? sanitize_text_field((string) $row['value']) : '';
728 if ($label === '' && $value === '') {
729 return null;
730 }
731 if ($value === '' && $label !== '') {
732 $value = sanitize_title($label);
733 }
734 return ['label' => $label, 'value' => $value];
735 }, $options)));
736 }
737 }
738
739 if ($request->has_param('default_value')) {
740 $data['default_value'] = sanitize_text_field($request->get_param('default_value'));
741 }
742
743 if ($request->has_param('placeholder')) {
744 $data['placeholder'] = sanitize_text_field($request->get_param('placeholder'));
745 }
746
747 if ($request->has_param('required')) {
748 $data['required'] = (bool) $request->get_param('required');
749 }
750
751 if ($request->has_param('validation_rules')) {
752 $rules = $request->get_param('validation_rules');
753 if (is_array($rules)) {
754 $data['validation_rules'] = $rules;
755 }
756 }
757
758 if ($request->has_param('display_order')) {
759 $data['display_order'] = (int) $request->get_param('display_order');
760 }
761
762 if ($request->has_param('show_on_frontend')) {
763 $data['show_on_frontend'] = (bool) $request->get_param('show_on_frontend');
764 }
765
766 if ($request->has_param('show_in_filters')) {
767 $data['show_in_filters'] = (bool) $request->get_param('show_in_filters');
768 }
769
770 if ($request->has_param('filter_type')) {
771 $data['filter_type'] = sanitize_text_field($request->get_param('filter_type'));
772 }
773
774 if ($request->has_param('searchable')) {
775 $data['searchable'] = (bool) $request->get_param('searchable');
776 }
777
778 if ($request->has_param('status')) {
779 $raw = $request->get_param('status');
780 $s = is_string($raw) ? strtolower(trim(sanitize_text_field($raw))) : '';
781 if (in_array($s, ['publish', 'draft', 'trash'], true)) {
782 $data['status'] = $s;
783 }
784 }
785
786 return $data;
787 }
788
789 /**
790 * Check permissions for read operations
791 */
792 public function get_permissions_check(): bool
793 {
794 return current_user_can('manage_options');
795 }
796
797 /**
798 * Check permissions for create/update/delete operations
799 */
800 public function check_permission(?WP_REST_Request $request = null): bool
801 {
802 $hasPermission = current_user_can('manage_options');
803
804 if (defined('WP_DEBUG') && WP_DEBUG) {
805 }
806
807 return $hasPermission;
808 }
809
810 /**
811 * Check permissions for search operations
812 */
813 public function search_permissions_check(): bool
814 {
815 return current_user_can('manage_options');
816 }
817
818 /**
819 * Check permissions for update operations
820 */
821 public function update_permissions_check(): bool
822 {
823 return current_user_can('manage_options');
824 }
825
826 /**
827 * Get item schema
828 */
829 public function get_item_schema(): array
830 {
831 return [
832 '$schema' => 'http://json-schema.org/draft-04/schema#',
833 'title' => 'attribute',
834 'type' => 'object',
835 'properties' => [
836 'id' => [
837 'description' => 'Unique identifier for the attribute.',
838 'type' => 'integer',
839 'readonly' => true,
840 ],
841 'name' => [
842 'description' => 'Attribute name.',
843 'type' => 'string',
844 'required' => true,
845 ],
846 'slug' => [
847 'description' => 'URL-friendly identifier.',
848 'type' => 'string',
849 ],
850 'description' => [
851 'description' => 'Attribute description.',
852 'type' => 'string',
853 ],
854 'field_type' => [
855 'description' => 'Form field type.',
856 'type' => 'string',
857 'enum' => ['text_field', 'number', 'email', 'url', 'textarea', 'select', 'radio', 'checkbox', 'date', 'time', 'color', 'file'],
858 'default' => 'text_field',
859 ],
860 'field_options' => [
861 'description' => 'Options for select/radio/checkbox fields.',
862 'type' => 'array',
863 ],
864 'default_value' => [
865 'description' => 'Default value.',
866 'type' => 'string',
867 ],
868 'placeholder' => [
869 'description' => 'Field placeholder text.',
870 'type' => 'string',
871 ],
872 'required' => [
873 'description' => 'Whether attribute is required.',
874 'type' => 'boolean',
875 'default' => false,
876 ],
877 'validation_rules' => [
878 'description' => 'Validation rules.',
879 'type' => 'array',
880 ],
881 'display_order' => [
882 'description' => 'Display order.',
883 'type' => 'integer',
884 'default' => 0,
885 ],
886 'show_on_frontend' => [
887 'description' => 'Show on trip pages.',
888 'type' => 'boolean',
889 'default' => true,
890 ],
891 'show_in_filters' => [
892 'description' => 'Show in trip listing filters.',
893 'type' => 'boolean',
894 'default' => false,
895 ],
896 'filter_type' => [
897 'description' => 'How to filter this attribute.',
898 'type' => 'string',
899 'enum' => ['exact', 'partial', 'range', 'dropdown'],
900 'default' => 'exact',
901 ],
902 'searchable' => [
903 'description' => 'Include in search index.',
904 'type' => 'boolean',
905 'default' => false,
906 ],
907 'status' => [
908 'description' => 'Attribute status.',
909 'type' => 'string',
910 'enum' => ['publish', 'draft', 'trash'],
911 'default' => 'publish',
912 ],
913 'created_at' => [
914 'description' => 'Creation date.',
915 'type' => 'string',
916 'format' => 'date-time',
917 'readonly' => true,
918 ],
919 'updated_at' => [
920 'description' => 'Last updated date.',
921 'type' => 'string',
922 'format' => 'date-time',
923 'readonly' => true,
924 ],
925 ],
926 ];
927 }
928
929 /**
930 * Get attribute statistics
931 * GET /attributes/stats
932 */
933 public function getStats(WP_REST_Request $request)
934 {
935 try {
936 $stats = $this->attributeService->getStatusCounts();
937 return $this->success_response($stats);
938 } catch (\Exception $e) {
939 return $this->error_response($e->getMessage(), 500);
940 }
941 }
942 }
943