PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / 6.3.8
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings v6.3.8
7.2.1 7.2 7.1.2 7.1.1 7.1 7.0.4 7.0.6 7.0.7 6.3.8 6.3.7 6.3.6 6.3.5 6.3.4 6.3.3 6.3.1 trunk 5.7.3 5.7.5 5.8.1 5.8.2 5.8.3 5.8.4 5.8.6 6.0.4 6.0.5 All 36 releases
mlsimport / includes / mlsimport-field-selector-functions.php

mlsimport-field-selector-functions.php in MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings 6.3.8, at includes/mlsimport-field-selector-functions.php

888 lines 33.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MLS Import Field Selector Functions
4 *
5 * This file contains functions for rendering and managing the MLS field selection interface.
6 * It provides a tabular approach to field selection with filtering, pagination, and
7 * interactive features for managing large numbers of MLS fields.
8 *
9 * @package MLSImport
10 * @subpackage MLSImport/includes
11 * @since 1.0.0
12 */
13
14 // Exit if accessed directly
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit;
17 }
18 /**
19 * Prepares MLS fields for display by applying filters, sorting, and pagination.
20 *
21 * @since 1.0.0
22 * @param array $fields The array of MLS fields to process.
23 * @param array $options The saved options for field selections.
24 * @param array $filter_params Parameters for filtering and pagination.
25 * @return array The processed fields ready for display.
26 */
27 function prepare_mls_fields_for_display($fields, $options, $filter_params) {
28 // Apply search filter if search term is provided
29 if (!empty($filter_params['search_term'])) {
30 $fields = search_mls_fields($filter_params['search_term'], $fields);
31 }
32
33 // Apply import status filter
34 if ($filter_params['import_filter'] !== 'all') {
35 $fields = filter_mls_fields_by_import_status($filter_params['import_filter'], $fields, $options);
36 }
37
38 // Apply alphabetical filter
39 if (!empty($filter_params['alpha_filter'])) {
40 $fields = filter_mls_fields_by_alphabet($filter_params['alpha_filter'], $fields);
41 }
42
43 // Apply custom field order if available, otherwise preserve input order
44 if (isset($options['field_order']) && is_array($options['field_order']) && !empty($options['field_order'])) {
45
46
47 $fields = mlsimport_sort_all_fields_by_order($options);
48
49
50 }
51 // No else needed - when no field_order exists, we keep fields in their original order
52
53
54 return $fields;
55 }
56
57 /**
58 [mls-fields] => Array
59 (
60 [ListingKey] => 0
61 [AboveGradeFinishedArea] => 0
62 [AboveGradeFinishedAreaSource] => 0
63 [AboveGradeFinishedAreaUnits] => 0
64 [AboveGradeUnfinishedArea] => 0
65 [AboveGradeUnfinishedAreaSourc
66
67
68 [mls-fields-admin] => Array
69 (
70 [ListingKey] => 0
71 [AboveGradeFinishedArea] => 0
72 [AboveGradeFinishedAreaSource] => 0
73 [AboveGradeFinishedAreaUnits] => 0
74 [AboveGradeUnfinishedArea] => 0
75 [AboveGradeUnfinishedAreaSource] => 0
76 [AboveGradeUnfinishedAreaUnits] => 0
77
78
79 [mls-fields-label] => Array
80 (
81 [ListingKey] =>
82 [AboveGradeFinishedArea] =>
83 [AboveGradeFinishedAreaSource] =>
84 [AboveGradeFinishedAreaUnits] =>
85 [AboveGradeUnfinishedArea] =>
86 [AboveGradeUnfinishedAreaSource] =>
87 [AboveGradeUnfinishedAreaUnits] =>
88 [AccessCode] =>
89 [AccessibilityFeatures] =>
90 [ActivationDate] =>
91 [AdditionalParcelsDescription] =>
92 [AdditionalParcelsYN] =>
93
94
95
96
97 [mls-fields-map-postmeta] => Array
98 (
99 [ListingKey] =>
100 [AboveGradeFinishedArea] =>
101 [AboveGradeFinishedAreaSource] =>
102 [AboveGradeFinishedAreaUnits] =>
103 [AboveGradeUnfinishedArea] =>
104 [AboveGradeUnfinishedAreaSource] =>
105 [AboveGradeUnfinishedAreaUnits] =>
106 [AccessCode] =>
107 [AccessibilityFeatures] =>
108
109
110
111 [mls-fields-map-taxonomy] => Array
112 (
113 [ListingKey] =>
114 [AboveGradeFinishedArea] =>
115 [AboveGradeFinishedAreaSource] =>
116 [AboveGradeFinishedAreaUnits] =>
117 [AboveGradeUnfinishedArea] =>
118 [AboveGradeUnfinishedAreaSource] =>
119 [AboveGradeUnfinishedAreaUnits] =>
120 [AccessCode] =>
121 [AccessibilityFeatures] =>
122 [ActivationDate] =>
123 [AdditionalParcelsDescription] =>
124 [AdditionalParcelsYN] =>
125
126 * Applies field_order sorting to all related MLSImport field arrays in options.
127 *
128 * @param array $options The complete options array containing field_order and field-related arrays.
129 * @return array The updated options array with sorted field arrays.
130 */
131 function mlsimport_sort_all_fields_by_order(array $options): array {
132 if (empty($options['field_order']) || !is_array($options['field_order'])) {
133 return $options;
134 }
135
136 asort($options['field_order']); // Sort field_order by index
137
138 $targets = [
139 'mls-fields',
140 'mls-fields-admin',
141 'mls-fields-label',
142 'mls-fields-map-postmeta',
143 'mls-fields-map-taxonomy'
144
145
146 ];
147
148 foreach ($targets as $target) {
149 if (!empty($options[$target]) && is_array($options[$target])) {
150 $ordered = [];
151
152 foreach ($options['field_order'] as $field_key => $index) {
153 if (isset($options[$target][$field_key])) {
154 $ordered[$field_key] = $options[$target][$field_key];
155 }
156 }
157
158 // Append remaining fields not in field_order
159 foreach ($options[$target] as $key => $value) {
160 if (!isset($ordered[$key])) {
161 $ordered[$key] = $value;
162 }
163 }
164
165 $options[$target] = $ordered;
166 }
167 }
168
169 return $options;
170 }
171
172
173 /**
174 * Computes a new field_order map after moving one field relative to another.
175 *
176 * PURE PHP — no WordPress functions (unit-testable). Extracted from
177 * mlsimport_ajax_save_field_position() so the index math can be tested in
178 * isolation and the handler can delegate array reordering to
179 * mlsimport_sort_all_fields_by_order() instead of a duplicated (buggy) loop.
180 *
181 * @param array $field_order key => int-index map (order defined by the values).
182 * @param int $moving_index 0-based position (in the asort()ed order) to move.
183 * @param int $target_index 0-based position to move relative to.
184 * @param string $position 'before' or 'after' the target.
185 * @return array New key => int-index map, contiguous 0..n-1.
186 */
187 function mlsimport_compute_field_order_after_move( array $field_order, int $moving_index, int $target_index, string $position ): array {
188 asort( $field_order ); // Order by stored index.
189 $ordered_keys = array_keys( $field_order );
190
191 // Out-of-range indexes: return the order unchanged (renumbered contiguously).
192 if ( ! isset( $ordered_keys[ $moving_index ] ) || ! isset( $ordered_keys[ $target_index ] ) ) {
193 return array_flip( array_values( $ordered_keys ) );
194 }
195
196 $moving_key = $ordered_keys[ $moving_index ];
197
198 // Remove the moving key from the list.
199 array_splice( $ordered_keys, $moving_index, 1 );
200
201 // Compensate target index for the just-removed element when moving downward.
202 if ( 'after' === $position && $moving_index < $target_index ) {
203 $target_index--;
204 }
205
206 $insert_index = ( 'before' === $position ) ? $target_index : $target_index + 1;
207
208 array_splice( $ordered_keys, $insert_index, 0, $moving_key );
209
210 // Rebuild a contiguous key => index map.
211 $new_order = array();
212 foreach ( $ordered_keys as $i => $key ) {
213 $new_order[ $key ] = $i;
214 }
215
216 return $new_order;
217 }
218
219 /**
220 * Main function to render the MLS field selection interface.
221 *
222 * @param array $fields The array of MLS fields to display.
223 * @param array $options The saved options for field selections.
224 * @param array $render_params Parameters to control rendering.
225 * @param array $theme_schema Optional. Theme schema for mandatory fields.
226 * @return string The HTML for the field selection interface.
227 */
228 function render_mls_field_selection_interface($fields, $options, $render_params = array(), $theme_schema = array()) {
229 $defaults = array(
230 'page' => 1,
231 'fields_per_page' => 50,
232 'search_term' => '',
233 'import_filter' => 'all', // 'all', 'selected', 'not_selected'
234 'alpha_filter' => '', // Filter by first letter
235 'show_pagination' => true,
236 'show_filters' => true,
237 'show_stats' => true,
238 'enable_drag_drop' => true,
239 'form_action' => 'options.php',
240 'form_method' => 'post',
241 'form_name' => 'mlsimport_fields_select',
242 'nonce_field' => 'mlsimport_admin_fields_select',
243 'plugin_name' => 'mlsimport',
244 );
245
246 // Merge default parameters with provided parameters
247 $params = wp_parse_args( $render_params, $defaults );
248
249
250 // Prepare fields for display (filtering, sorting, pagination)
251 $display_fields = prepare_mls_fields_for_display( $fields, $options, $params );
252
253
254
255
256
257
258
259
260 // Calculate statistics for display
261 $stats = calculate_mls_fields_stats( $options );
262
263
264 // Start output buffering
265 ob_start();
266
267 // Generate form opening
268 echo '<form method="' . esc_attr( $params['form_method'] ) . '" name="' . esc_attr( $params['form_name'] ) . '" action="' . esc_attr( $params['form_action'] ) . '" class="mlsimport-fields-form">';
269
270 // Add nonce field
271 settings_fields( $params['nonce_field'] );
272 do_settings_sections( $params['nonce_field'] );
273
274 // Add container div for the entire interface
275 echo '<div class="mlsimport-field-selector-container">';
276
277 // Show stats if enabled
278 if ( $params['show_stats'] ) {
279 render_mls_field_stats( $stats );
280 }
281
282 // Show filters if enabled
283 if ( $params['show_filters'] ) {
284 render_mls_field_filters( $params );
285 }
286
287 // Start table
288 echo '<table class="mlsimport-fields-table" id="mlsimport-fields-table">';
289
290 // Render table header
291 render_mls_field_table_header();
292
293 // Render table body
294 echo '<tbody id="mlsimport-fields-table-body">';
295
296
297 // Loop through fields and render rows
298 if ( ! empty( $display_fields ) ) {
299 foreach ($display_fields['mls-fields'] as $field_key => $field_value) {
300 render_mls_field_table_row(
301 $field_key,
302 $field_value,
303 $display_fields,
304 $params['plugin_name'],
305 $theme_schema // Pass the theme_schema here
306 );
307 }
308 } else {
309 // No fields found
310 echo '<tr><td colspan="5">' . esc_html__( 'No fields found matching your criteria.', 'mlsimport' ) . '</td></tr>';
311 }
312
313 echo '</tbody>';
314 echo '</table>';
315
316 // Show pagination if enabled
317 if ( $params['show_pagination'] ) {
318 $total_pages = ceil( count( $fields ) / $params['fields_per_page'] );
319 render_mls_field_table_pagination( $params['page'], $total_pages );
320 }
321
322 // Close container div
323 echo '</div>';
324
325
326 // Close form
327 echo '</form>';
328
329
330 // Return the buffered output
331 return ob_get_clean();
332 }
333
334 /**
335 * Prepares MLS fields for display by applying filters, sorting, and pagination.
336 *
337 * @since 1.0.0
338 * @param array $fields The array of MLS fields to process.
339 * @param array $options The saved options for field selections.
340 * @param array $filter_params Parameters for filtering and pagination.
341 * @return array The processed fields ready for display.
342 *//**
343 * Apply custom field order if available
344 */
345 function apply_custom_field_order($fields, $options) {
346 // Check if we have a custom order
347 if (isset($options['field_order']) && is_array($options['field_order']) && !empty($options['field_order'])) {
348 $ordered_fields = array();
349 $remaining_fields = $fields;
350
351 // First add fields that are in the custom order
352 foreach ($options['field_order'] as $field_key) {
353 if (isset($fields[$field_key])) {
354 $ordered_fields[$field_key] = $fields[$field_key];
355 unset($remaining_fields[$field_key]);
356 }
357 }
358
359 // Then add any remaining fields that weren't in the custom order
360 if (!empty($remaining_fields)) {
361 $ordered_fields = array_merge($ordered_fields, $remaining_fields);
362 }
363
364 return $ordered_fields;
365 }
366
367 // No custom order, return original
368 return $fields;
369 }
370
371 /**
372 * Filters fields by name based on search input.
373 *
374 * @since 1.0.0
375 * @param string $search_term The term to search for.
376 * @param array $all_fields The array of all MLS fields.
377 * @return array Filtered fields matching the search term.
378 */
379 function search_mls_fields( $search_term, $all_fields ) {
380 $filtered_fields = array();
381
382 foreach ( $all_fields as $key => $value ) {
383 // Search in field key (name)
384 if ( stripos( $key, $search_term ) !== false ) {
385 $filtered_fields[ $key ] = $value;
386 }
387 }
388
389 return $filtered_fields;
390 }
391
392 /**
393 * Filters fields by their import status (selected or not selected).
394 *
395 * @since 1.0.0
396 * @param string $import_status The import status to filter by ('selected' or 'not_selected').
397 * @param array $all_fields The array of all MLS fields.
398 * @param array $options The saved options for field selections.
399 * @return array Filtered fields matching the import status.
400 */
401 function filter_mls_fields_by_import_status( $import_status, $all_fields, $options ) {
402 $filtered_fields = array();
403
404 foreach ( $all_fields as $key => $value ) {
405 $is_selected = isset( $options['mls-fields'][ $key ] ) &&
406 intval( $options['mls-fields'][ $key ] ) === 1;
407
408 if ( $import_status === 'selected' && $is_selected ) {
409 $filtered_fields[ $key ] = $value;
410 } elseif ( $import_status === 'not_selected' && ! $is_selected ) {
411 $filtered_fields[ $key ] = $value;
412 }
413 }
414
415 return $filtered_fields;
416 }
417
418 /**
419 * Filters fields starting with a particular letter.
420 *
421 * @since 1.0.0
422 * @param string $letter The letter to filter by.
423 * @param array $all_fields The array of all MLS fields.
424 * @return array Filtered fields starting with the specified letter.
425 */
426 function filter_mls_fields_by_alphabet( $letter, $all_fields ) {
427 $filtered_fields = array();
428
429 foreach ( $all_fields as $key => $value ) {
430 if ( strtoupper( substr( $key, 0, 1 ) ) === strtoupper( $letter ) ) {
431 $filtered_fields[ $key ] = $value;
432 }
433 }
434
435 return $filtered_fields;
436 }
437
438 /**
439 * Calculates statistics for display (total, selected, missing labels).
440 *
441 * @since 1.0.0
442 * @param array $all_fields The array of all MLS fields.
443 * @param array $options The saved options for field selections.
444 * @return array Statistics about the fields.
445 */
446 function calculate_mls_fields_stats( $options ) {
447 $stats = array(
448 'total_fields' => count( $options['mls-fields'] ),
449 'selected_fields' => 0,
450 'missing_labels' => 0,
451 );
452
453 foreach ( $options['mls-fields'] as $key => $value ) {
454 // Count selected fields
455 if ( isset( $options['mls-fields'][ $key ] ) &&
456 intval( $options['mls-fields'][ $key ] ) === 1 ) {
457 $stats['selected_fields']++;
458
459
460 }
461 }
462
463 return $stats;
464 }
465
466 /**
467 * Renders the statistics bar showing counts of fields.
468 *
469 * @since 1.0.0
470 * @param array $stats The statistics to display.
471 * @return void
472 */
473
474 function render_mls_field_stats( $stats ) {
475 echo '<div class="mlsimport-field-stats">';
476 echo '<ul>';
477 echo '<li>' . esc_html( sprintf( __( '%d fields total', 'mlsimport' ), $stats['total_fields'] ) ) . '</li>';
478 echo '<li>' . esc_html( sprintf( __( '%d marked for import', 'mlsimport' ), $stats['selected_fields'] ) ) . '</li>';
479
480 echo '</ul>';
481
482 echo '<div class="mlsimport-button-action-wrapper">';
483 echo '<button id="mlsimport-select-all-import" class="button mlsimport_button">' . esc_html__( 'Import - Select All', 'mlsimport' ) . '</button>';
484 echo '<button id="mlsimport-select-none-import" class="button mlsimport_button">' . esc_html__( 'Import - Select None', 'mlsimport' ) . '</button>';
485 echo '<button id="mlsimport-select-all-admin" class="button mlsimport_button secondary">' . esc_html__( 'Hidden from Public - Select All', 'mlsimport' ) . '</button>';
486 echo '<button id="mlsimport-select-none-admin" class="button mlsimport_button secondary">' . esc_html__( 'Hidden from Public - Select None', 'mlsimport' ) . '</button>';
487 echo '</div>';
488 echo '</div>';
489 }
490
491
492
493
494 /**
495 * Renders the filters bar for searching and filtering fields.
496 *
497 * @since 1.0.0
498 * @param array $params Parameters for filtering.
499 * @return void
500 */
501 function render_mls_field_filters( $params ) {
502 // Field sorting dropdown
503 echo '<div class="mlsimport-field-filters">';
504
505 // Field sorting dropdown
506 echo '<select id="mlsimport-field-sort" class="mlsimport-field-filter mlsimport-2025-select" style="width:200px">';
507
508 echo '<option value="field_asc">' . esc_html__( 'Field Name (A-Z)', 'mlsimport' ) . '</option>';
509 echo '<option value="field_desc">' . esc_html__( 'Field Name (Z-A)', 'mlsimport' ) . '</option>';
510
511 echo '<option value="label_asc">' . esc_html__( 'Label (A-Z)', 'mlsimport' ) . '</option>';
512 echo '<option value="label_desc">' . esc_html__( 'Label (Z-A)', 'mlsimport' ) . '</option>';
513
514 echo '<option value="postmeta_asc">' . esc_html__( 'Property Detail Field (A-Z)', 'mlsimport' ) . '</option>';
515 echo '<option value="postmeta_desc">' . esc_html__( 'Property Detail Field (Z-A)', 'mlsimport' ) . '</option>';
516
517 echo '<option value="category_asc">' . esc_html__( 'Category (A-Z)', 'mlsimport' ) . '</option>';
518 echo '<option value="category_desc">' . esc_html__( 'Category (Z-A)', 'mlsimport' ) . '</option>';
519
520 echo '<option value="import_selected">' . esc_html__( 'Import Selected First', 'mlsimport' ) . '</option>';
521 echo '<option value="import_unselected">' . esc_html__( 'Import Unselected First', 'mlsimport' ) . '</option>';
522
523 echo '<option value="hidden_selected">' . esc_html__( 'Hidden from Public Selected First', 'mlsimport' ) . '</option>';
524 echo '<option value="hidden_unselected">' . esc_html__( 'Hidden from Public Unselected First', 'mlsimport' ) . '</option>';
525
526 echo '</select>';
527
528 // Import status filter
529 echo '<select id="mlsimport-import-filter" class="mlsimport-field-filter mlsimport-2025-select" style="width:200px">';
530 echo '<option value="all"' . selected( $params['import_filter'], 'all', false ) . '>' . esc_html__( 'All Fields', 'mlsimport' ) . '</option>';
531 echo '<option value="selected"' . selected( $params['import_filter'], 'selected', false ) . '>' . esc_html__( 'Selected Only', 'mlsimport' ) . '</option>';
532 echo '<option value="not_selected"' . selected( $params['import_filter'], 'not_selected', false ) . '>' . esc_html__( 'Not Selected', 'mlsimport' ) . '</option>';
533 echo '</select>';
534
535 // Search box
536 echo '<input type="text" id="mlsimport-field-search" class="mlsimport-field-filter mlsimport-input mlsimport-2025-input" style="width:300px" placeholder="' . esc_attr__( 'Search...', 'mlsimport' ) . '" value="' . esc_attr( $params['search_term'] ) . '">';
537
538 echo '</div>';
539 }
540
541 /**
542 * Renders the table header for the field selection table.
543 *
544 * @since 1.0.0
545 * @return void
546 */
547 function render_mls_field_table_header() {
548 echo '<thead>';
549 echo '<tr>';
550 echo '<th>' . esc_html__( 'Field', 'mlsimport' ) . '</th>';
551 echo '<th style="width:75px;">' . esc_html__( 'Import it?', 'mlsimport' ) . '</th>';
552 echo '<th>' . esc_html__( 'Front End Label', 'mlsimport' ) . '</th>';
553 echo '<th style="width:75px;">' . esc_html__( 'Hidden from Public?', 'mlsimport' ) . '</th>';
554 echo '<th>' . esc_html__( 'Property Detail Field (post meta)', 'mlsimport' ) . '</th>';
555 echo '<th>' . esc_html__( 'Category', 'mlsimport' ) . '</th>';
556 echo '<th style="width:140px;">' . esc_html__( 'Actions', 'mlsimport' ) . '</th>';
557 echo '</tr>';
558 echo '</thead>';
559 }
560
561
562 /**
563 * Renders a single table row for a field.
564 *
565 * @since 1.0.0
566 * @param string $field_key The field key/name.
567 * @param string $field_value The field explanation/description.
568 * @param array $options The saved options for field selections.
569 * @param string $plugin_name The plugin name for form field IDs.
570 * @param array $theme_schema Optional. Theme schema for mandatory fields.
571 * @return void
572 */
573 function render_mls_field_table_row($field_key, $field_value, $options, $plugin_name, $theme_schema = array()) {
574 // Determine if this is a mandatory field
575 $is_mandatory = 0;
576
577 // Determine if field is checked for import
578 $is_checked = $is_mandatory ||
579 (isset($options['mls-fields'][$field_key]) &&
580 intval($options['mls-fields'][$field_key]) === 1);
581
582 // Determine if field is checked for admin-only visibility
583 $is_admin_only = isset($options['mls-fields-admin'][$field_key]) &&
584 intval($options['mls-fields-admin'][$field_key]) === 1;
585
586 // Get field label value
587 $label_value = isset($options['mls-fields-label'][$field_key]) ?
588 $options['mls-fields-label'][$field_key] : '';
589
590 // Get post meta value
591 $post_meta_value = isset($options['mls-fields-map-postmeta'][$field_key]) ?
592 $options['mls-fields-map-postmeta'][$field_key] : '';
593
594 // Get taxonomy value
595 $taxonomy_value = isset($options['mls-fields-map-taxonomy'][$field_key]) ?
596 $options['mls-fields-map-taxonomy'][$field_key] : null;
597
598
599 // Get taxonomy value
600 $field_order= isset($options['field_order'][$field_key]) ?
601 $options['field_order'][$field_key] : '';
602
603
604
605 // Define CSS classes for the row
606 $row_classes = array('mlsimport-field-row');
607 if ($is_mandatory) {
608 $row_classes[] = 'mlsimport-mandatory-row';
609 }
610
611 // Start row
612 echo '<tr style="display:none; opacity:0;" class="' . esc_attr(implode(' ', $row_classes)) . '"
613 data-field-order="'.intval($field_order).'" data-field-key="' . esc_attr($field_key) . '" data-is-mandatory="' . ($is_mandatory ? 'true' : 'false') . '">';
614 // Field name column with full explanation
615
616
617 echo '<td class="mlsimport-field-name mlsimport-field-name_title_desc">';
618
619 // Position indicator for debugging
620
621 echo '<span class="field-name">';
622 echo '<span class="field-position">' . ( intval( $field_order ) + 1 ) . '. </span>';
623 echo esc_html( $field_key ) . '</span>';
624
625 if ( !empty($field_value) && intval($field_value)!==1 ) {
626 echo '<div class="field-explanation">' . esc_html($field_value) . '</div>';
627 }
628 echo '</td>';
629
630 // Import checkbox column
631 echo '<td class="mlsimport-field-import">';
632 echo '<input type="hidden" name="' . esc_attr($plugin_name) . '_admin_fields_select[mls-fields][' . esc_attr($field_key) . ']" value="' . ($is_mandatory ? '1' : '0') . '">';
633
634 if ($is_mandatory) {
635 // For mandatory fields, show a disabled checked checkbox
636 echo '<input type="checkbox" class="mlsimport-import-checkbox" checked="checked" disabled="disabled" title="' . esc_attr__('Mandatory field - cannot be deselected', 'mlsimport') . '">';
637 } else {
638 // For optional fields, show a regular checkbox
639 echo '<input type="checkbox" class="mlsimport-import-checkbox" name="' . esc_attr($plugin_name) . '_admin_fields_select[mls-fields][' . esc_attr($field_key) . ']" value="1" ' . checked($is_checked, true, false) . '>';
640 }
641 echo '</td>';
642
643 // Label column
644 echo '<td class="mlsimport-field-label">';
645 echo '<input type="text" class="mlsimport-label-input mlsimport-input mlsimport-2025-input" name="' . esc_attr($plugin_name) . '_admin_fields_select[mls-fields-label][' . esc_attr($field_key) . ']" value="' . esc_attr($label_value) . '" placeholder="' . esc_attr__('enter label...', 'mlsimport') . '">';
646 echo '</td>';
647
648 // Admin-only visibility checkbox column
649 echo '<td class="mlsimport-field-admin">';
650 echo '<input type="hidden" name="' . esc_attr($plugin_name) . '_admin_fields_select[mls-fields-admin][' . esc_attr($field_key) . ']" value="0">';
651 echo '<input type="checkbox" class="mlsimport-admin-checkbox" name="' . esc_attr($plugin_name) . '_admin_fields_select[mls-fields-admin][' . esc_attr($field_key) . ']" value="1" ' . checked($is_admin_only, true, false) . '>';
652 echo '</td>';
653
654 // Post meta mapping column
655 echo '<td class="mlsimport-field-postmeta">';
656 echo '<input type="text" class="mlsimport-postmeta-input mlsimport-input mlsimport-2025-input " name="' . esc_attr($plugin_name) . '_admin_fields_select[mls-fields-map-postmeta][' . esc_attr($field_key) . ']" value="' . esc_attr($post_meta_value) . '">';
657 echo '</td>';
658
659 // Category/taxonomy mapping column
660 echo '<td class="mlsimport-field-taxonomy">';
661 echo get_taxonomy_dropdown_html($field_key, $taxonomy_value, $plugin_name, $theme_schema);
662 echo '</td>';
663
664 // Actions column with move buttons
665 echo '<td class="mlsimport-field-actions">';
666 echo '<div class="mlsimport-row-actions">';
667 echo '<button type="button" class="mlsimport-move-btn mlsimport-move-up" title="Move field up">↑</button>';
668 echo '<button type="button" class="mlsimport-move-btn mlsimport-move-down" title="Move field down">↓</button>';
669 echo '<button type="button" class="mlsimport-move-btn mlsimport-move-top" title="Move field to top">⇈</button>';
670 echo '<button type="button" class="mlsimport-move-btn mlsimport-move-bottom" title="Move field to bottom">⇊</button>';
671 echo '</div>';
672 echo '</td>';
673
674 // End row
675 echo '</tr>';
676 }
677
678 /**
679 * Gets HTML for taxonomy dropdown with proper availability checking.
680 *
681 * @since 1.0.0
682 * @param string $field_key The field key/name.
683 * @param string $selected The currently selected taxonomy.
684 * @param string $plugin_name The plugin name for form field IDs.
685 * @param array $theme_schema Optional. Theme schema for mandatory fields.
686 * @return string HTML for the taxonomy dropdown.
687 */
688 function get_taxonomy_dropdown_html($field_key, $selected, $plugin_name, $theme_schema = array()) {
689 // Don't rely on global - explicitly get the taxonomies
690 global $mlsimport;
691 $post_type = '';
692
693 if (method_exists($mlsimport->admin->env_data, 'get_property_post_type')) {
694 $post_type = $mlsimport->admin->env_data->get_property_post_type();
695 }
696
697 // Get taxonomies directly
698 $available_taxonomies = mlsimport_get_custom_post_type_taxonomies($post_type);
699
700 // For mandatory fields, set default taxonomy from theme schema
701 if (!empty($theme_schema) && isset($theme_schema[$field_key]) &&
702 isset($theme_schema[$field_key]['type']) && $theme_schema[$field_key]['type'] === 'taxonomy' &&
703 isset($theme_schema[$field_key]['name'])) {
704 // Use schema's taxonomy value only if never set (null means no saved selection)
705 if ($selected === null) {
706 $selected = $theme_schema[$field_key]['name'];
707 }
708 }
709
710 // Build dropdown HTML
711 $html = '<select class="mlsimport-taxonomy-select mlsimport-2025-select " name="mlsimport_admin_fields_select[mls-fields-map-taxonomy][' . esc_attr($field_key) . ']">';
712 $html .= '<option value="">' . esc_html__('None', 'mlsimport') . '</option>';
713
714 if (is_array($available_taxonomies) && !empty($available_taxonomies)) {
715 foreach ($available_taxonomies as $key => $label) {
716 $html .= '<option value="' . esc_attr($key) . '" ' . selected($selected, $key, false) . '>' . esc_html($label) . '</option>';
717 }
718 }
719
720 $html .= '</select>';
721 return $html;
722 }
723 /**
724 * Gets available taxonomies for the dropdown.
725 *
726 * @since 1.0.0
727 * @return array Array of taxonomies with key => label pairs.
728 */
729 function get_custom_taxonomies_for_dropdown() {
730 // This function should return an array of taxonomies
731 // Replace with actual implementation based on your needs
732
733 // For example:
734 $post_type = ''; // Get your post type here
735
736 if ( function_exists( 'mlsimport_get_custom_post_type_taxonomies' ) ) {
737 return mlsimport_get_custom_post_type_taxonomies( $post_type );
738 }
739
740 // Fallback to empty array if function doesn't exist
741 return array();
742 }
743
744 /**
745 * Renders pagination controls for the table.
746 *
747 * @since 1.0.0
748 * @param int $current_page The current page number.
749 * @param int $total_pages The total number of pages.
750 * @return void
751 */
752 function render_mls_field_table_pagination( $current_page, $total_pages ) {
753 if ( $total_pages <= 1 ) {
754 return;
755 }
756
757 echo '<div class="mlsimport-pagination">';
758
759 // Previous page
760 if ( $current_page > 1 ) {
761 echo '<a href="#" class="mlsimport-page-link" data-page="' . ( $current_page - 1 ) . '">&laquo; ' . esc_html__( 'Previous', 'mlsimport' ) . '</a>';
762 } else {
763 echo '<span class="mlsimport-page-disabled">&laquo; ' . esc_html__( 'Previous', 'mlsimport' ) . '</span>';
764 }
765
766 // Page numbers
767 $start_page = max( 1, $current_page - 2 );
768 $end_page = min( $total_pages, $current_page + 2 );
769
770 if ( $start_page > 1 ) {
771 echo '<a href="#" class="mlsimport-page-link" data-page="1">1</a>';
772 if ( $start_page > 2 ) {
773 echo '<span class="mlsimport-page-ellipsis">...</span>';
774 }
775 }
776
777 for ( $i = $start_page; $i <= $end_page; $i++ ) {
778 if ( $i === $current_page ) {
779 echo '<span class="mlsimport-page-current">' . $i . '</span>';
780 } else {
781 echo '<a href="#" class="mlsimport-page-link" data-page="' . $i . '">' . $i . '</a>';
782 }
783 }
784
785 if ( $end_page < $total_pages ) {
786 if ( $end_page < $total_pages - 1 ) {
787 echo '<span class="mlsimport-page-ellipsis">...</span>';
788 }
789 echo '<a href="#" class="mlsimport-page-link" data-page="' . $total_pages . '">' . $total_pages . '</a>';
790 }
791
792 // Next page
793 if ( $current_page < $total_pages ) {
794 echo '<a href="#" class="mlsimport-page-link" data-page="' . ( $current_page + 1 ) . '">' . esc_html__( 'Next', 'mlsimport' ) . ' &raquo;</a>';
795 } else {
796 echo '<span class="mlsimport-page-disabled">' . esc_html__( 'Next', 'mlsimport' ) . ' &raquo;</span>';
797 }
798
799 echo '</div>';
800 }
801
802
803 /**
804 * Processes bulk selection/deselection of fields.
805 *
806 * @since 1.0.0
807 * @param string $action The bulk action ('select_all', 'select_none', etc.).
808 * @param array $fields The fields to apply the action to.
809 * @return void
810 */
811 function handle_bulk_actions( $action, $fields ) {
812 // This function will be implemented in JavaScript for client-side actions
813 // Server-side processing would happen in the form submission handler
814 }
815
816 /**
817 * Saves the custom order from drag and drop operations.
818 *
819 * @since 1.0.0
820 * @param array $ordered_fields The fields in their new order.
821 * @return bool Whether the save was successful.
822 */
823 function save_field_display_order( $ordered_fields ) {
824 // Save the custom order to user meta or options
825 // This would be called via AJAX when the user reorders fields
826
827 // Example implementation:
828 update_option( 'mlsimport_field_display_order', $ordered_fields );
829
830 return true;
831 }
832
833 /**
834 * Updates individual field options.
835 *
836 * @since 1.0.0
837 * @param string $field_key The field key to update.
838 * @param string $option_type The option type ('import', 'admin', 'label', etc.).
839 * @param mixed $value The new value for the option.
840 * @return bool Whether the update was successful.
841 */
842 function update_field_options( $field_key, $option_type, $value ) {
843 // This would be used for AJAX updates to individual field options
844 // Implementation would depend on how options are stored
845
846 return true;
847 }
848
849
850 /**
851 * Generate HTML for taxonomy dropdown with automatic selection for mandatory fields
852 *
853 * @param string $field_key The field key/name
854 * @param array $theme_schema The theme schema array
855 * @param array $options The saved options
856 * @param string $plugin_name The plugin name
857 * @param array $available_taxonomies Available taxonomies array
858 * @return string HTML for the select dropdown
859 */
860 function mlsimport_get_taxonomy_dropdown($field_key, $theme_schema, $options, $plugin_name, $available_taxonomies) {
861 // Get current selection from saved options
862 $selected_value = isset($options['mls-fields-map-taxonomy'][$field_key]) ?
863 $options['mls-fields-map-taxonomy'][$field_key] : '';
864
865 // For mandatory fields, set default from theme schema
866 if (isset($theme_schema[$field_key]) && isset($theme_schema[$field_key]['type']) &&
867 $theme_schema[$field_key]['type'] == 'taxonomy' && isset($theme_schema[$field_key]['name'])) {
868 // If this is a taxonomy field in schema, use the schema's taxonomy name by default
869 $selected_value = $theme_schema[$field_key]['name'];
870 }
871
872 // Start building the dropdown
873 $html = '<select class="mlsfield_map_post_tax_select" name="' . esc_attr($plugin_name) . '_admin_fields_select[mls-fields-map-taxonomy][' . esc_attr($field_key) . ']">';
874 $html .= '<option value="">' . esc_html__('None', 'mlsimport') . '</option>';
875
876 // Add options for each available taxonomy
877 foreach ($available_taxonomies as $tax_key => $tax_label) {
878 $html .= '<option value="' . esc_attr($tax_key) . '"';
879 if ($selected_value == $tax_key) {
880 $html .= ' selected';
881 }
882 $html .= '>' . esc_html($tax_label) . '</option>';
883 }
884
885 $html .= '</select>';
886
887 return $html;
888 }