PluginProbe
Property Hive / 2.3.1
Property Hive v2.3.1
2.3.1 2.3.0 2.2.6 2.2.5 2.2.4 2.2.3 2.2.2 1.4.46 1.4.47 1.4.48 1.4.49 1.4.5 1.4.50 1.4.51 1.4.52 1.4.53 1.4.54 1.4.55 1.4.56 1.4.57 1.4.58 1.4.59 1.4.6 1.4.60 1.4.61 All 261 releases
← All changes | includes/admin/settings/class-ph-settings-custom-fields.php +677 -472 2.2.62.3.1 View file →
@@ -1,5 +1,8 @@
1 1 <?php
2 +// phpcs:set WordPress.Security.ValidatedSanitizedInput customSanitizingFunctions[] ph_clean
3 +// ph_clean() recursively sanitizes text; presence, shape and unslashing checks remain separate.
4 +
2 5 /**
3 6 * PropertyHive Custom Fields Settings
4 7 *
5 8 * @author PropertyHive
@@ -16,8 +19,9 @@
16 19
17 20 /**
18 21 * PH_Settings_General
19 22 */
23 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Legacy public global class PH_Settings_Custom_Fields; preserving the existing PH_* class name is required for plugin and extension compatibility.
20 24 class PH_Settings_Custom_Fields extends PH_Settings_Page {
21 25
22 26 const LINKED_POSTS_COLUMN_HEADING = 'Assigned Properties';
23 27
@@ -46,59 +50,145 @@
46 50 }
47 51
48 52 public function check_for_delete_additional_field()
49 53 {
50 - if ( isset($_GET['action']) && $_GET['action'] == 'deleteadditionalfield' && isset($_GET['id']) && $_GET['id'] != '' )
51 - {
52 - $current_settings = get_option( 'propertyhive_template_assistant', array() );
54 + if (
55 + ! isset( $_GET['action'] ) ||
56 + 'deleteadditionalfield' !== $_GET['action']
57 + ) {
58 + return;
59 + }
53 60
54 - $current_id = ( !isset( $_GET['id'] ) ) ? '' : sanitize_title( $_GET['id'] );
61 + if ( ! current_user_can( 'manage_options' ) ) {
62 + wp_die(
63 + esc_html__( 'You do not have permission to manage Property Hive fields.', 'propertyhive' ),
64 + '',
65 + array( 'response' => 403 )
66 + );
67 + }
55 68
56 - $existing_custom_fields = ( (isset($current_settings['custom_fields'])) ? $current_settings['custom_fields'] : array() );
69 + check_admin_referer( 'propertyhive_delete_additional_field' );
57 70
58 - if ( !isset($existing_custom_fields[$current_id]) )
59 - {
60 - die("Trying to delete a non-existant additional field. Please go back and try again");
61 - }
71 + $current_id = isset( $_GET['id'] ) && is_string( $_GET['id'] )
72 + ? sanitize_title( wp_unslash( $_GET['id'] ) )
73 + : '';
62 74
63 - if ( isset($existing_custom_fields[$current_id]) )
64 - {
65 - unset($existing_custom_fields[$current_id]);
66 - }
75 + if ( '' === $current_id ) {
76 + wp_die(
77 + esc_html__( 'Invalid additional field.', 'propertyhive' ),
78 + '',
79 + array( 'response' => 400 )
80 + );
81 + }
67 82
68 - $current_settings['custom_fields'] = $existing_custom_fields;
83 + $current_settings = get_option(
84 + 'propertyhive_template_assistant',
85 + array()
86 + );
69 87
70 - update_option( 'propertyhive_template_assistant', $current_settings );
88 + $existing_custom_fields =
89 + isset( $current_settings['custom_fields'] )
90 + && is_array( $current_settings['custom_fields'] )
91 + ? $current_settings['custom_fields']
92 + : array();
93 +
94 + if ( ! isset( $existing_custom_fields[ $current_id ] ) ) {
95 + wp_die(
96 + esc_html__( 'The additional field does not exist.', 'propertyhive' ),
97 + '',
98 + array( 'response' => 404 )
99 + );
71 100 }
101 +
102 + unset( $existing_custom_fields[ $current_id ] );
103 +
104 + $current_settings['custom_fields'] = $existing_custom_fields;
105 +
106 + update_option(
107 + 'propertyhive_template_assistant',
108 + $current_settings
109 + );
110 +
111 + wp_safe_redirect(
112 + admin_url(
113 + 'admin.php?page=ph-settings&tab=customfields&section=additional&ph_message=' . __( 'Additional field deleted successfully', 'propertyhive' )
114 + )
115 + );
116 + exit;
72 117 }
73 118
74 119 public function check_for_reorder_additional_fields()
75 120 {
76 - if ( isset($_GET['neworder']) && $_GET['neworder'] != '' )
77 - {
78 - $current_settings = get_option( 'propertyhive_template_assistant', array() );
121 + if ( ! isset( $_GET['neworder'] ) ) {
122 + return;
123 + }
79 124
80 - $current_id = ( !isset( $_GET['id'] ) ) ? '' : sanitize_title( $_GET['id'] );
125 + if ( ! current_user_can( 'manage_options' ) ) {
126 + wp_die(
127 + esc_html__( 'You do not have permission to manage Property Hive fields.', 'propertyhive' ),
128 + '',
129 + array( 'response' => 403 )
130 + );
131 + }
81 132
82 - $existing_custom_fields = ( (isset($current_settings['custom_fields'])) ? $current_settings['custom_fields'] : array() );
133 + check_admin_referer( 'propertyhive_reorder_additional_fields' );
83 134
84 - $new_order = explode(",", sanitize_text_field($_GET['neworder']));
85 - $new_order = ph_clean($new_order);
135 + if ( ! is_string( $_GET['neworder'] ) ) {
136 + wp_die( esc_html__( 'Invalid additional field order.', 'propertyhive' ), '', array( 'response' => 400 ) );
137 + }
86 138
87 - $new_custom_fields = array();
139 + $new_order = array_map(
140 + 'sanitize_title',
141 + explode(
142 + ',',
143 + sanitize_text_field(
144 + wp_unslash( $_GET['neworder'] )
145 + )
146 + )
147 + );
88 148
89 - foreach ( $new_order as $id )
90 - {
91 - $new_custom_fields[] = $existing_custom_fields[$id];
92 - }
149 + $current_settings = get_option(
150 + 'propertyhive_template_assistant',
151 + array()
152 + );
93 153
94 - $current_settings['custom_fields'] = $new_custom_fields;
154 + $existing_custom_fields =
155 + isset( $current_settings['custom_fields'] )
156 + && is_array( $current_settings['custom_fields'] )
157 + ? $current_settings['custom_fields']
158 + : array();
95 159
96 - update_option( 'propertyhive_template_assistant', $current_settings );
160 + if (
161 + count( $new_order ) !== count( $existing_custom_fields ) ||
162 + array_diff( $new_order, array_keys( $existing_custom_fields ) ) ||
163 + array_diff( array_keys( $existing_custom_fields ), $new_order )
164 + ) {
165 + wp_die(
166 + esc_html__( 'Invalid additional field order.', 'propertyhive' ),
167 + '',
168 + array( 'response' => 400 )
169 + );
170 + }
97 171
98 - header("Location: " . admin_url('admin.php?page=ph-settings&tab=customfields&section=additional'));
99 - exit();
172 + $new_custom_fields = array();
173 +
174 + foreach ( $new_order as $id ) {
175 + $new_custom_fields[ $id ] = $existing_custom_fields[ $id ];
100 176 }
177 +
178 + $current_settings['custom_fields'] = $new_custom_fields;
179 +
180 + update_option(
181 + 'propertyhive_template_assistant',
182 + $current_settings
183 + );
184 +
185 + wp_safe_redirect(
186 + admin_url(
187 + 'admin.php?page=ph-settings&tab=customfields&section=additional&ph_message=' . __( 'Additional fields reordered successfully', 'propertyhive' )
188 + )
189 + );
190 + exit;
101 191 }
102 192
103 193 /**
104 194 * Get sections
@@ -246,36 +336,13 @@
246 336 public function get_settings() {
247 337
248 338 global $hide_save_button;
249 339
340 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global.
250 341 $hide_save_button = true;
251 342
252 343 $i = 0;
253 - $html = '<style>
254 -
255 - .ph-custom-fields-grid { display:grid; grid-template-columns:repeat(4, 1fr); gap:22px; }
256 - .ph-custom-fields-grid > div { display:flex; gap:20px; background:#FFF; padding:25px; border:1px solid #AAA }
257 - .ph-custom-fields-grid > div .ph-grid-image { flex:0 0 50px; }
258 - .ph-custom-fields-grid > div .ph-grid-image-bg { background:#fbfcd4; border:1px solid #ffcd00; padding:10px; border-radius:7px; }
259 - .ph-custom-fields-grid > div .ph-grid-image img { max-width:40px; height:40px; display:block }
260 - .ph-custom-fields-grid > div .feature-card-content { flex:1; min-width:0; }
261 - .ph-custom-fields-grid > div h3 { margin-top:0; margin-bottom:0.6em }
262 -
263 - @media (max-width:1750px) {
264 -
265 - .ph-custom-fields-grid { grid-template-columns:repeat(3, 1fr); }
266 -
267 - }
268 -
269 - @media (max-width:1370px) {
270 -
271 - .ph-custom-fields-grid { grid-template-columns:repeat(2, 1fr); }
272 -
273 - }
274 -
275 - </style>
276 -
277 - <div class="ph-custom-fields-grid">';
344 + $html = '<div class="ph-custom-fields-grid">';
278 345 foreach ( $this->custom_field_sections as $key => $value )
279 346 {
280 347 $image = 'default.png';
281 348 if ( file_exists(PH()->plugin_path() . '/assets/images/admin/settings/custom-fields/' . $key . '.png') )
@@ -323,8 +390,9 @@
323 390 case "additional":
324 391 {
325 392 global $hide_save_button;
326 393
394 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global.
327 395 $hide_save_button = true;
328 396
329 397 // The main custom field screen listing them in a table
330 398 $settings = $this->get_custom_fields_additional_fields_setting();
@@ -344,11 +412,13 @@
344 412 break;
345 413 }
346 414 default:
347 415 {
348 - if (isset($_REQUEST['id'])) // we're either adding or editing
416 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This branch only renders a settings form from an optional id; it does not mutate state. The corresponding save path verifies the settings nonce and capability.
417 + if ( isset( $_REQUEST['id'] ) ) // we're either adding or editing
349 418 {
350 - $current_id = empty( $_REQUEST['id'] ) ? '' : sanitize_text_field($_REQUEST['id']);
419 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This id is used only to render a settings form; the corresponding save path verifies the settings nonce and capability.
420 + $current_id = ( isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['id'] ) ) : '';
351 421
352 422 switch ($current_section)
353 423 {
354 424 case "availability": { $settings = $this->get_custom_fields_availability_setting(); break; }
@@ -405,8 +475,9 @@
405 475 else
406 476 {
407 477 global $hide_save_button;
408 478
479 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global.
409 480 $hide_save_button = true;
410 481
411 482 // The main custom field screen listing them in a table
412 483 $settings = $this->get_custom_fields_setting($current_section);
@@ -434,9 +505,10 @@
434 505 {
435 506 $current_settings['custom_fields'] = array();
436 507 }
437 508
438 - $current_id = ( !isset( $_REQUEST['id'] ) ) ? '' : sanitize_title( $_REQUEST['id'] );
509 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This settings getter only loads stored configuration for rendering; it does not mutate state.
510 + $current_id = ( isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] ) ) ? sanitize_title( wp_unslash( $_REQUEST['id'] ) ) : '';
439 511
440 512 $custom_field_details = array();
441 513
442 514 if ($current_id != '')
@@ -454,9 +526,9 @@
454 526 }
455 527
456 528 $settings = array(
457 529
458 - array( 'title' => __( ( $current_section == 'addadditionalfield' ? 'Add Additional Field' : 'Edit Additional Field' ), 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'customfield' ),
530 + array( 'title' => ( $current_section == 'addadditionalfield' ? __( 'Add Additional Field', 'propertyhive' ) : __( 'Edit Additional Field', 'propertyhive' ) ), 'type' => 'title', 'desc' => '', 'id' => 'customfield' ),
459 531
460 532 );
461 533
462 534 $settings[] = array(
@@ -699,9 +771,10 @@
699 771 {
700 772 $current_settings['custom_fields'] = array();
701 773 }
702 774
703 - $current_id = ( !isset( $_REQUEST['id'] ) ) ? '' : sanitize_title( $_REQUEST['id'] );
775 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This settings getter only loads stored configuration for rendering; it does not mutate state.
776 + $current_id = ( isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] ) ) ? sanitize_title( wp_unslash( $_REQUEST['id'] ) ) : '';
704 777
705 778 $custom_field_details = array();
706 779
707 780 if ($current_id != '')
@@ -722,15 +795,15 @@
722 795 <tr valign="top" id="row_dropdown_options">
723 796 <th scope="row" class="titledesc">
724 797 <label for="field_type">Dropdown Options</label>
725 798 </th>
726 - <td class="forminp forminp-dropdown-options"><div id="sortable_options_' . $current_id . '">';
799 + <td class="forminp forminp-dropdown-options"><div id="sortable_options_' . esc_attr( $current_id ) . '">';
727 800 if ( isset($custom_field_details['dropdown_options']) && !empty($custom_field_details['dropdown_options']) )
728 801 {
729 802 foreach ( $custom_field_details['dropdown_options'] as $dropdown_option )
730 803 {
731 804 echo '
732 - <div><i class="fa fa-reorder" style="cursor:pointer; opacity:0.3"></i> <input type="text" name="dropdown_options[]" value="' . $dropdown_option . '"> <a href="" class="delete-dropdown-option">Delete Option</a></div>
805 + <div><i class="fa fa-reorder" style="cursor:pointer; opacity:0.3"></i> <input type="text" name="dropdown_options[]" value="' . esc_attr( $dropdown_option ) . '"> <a href="" class="delete-dropdown-option">Delete Option</a></div>
733 806 ';
734 807 }
735 808 }
736 809 else
@@ -774,9 +847,9 @@
774 847 jQuery(this).parent().remove();
775 848 }
776 849 });
777 850
778 - jQuery( \'#sortable_options_' . $current_id . '\' )
851 + jQuery( \'#sortable_options_' . esc_js( $current_id ) . '\' )
779 852 .sortable({
780 853 axis: "y",
781 854 handle: "i",
782 855 stop: function( event, ui )
@@ -899,11 +972,19 @@
899 972 {
900 973 echo '-';
901 974 }
902 975 echo '</td>';
976 +
977 + $delete_url = wp_nonce_url(
978 + admin_url(
979 + 'admin.php?page=ph-settings&tab=customfields&section=additional&action=deleteadditionalfield&id=' . rawurlencode( $id )
980 + ),
981 + 'propertyhive_delete_additional_field'
982 + );
983 +
903 984 echo '<td class="settings">
904 985 <a class="button" href="' . esc_url(admin_url( 'admin.php?page=ph-settings&tab=customfields&section=editadditionalfield&id=' . $id )) . '">' . esc_html(__( 'Edit Field', 'propertyhive' )) . '</a>
905 - <a class="button" href="' . esc_url(admin_url( 'admin.php?page=ph-settings&tab=customfields&section=additional&action=deleteadditionalfield&id=' . $id )) . '" onclick="var confirmBox = confirm(\'Are you sure you wish to delete this custom field?\'); return confirmBox;">' . esc_html(__( 'Delete', 'propertyhive' )) . '</a>
986 + <a class="button" href="' . esc_url( $delete_url ) . '" onclick="var confirmBox = confirm(\'Are you sure you wish to delete this custom field?\'); return confirmBox;">' . esc_html(__( 'Delete', 'propertyhive' )) . '</a>
906 987 </td>';
907 988 echo '</tr>';
908 989 }
909 990 }
@@ -938,9 +1019,9 @@
938 1019 new_order = new_order + jQuery(this).attr('id').replace('custom_field_', '');
939 1020 });
940 1021
941 1022 // reload page
942 - window.location.href = '<?php echo admin_url('admin.php?page=ph-settings&tab=customfields&section=additional'); ?>&neworder=' + new_order;
1023 + window.location.href = <?php echo wp_json_encode( add_query_arg( '_wpnonce', wp_create_nonce( 'propertyhive_reorder_additional_fields' ), admin_url( 'admin.php?page=ph-settings&tab=customfields&section=additional' ) ), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); ?> + '&neworder=' + encodeURIComponent( new_order );
943 1024 }
944 1025 });
945 1026
946 1027 });
@@ -1012,9 +1093,9 @@
1012 1093 <th class="id" style="width:45px;"><?php echo esc_html(__( 'ID', 'propertyhive' )); ?></th>
1013 1094 <?php do_action( 'propertyhive_custom_field_availability_table_before_header_column' ); ?>
1014 1095 <th class="type"><?php echo esc_html(__( 'Availability', 'propertyhive' )); ?></th>
1015 1096 <th class="department"><?php echo esc_html(__( 'Applies To', 'propertyhive' )); ?></th>
1016 - <th class="assigned_count"><?php echo esc_html(__( $this::LINKED_POSTS_COLUMN_HEADING, 'propertyhive' )); ?></th>
1097 + <th class="assigned_count"><?php echo esc_html(__( 'Assigned Properties', 'propertyhive' )); ?></th>
1017 1098 <th class="settings">&nbsp;</th>
1018 1099 </tr>
1019 1100 </thead>
1020 1101 <tbody>
@@ -1022,9 +1103,9 @@
1022 1103 $args = array(
1023 1104 'hide_empty' => false,
1024 1105 'parent' => 0
1025 1106 );
1026 - $terms = get_terms( 'availability', $args );
1107 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'availability' ) ) );
1027 1108
1028 1109 if ( !empty( $terms ) && !is_wp_error( $terms ) )
1029 1110 {
1030 1111 foreach ( $terms as $term )
@@ -1121,9 +1202,9 @@
1121 1202 <th class="cb" style="width:1px;"><input class="select_all" type="checkbox" style="margin: 2px 0 0 0;"></th>
1122 1203 <th class="id" style="width:45px;"><?php echo esc_html(__( 'ID', 'propertyhive' )); ?></th>
1123 1204 <?php do_action( 'propertyhive_custom_field_property_type_table_before_header_column' ); ?>
1124 1205 <th class="type"><?php echo esc_html(__( 'Property Type', 'propertyhive' )); ?></th>
1125 - <th class="assigned_count"><?php echo esc_html(__( $this::LINKED_POSTS_COLUMN_HEADING, 'propertyhive' )); ?></th>
1206 + <th class="assigned_count"><?php echo esc_html(__( 'Assigned Properties', 'propertyhive' )); ?></th>
1126 1207 <th class="settings">&nbsp;</th>
1127 1208 </tr>
1128 1209 </thead>
1129 1210 <tbody>
@@ -1131,9 +1212,9 @@
1131 1212 $args = array(
1132 1213 'hide_empty' => false,
1133 1214 'parent' => 0
1134 1215 );
1135 - $terms = get_terms( 'property_type', $args );
1216 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_type' ) ) );
1136 1217
1137 1218 if ( !empty( $terms ) && !is_wp_error( $terms ) )
1138 1219 {
1139 1220 foreach ($terms as $term)
@@ -1143,9 +1224,9 @@
1143 1224 $args = array(
1144 1225 'hide_empty' => false,
1145 1226 'parent' => $parent_term_id
1146 1227 );
1147 - $subterms = get_terms( 'property_type', $args );
1228 + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_type' ) ) );
1148 1229 ?>
1149 1230 <tr>
1150 1231 <td class="cb"><?php if ( empty( $subterms ) ) { ?><input type="checkbox" name="term_id[]" value="<?php echo esc_attr($term->term_id); ?>"><?php }else{ echo '&nbsp;'; } ?></td>
1151 1232 <td class="id"><?php echo esc_html($term->term_id); ?></td>
@@ -1235,9 +1316,9 @@
1235 1316 <th class="cb" style="width:1px;"><input class="select_all" type="checkbox" style="margin: 2px 0 0 0;"></th>
1236 1317 <th class="id" style="width:45px;"><?php echo esc_html(__( 'ID', 'propertyhive' )); ?></th>
1237 1318 <?php do_action( 'propertyhive_custom_field_commercial_property_type_table_before_header_column' ); ?>
1238 1319 <th class="type"><?php echo esc_html(__( 'Property Type', 'propertyhive' )); ?></th>
1239 - <th class="assigned_count"><?php echo esc_html(__( $this::LINKED_POSTS_COLUMN_HEADING, 'propertyhive' )); ?></th>
1320 + <th class="assigned_count"><?php echo esc_html(__( 'Assigned Properties', 'propertyhive' )); ?></th>
1240 1321 <th class="settings">&nbsp;</th>
1241 1322 </tr>
1242 1323 </thead>
1243 1324 <tbody>
@@ -1245,9 +1326,9 @@
1245 1326 $args = array(
1246 1327 'hide_empty' => false,
1247 1328 'parent' => 0
1248 1329 );
1249 - $terms = get_terms( 'commercial_property_type', $args );
1330 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'commercial_property_type' ) ) );
1250 1331
1251 1332 if ( !empty( $terms ) && !is_wp_error( $terms ) )
1252 1333 {
1253 1334 foreach ($terms as $term)
@@ -1257,9 +1338,9 @@
1257 1338 $args = array(
1258 1339 'hide_empty' => false,
1259 1340 'parent' => $parent_term_id
1260 1341 );
1261 - $subterms = get_terms( 'commercial_property_type', $args );
1342 + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'commercial_property_type' ) ) );
1262 1343 ?>
1263 1344 <tr>
1264 1345 <td class="cb"><?php if ( empty( $subterms ) ) { ?><input type="checkbox" name="term_id[]" value="<?php echo esc_attr($term->term_id); ?>"><?php }else{ echo '&nbsp;'; } ?></td>
1265 1346 <td class="id"><?php echo esc_html($term->term_id); ?></td>
@@ -1348,9 +1429,9 @@
1348 1429 <tr>
1349 1430 <th class="cb" style="width:1px;"><input class="select_all" type="checkbox" style="margin: 2px 0 0 0;"></th>
1350 1431 <th class="id" style="width:45px;"><?php echo esc_html(__( 'ID', 'propertyhive' )); ?></th>
1351 1432 <th class="type"><?php echo esc_html(__( 'Location', 'propertyhive' )); ?></th>
1352 - <th class="assigned_count"><?php echo esc_html(__( $this::LINKED_POSTS_COLUMN_HEADING, 'propertyhive' )); ?></th>
1433 + <th class="assigned_count"><?php echo esc_html(__( 'Assigned Properties', 'propertyhive' )); ?></th>
1353 1434 <th class="settings">&nbsp;</th>
1354 1435 </tr>
1355 1436 </thead>
1356 1437 <tbody>
@@ -1358,9 +1439,9 @@
1358 1439 $args = array(
1359 1440 'hide_empty' => false,
1360 1441 'parent' => 0
1361 1442 );
1362 - $terms = get_terms( 'location', $args );
1443 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) );
1363 1444
1364 1445 if ( !empty( $terms ) && !is_wp_error( $terms ) )
1365 1446 {
1366 1447 foreach ($terms as $term)
@@ -1368,9 +1449,9 @@
1368 1449 $args = array(
1369 1450 'hide_empty' => false,
1370 1451 'parent' => $term->term_id
1371 1452 );
1372 - $subterms = get_terms( 'location', $args );
1453 + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) );
1373 1454 ?>
1374 1455 <tr>
1375 1456 <td class="cb"><?php if ( empty( $subterms ) ) { ?><input type="checkbox" name="term_id[]" value="<?php echo esc_attr($term->term_id); ?>"><?php }else{ echo '&nbsp;'; } ?></td>
1376 1457 <td class="id"><?php echo esc_html($term->term_id); ?></td>
@@ -1391,9 +1472,9 @@
1391 1472 $args = array(
1392 1473 'hide_empty' => false,
1393 1474 'parent' => $term->term_id
1394 1475 );
1395 - $subsubterms = get_terms( 'location', $args );
1476 + $subsubterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) );
1396 1477 ?>
1397 1478 <tr>
1398 1479 <td class="cb"><?php if ( empty( $subsubterms ) ) { ?><input type="checkbox" name="term_id[]" value="<?php echo esc_attr($term->term_id); ?>"><?php }else{ echo '&nbsp;'; } ?></td>
1399 1480 <td class="id"><?php echo esc_html($term->term_id); ?></td>
@@ -1482,9 +1563,9 @@
1482 1563 <tr>
1483 1564 <th class="cb" style="width:1px;"><input class="select_all" type="checkbox" style="margin: 2px 0 0 0;"></th>
1484 1565 <th class="id" style="width:45px;"><?php echo esc_html(__( 'ID', 'propertyhive' )); ?></th>
1485 1566 <th class="type"><?php echo esc_html(__( 'Parking', 'propertyhive' )); ?></th>
1486 - <th class="assigned_count"><?php echo esc_html(__( $this::LINKED_POSTS_COLUMN_HEADING, 'propertyhive' )); ?></th>
1567 + <th class="assigned_count"><?php echo esc_html(__( 'Assigned Properties', 'propertyhive' )); ?></th>
1487 1568 <th class="settings">&nbsp;</th>
1488 1569 </tr>
1489 1570 </thead>
1490 1571 <tbody>
@@ -1492,9 +1573,9 @@
1492 1573 $args = array(
1493 1574 'hide_empty' => false,
1494 1575 'parent' => 0
1495 1576 );
1496 - $terms = get_terms( 'parking', $args );
1577 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'parking' ) ) );
1497 1578
1498 1579 if ( !empty( $terms ) && !is_wp_error( $terms ) )
1499 1580 {
1500 1581 foreach ($terms as $term)
@@ -1564,9 +1645,9 @@
1564 1645 <tr>
1565 1646 <th class="cb" style="width:1px;"><input class="select_all" type="checkbox" style="margin: 2px 0 0 0;"></th>
1566 1647 <th class="id" style="width:45px;"><?php echo esc_html(__( 'ID', 'propertyhive' )); ?></th>
1567 1648 <th class="type"><?php echo esc_html(__( 'Outside Space', 'propertyhive' )); ?></th>
1568 - <th class="assigned_count"><?php echo esc_html(__( $this::LINKED_POSTS_COLUMN_HEADING, 'propertyhive' )); ?></th>
1649 + <th class="assigned_count"><?php echo esc_html(__( 'Assigned Properties', 'propertyhive' )); ?></th>
1569 1650 <th class="settings">&nbsp;</th>
1570 1651 </tr>
1571 1652 </thead>
1572 1653 <tbody>
@@ -1574,9 +1655,9 @@
1574 1655 $args = array(
1575 1656 'hide_empty' => false,
1576 1657 'parent' => 0
1577 1658 );
1578 - $terms = get_terms( 'outside_space', $args );
1659 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'outside_space' ) ) );
1579 1660
1580 1661 if ( !empty( $terms ) && !is_wp_error( $terms ) )
1581 1662 {
1582 1663 foreach ($terms as $term)
@@ -1646,9 +1727,9 @@
1646 1727 <tr>
1647 1728 <th class="cb" style="width:1px;"><input class="select_all" type="checkbox" style="margin: 2px 0 0 0;"></th>
1648 1729 <th class="id" style="width:45px;"><?php echo esc_html(__( 'ID', 'propertyhive' )); ?></th>
1649 1730 <th class="type"><?php echo esc_html(__( 'Price Qualifier', 'propertyhive' )); ?></th>
1650 - <th class="assigned_count"><?php echo esc_html(__( $this::LINKED_POSTS_COLUMN_HEADING, 'propertyhive' )); ?></th>
1731 + <th class="assigned_count"><?php echo esc_html(__( 'Assigned Properties', 'propertyhive' )); ?></th>
1651 1732 <th class="settings">&nbsp;</th>
1652 1733 </tr>
1653 1734 </thead>
1654 1735 <tbody>
@@ -1656,9 +1737,9 @@
1656 1737 $args = array(
1657 1738 'hide_empty' => false,
1658 1739 'parent' => 0
1659 1740 );
1660 - $terms = get_terms( 'price_qualifier', $args );
1741 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'price_qualifier' ) ) );
1661 1742
1662 1743 if ( !empty( $terms ) && !is_wp_error( $terms ) )
1663 1744 {
1664 1745 foreach ($terms as $term)
@@ -1728,9 +1809,9 @@
1728 1809 <tr>
1729 1810 <th class="cb" style="width:1px;"><input class="select_all" type="checkbox" style="margin: 2px 0 0 0;"></th>
1730 1811 <th class="id" style="width:45px;"><?php echo esc_html(__( 'ID', 'propertyhive' )); ?></th>
1731 1812 <th class="type"><?php echo esc_html(__( 'Sale By', 'propertyhive' )); ?></th>
1732 - <th class="assigned_count"><?php echo esc_html(__( $this::LINKED_POSTS_COLUMN_HEADING, 'propertyhive' )); ?></th>
1813 + <th class="assigned_count"><?php echo esc_html(__( 'Assigned Properties', 'propertyhive' )); ?></th>
1733 1814 <th class="settings">&nbsp;</th>
1734 1815 </tr>
1735 1816 </thead>
1736 1817 <tbody>
@@ -1738,9 +1819,9 @@
1738 1819 $args = array(
1739 1820 'hide_empty' => false,
1740 1821 'parent' => 0
1741 1822 );
1742 - $terms = get_terms( 'sale_by', $args );
1823 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'sale_by' ) ) );
1743 1824
1744 1825 if ( !empty( $terms ) && !is_wp_error( $terms ) )
1745 1826 {
1746 1827 foreach ($terms as $term)
@@ -1810,9 +1891,9 @@
1810 1891 <tr>
1811 1892 <th class="cb" style="width:1px;"><input class="select_all" type="checkbox" style="margin: 2px 0 0 0;"></th>
1812 1893 <th class="id" style="width:45px;"><?php echo esc_html(__( 'ID', 'propertyhive' )); ?></th>
1813 1894 <th class="type"><?php echo esc_html(__( 'Tenure', 'propertyhive' )); ?></th>
1814 - <th class="assigned_count"><?php echo esc_html(__( $this::LINKED_POSTS_COLUMN_HEADING, 'propertyhive' )); ?></th>
1895 + <th class="assigned_count"><?php echo esc_html(__( 'Assigned Properties', 'propertyhive' )); ?></th>
1815 1896 <th class="settings">&nbsp;</th>
1816 1897 </tr>
1817 1898 </thead>
1818 1899 <tbody>
@@ -1820,9 +1901,9 @@
1820 1901 $args = array(
1821 1902 'hide_empty' => false,
1822 1903 'parent' => 0
1823 1904 );
1824 - $terms = get_terms( 'tenure', $args );
1905 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'tenure' ) ) );
1825 1906
1826 1907 if ( !empty( $terms ) && !is_wp_error( $terms ) )
1827 1908 {
1828 1909 foreach ($terms as $term)
@@ -1892,9 +1973,9 @@
1892 1973 <tr>
1893 1974 <th class="cb" style="width:1px;"><input class="select_all" type="checkbox" style="margin: 2px 0 0 0;"></th>
1894 1975 <th class="id" style="width:45px;"><?php echo esc_html(__( 'ID', 'propertyhive' )); ?></th>
1895 1976 <th class="type"><?php echo esc_html(__( 'Tenure', 'propertyhive' )); ?></th>
1896 - <th class="assigned_count"><?php echo esc_html(__( $this::LINKED_POSTS_COLUMN_HEADING, 'propertyhive' )); ?></th>
1977 + <th class="assigned_count"><?php echo esc_html(__( 'Assigned Properties', 'propertyhive' )); ?></th>
1897 1978 <th class="settings">&nbsp;</th>
1898 1979 </tr>
1899 1980 </thead>
1900 1981 <tbody>
@@ -1902,9 +1983,9 @@
1902 1983 $args = array(
1903 1984 'hide_empty' => false,
1904 1985 'parent' => 0
1905 1986 );
1906 - $terms = get_terms( 'commercial_tenure', $args );
1987 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'commercial_tenure' ) ) );
1907 1988
1908 1989 if ( !empty( $terms ) && !is_wp_error( $terms ) )
1909 1990 {
1910 1991 foreach ($terms as $term)
@@ -1974,9 +2055,9 @@
1974 2055 <tr>
1975 2056 <th class="cb" style="width:1px;"><input class="select_all" type="checkbox" style="margin: 2px 0 0 0;"></th>
1976 2057 <th class="id" style="width:45px;"><?php echo esc_html(__( 'ID', 'propertyhive' )); ?></th>
1977 2058 <th class="type"><?php echo esc_html(__( 'Furnished', 'propertyhive' )); ?></th>
1978 - <th class="assigned_count"><?php echo esc_html(__( $this::LINKED_POSTS_COLUMN_HEADING, 'propertyhive' )); ?></th>
2059 + <th class="assigned_count"><?php echo esc_html(__( 'Assigned Properties', 'propertyhive' )); ?></th>
1979 2060 <th class="settings">&nbsp;</th>
1980 2061 </tr>
1981 2062 </thead>
1982 2063 <tbody>
@@ -1984,9 +2065,9 @@
1984 2065 $args = array(
1985 2066 'hide_empty' => false,
1986 2067 'parent' => 0
1987 2068 );
1988 - $terms = get_terms( 'furnished', $args );
2069 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'furnished' ) ) );
1989 2070
1990 2071 if ( !empty( $terms ) && !is_wp_error( $terms ) )
1991 2072 {
1992 2073 foreach ($terms as $term)
@@ -2042,9 +2123,9 @@
2042 2123 </td>
2043 2124 </tr>
2044 2125 <?php foreach( array ('property_management' => __( 'Property Management', 'propertyhive' ), 'tenancy_management' => __( 'Tenancy Management', 'propertyhive' ) ) as $type => $title): ?>
2045 2126 <tr valign="top">
2046 - <th scope="row" class="titledesc no-auto"><?php echo esc_html(__( $title, 'propertyhive' )); ?></th>
2127 + <th scope="row" class="titledesc no-auto"><?php echo esc_html( $title ); ?></th>
2047 2128 <td class="forminp no-auto">
2048 2129 <table class="ph_customfields widefat" cellspacing="0">
2049 2130 <thead>
2050 2131 <tr>
@@ -2059,9 +2140,9 @@
2059 2140 $args = array(
2060 2141 'hide_empty' => false,
2061 2142 'parent' => 0
2062 2143 );
2063 - $terms = get_terms( 'management_key_date_type', $args );
2144 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'management_key_date_type' ) ) );
2064 2145
2065 2146 if ( !empty( $terms ) && !is_wp_error( $terms ) )
2066 2147 {
2067 2148 $recurrence_rules = get_option( 'propertyhive_key_date_type', array() );
@@ -2182,9 +2263,9 @@
2182 2263 <tr>
2183 2264 <th class="cb" style="width:1px;"><input class="select_all" type="checkbox" style="margin: 2px 0 0 0;"></th>
2184 2265 <th class="id" style="width:45px;"><?php echo esc_html(__( 'ID', 'propertyhive' )); ?></th>
2185 2266 <th class="type"><?php echo esc_html(__( 'Marketing Flag', 'propertyhive' )); ?></th>
2186 - <th class="assigned_count"><?php echo esc_html(__( $this::LINKED_POSTS_COLUMN_HEADING, 'propertyhive' )); ?></th>
2267 + <th class="assigned_count"><?php echo esc_html(__( 'Assigned Properties', 'propertyhive' )); ?></th>
2187 2268 <th class="settings">&nbsp;</th>
2188 2269 </tr>
2189 2270 </thead>
2190 2271 <tbody>
@@ -2192,9 +2273,9 @@
2192 2273 $args = array(
2193 2274 'hide_empty' => false,
2194 2275 'parent' => 0
2195 2276 );
2196 - $terms = get_terms( 'marketing_flag', $args );
2277 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'marketing_flag' ) ) );
2197 2278
2198 2279 if ( !empty( $terms ) && !is_wp_error( $terms ) )
2199 2280 {
2200 2281 foreach ($terms as $term)
@@ -2264,9 +2345,9 @@
2264 2345 <tr>
2265 2346 <th class="cb" style="width:1px;"><input class="select_all" type="checkbox" style="margin: 2px 0 0 0;"></th>
2266 2347 <th class="id" style="width:45px;"><?php echo esc_html(__( 'ID', 'propertyhive' )); ?></th>
2267 2348 <th class="type"><?php echo esc_html(__( 'Property Feature', 'propertyhive' )); ?></th>
2268 - <th class="assigned_count"><?php echo esc_html(__( $this::LINKED_POSTS_COLUMN_HEADING, 'propertyhive' )); ?></th>
2349 + <th class="assigned_count"><?php echo esc_html(__( 'Assigned Properties', 'propertyhive' )); ?></th>
2269 2350 <th class="settings">&nbsp;</th>
2270 2351 </tr>
2271 2352 </thead>
2272 2353 <tbody>
@@ -2274,9 +2355,9 @@
2274 2355 $args = array(
2275 2356 'hide_empty' => false,
2276 2357 'parent' => 0
2277 2358 );
2278 - $terms = get_terms( 'property_feature', $args );
2359 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_feature' ) ) );
2279 2360
2280 2361 if ( !empty( $terms ) && !is_wp_error( $terms ) )
2281 2362 {
2282 2363 foreach ($terms as $term)
@@ -2327,9 +2408,10 @@
2327 2408 * @return string
2328 2409 */
2329 2410 public function get_custom_fields_availability_setting()
2330 2411 {
2331 - $current_id = empty( $_REQUEST['id'] ) ? '' : (int)$_REQUEST['id'];
2412 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This getter only renders stored taxonomy settings and does not mutate state.
2413 + $current_id = $this->normalize_custom_fields_render_term_id( ( isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['id'] ) ) : '' );
2332 2414
2333 2415 $taxonomy = 'availability';
2334 2416 $term_name = '';
2335 2417 if ($current_id != '')
@@ -2341,9 +2423,9 @@
2341 2423 $departments = ph_get_departments();
2342 2424
2343 2425 $args = array(
2344 2426
2345 - array( 'title' => __( ( $current_id == '' ? 'Add New Availability Option' : 'Edit Availability' ), 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_availability_settings' ),
2427 + array( 'title' => ( $current_id == '' ? __( 'Add New Availability Option', 'propertyhive' ) : __( 'Edit Availability', 'propertyhive' ) ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_availability_settings' ),
2346 2428
2347 2429 array(
2348 2430 'title' => __( 'Availability', 'propertyhive' ),
2349 2431 'id' => 'availability_name',
@@ -2395,9 +2477,10 @@
2395 2477 * @return string
2396 2478 */
2397 2479 public function get_custom_fields_property_type_setting()
2398 2480 {
2399 - $current_id = empty( $_REQUEST['id'] ) ? '' : (int)$_REQUEST['id'];
2481 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This getter only renders stored taxonomy settings and does not mutate state.
2482 + $current_id = $this->normalize_custom_fields_render_term_id( ( isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['id'] ) ) : '' );
2400 2483
2401 2484 $taxonomy = 'property_type';
2402 2485 $term_name = '';
2403 2486 $term_parent = '';
@@ -2412,11 +2495,12 @@
2412 2495
2413 2496 $args = array(
2414 2497 'hide_empty' => false,
2415 2498 'parent' => 0,
2499 + // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- Edit screens exclude only the currently edited term from its own parent choices. Each line is exclude=>array($current_id), where current_id is cast to int from the request; one term ID prevents self-parenting.
2416 2500 'exclude' => array($current_id)
2417 2501 );
2418 - $terms = get_terms( 'property_type', $args );
2502 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_type' ) ) );
2419 2503 if ( !empty( $terms ) && !is_wp_error( $terms ) )
2420 2504 {
2421 2505 foreach ($terms as $term)
2422 2506 {
@@ -2425,9 +2509,9 @@
2425 2509 }
2426 2510
2427 2511 $args = array(
2428 2512
2429 - array( 'title' => __( ( $current_id == '' ? 'Add New Property Type' : 'Edit Property Type' ), 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_property_type_settings' ),
2513 + array( 'title' => ( $current_id == '' ? __( 'Add New Property Type', 'propertyhive' ) : __( 'Edit Property Type', 'propertyhive' ) ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_property_type_settings' ),
2430 2514
2431 2515 array(
2432 2516 'title' => __( 'Property Type', 'propertyhive' ),
2433 2517 'id' => 'property_type_name',
@@ -2466,9 +2550,10 @@
2466 2550 * @return string
2467 2551 */
2468 2552 public function get_custom_fields_commercial_property_type_setting()
2469 2553 {
2470 - $current_id = empty( $_REQUEST['id'] ) ? '' : (int)$_REQUEST['id'];
2554 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This getter only renders stored taxonomy settings and does not mutate state.
2555 + $current_id = $this->normalize_custom_fields_render_term_id( ( isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['id'] ) ) : '' );
2471 2556
2472 2557 $taxonomy = 'commercial_property_type';
2473 2558 $term_name = '';
2474 2559 $term_parent = '';
@@ -2483,11 +2568,12 @@
2483 2568
2484 2569 $args = array(
2485 2570 'hide_empty' => false,
2486 2571 'parent' => 0,
2572 + // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- Edit screens exclude only the currently edited term from its own parent choices. Each line is exclude=>array($current_id), where current_id is cast to int from the request; one term ID prevents self-parenting.
2487 2573 'exclude' => array($current_id)
2488 2574 );
2489 - $terms = get_terms( 'commercial_property_type', $args );
2575 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'commercial_property_type' ) ) );
2490 2576 if ( !empty( $terms ) && !is_wp_error( $terms ) )
2491 2577 {
2492 2578 foreach ($terms as $term)
2493 2579 {
@@ -2496,9 +2582,9 @@
2496 2582 }
2497 2583
2498 2584 $args = array(
2499 2585
2500 - array( 'title' => __( ( $current_id == '' ? 'Add New Property Type' : 'Edit Property Type' ), 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_commercial_property_type_settings' ),
2586 + array( 'title' => ( $current_id == '' ? __( 'Add New Property Type', 'propertyhive' ) : __( 'Edit Property Type', 'propertyhive' ) ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_commercial_property_type_settings' ),
2501 2587
2502 2588 array(
2503 2589 'title' => __( 'Property Type', 'propertyhive' ),
2504 2590 'id' => 'commercial_property_type_name',
@@ -2537,9 +2623,10 @@
2537 2623 * @return string
2538 2624 */
2539 2625 public function get_custom_fields_location_setting()
2540 2626 {
2541 - $current_id = empty( $_REQUEST['id'] ) ? '' : (int)$_REQUEST['id'];
2627 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This getter only renders stored taxonomy settings and does not mutate state.
2628 + $current_id = $this->normalize_custom_fields_render_term_id( ( isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['id'] ) ) : '' );
2542 2629
2543 2630 $taxonomy = 'location';
2544 2631 $term_name = '';
2545 2632 $term_parent = '';
@@ -2554,11 +2641,12 @@
2554 2641
2555 2642 $args = array(
2556 2643 'hide_empty' => false,
2557 2644 'parent' => 0,
2645 + // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- Edit screens exclude only the currently edited term from its own parent choices. Each line is exclude=>array($current_id), where current_id is cast to int from the request; one term ID prevents self-parenting.
2558 2646 'exclude' => array($current_id)
2559 2647 );
2560 - $terms = get_terms( $taxonomy, $args );
2648 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => $taxonomy ) ) );
2561 2649 if ( !empty( $terms ) && !is_wp_error( $terms ) )
2562 2650 {
2563 2651 foreach ($terms as $term)
2564 2652 {
@@ -2566,11 +2654,12 @@
2566 2654
2567 2655 $args = array(
2568 2656 'hide_empty' => false,
2569 2657 'parent' => $term->term_id,
2658 + // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- Edit screens exclude only the currently edited term from its own parent choices. Each line is exclude=>array($current_id), where current_id is cast to int from the request; one term ID prevents self-parenting.
2570 2659 'exclude' => array($current_id)
2571 2660 );
2572 - $terms = get_terms( $taxonomy, $args );
2661 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => $taxonomy ) ) );
2573 2662 if ( !empty( $terms ) && !is_wp_error( $terms ) )
2574 2663 {
2575 2664 foreach ($terms as $term)
2576 2665 {
@@ -2581,9 +2670,9 @@
2581 2670 }
2582 2671
2583 2672 $args = array(
2584 2673
2585 - array( 'title' => __( ( $current_id == '' ? 'Add New Location' : 'Edit Location' ), 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_location_settings' ),
2674 + array( 'title' => ( $current_id == '' ? __( 'Add New Location', 'propertyhive' ) : __( 'Edit Location', 'propertyhive' ) ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_location_settings' ),
2586 2675
2587 2676 array(
2588 2677 'title' => __( 'Location', 'propertyhive' ),
2589 2678 'id' => 'location_name',
@@ -2622,9 +2711,10 @@
2622 2711 * @return string
2623 2712 */
2624 2713 public function get_custom_fields_parking_setting()
2625 2714 {
2626 - $current_id = empty( $_REQUEST['id'] ) ? '' : (int)$_REQUEST['id'];
2715 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This getter only renders stored taxonomy settings and does not mutate state.
2716 + $current_id = $this->normalize_custom_fields_render_term_id( ( isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['id'] ) ) : '' );
2627 2717
2628 2718 $taxonomy = 'parking';
2629 2719 $term_name = '';
2630 2720 if ($current_id != '')
@@ -2634,9 +2724,9 @@
2634 2724 }
2635 2725
2636 2726 $args = array(
2637 2727
2638 - array( 'title' => __( ( $current_id == '' ? 'Add New Parking Option' : 'Edit Parking' ), 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_parking_settings' ),
2728 + array( 'title' => ( $current_id == '' ? __( 'Add New Parking Option', 'propertyhive' ) : __( 'Edit Parking', 'propertyhive' ) ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_parking_settings' ),
2639 2729
2640 2730 array(
2641 2731 'title' => __( 'Parking', 'propertyhive' ),
2642 2732 'id' => 'parking_name',
@@ -2665,9 +2755,10 @@
2665 2755 * @return string
2666 2756 */
2667 2757 public function get_custom_fields_outside_space_setting()
2668 2758 {
2669 - $current_id = empty( $_REQUEST['id'] ) ? '' : (int)$_REQUEST['id'];
2759 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This getter only renders stored taxonomy settings and does not mutate state.
2760 + $current_id = $this->normalize_custom_fields_render_term_id( ( isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['id'] ) ) : '' );
2670 2761
2671 2762 $taxonomy = 'outside_space';
2672 2763 $term_name = '';
2673 2764 if ($current_id != '')
@@ -2677,9 +2768,9 @@
2677 2768 }
2678 2769
2679 2770 $args = array(
2680 2771
2681 - array( 'title' => __( ( $current_id == '' ? 'Add New Outside Space' : 'Edit Outside Space' ), 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_outside_space_settings' ),
2772 + array( 'title' => ( $current_id == '' ? __( 'Add New Outside Space', 'propertyhive' ) : __( 'Edit Outside Space', 'propertyhive' ) ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_outside_space_settings' ),
2682 2773
2683 2774 array(
2684 2775 'title' => __( 'Outside Space', 'propertyhive' ),
2685 2776 'id' => 'outside_space_name',
@@ -2710,20 +2801,26 @@
2710 2801 public function get_custom_fields_delete($current_id, $taxonomy, $taxonomy_name)
2711 2802 {
2712 2803 global $save_button_text;
2713 2804
2805 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global.
2714 2806 $save_button_text = __( 'Delete', 'propertyhive' );
2715 2807
2716 2808 //$taxonomy = 'outside_space';
2717 2809 //$taxonomy_name = __( 'Outside Space', 'propertyhive' );
2718 2810
2719 - if ( isset($_POST['confirm_removal']) && $_POST['confirm_removal'] == 1 )
2811 + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- This branch only selects a read-only success view; the deletion mutation occurs in save() after the settings nonce and capability checks.
2812 + $confirm_removal = ( isset( $_POST['confirm_removal'] ) && is_string( $_POST['confirm_removal'] ) ) ? sanitize_text_field( wp_unslash( $_POST['confirm_removal'] ) ) : '';
2813 + if ( '1' === $confirm_removal )
2720 2814 {
2721 2815 // A term has just been deleted
2722 2816 global $hide_save_button, $show_cancel_button, $cancel_button_href;
2723 2817
2818 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global.
2724 2819 $hide_save_button = TRUE;
2820 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global.
2725 2821 $show_cancel_button = TRUE;
2822 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global.
2726 2823 $cancel_button_href = admin_url( 'admin.php?page=ph-settings&tab=customfields&section=' . str_replace("_", "-", $taxonomy) );
2727 2824
2728 2825 $args = array();
2729 2826
@@ -2770,8 +2867,9 @@
2770 2867 $query_args = array(
2771 2868 'post_type' => 'property',
2772 2869 'nopaging' => true,
2773 2870 'post_status' => array( 'pending', 'auto-draft', 'draft', 'private', 'publish', 'future', 'trash' ),
2871 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Term deletion/reassignment must locate all properties or key dates attached to this selected term before removing it.
2774 2872 'tax_query' => array(
2775 2873 array(
2776 2874 'taxonomy' => $taxonomy,
2777 2875 'field' => 'id',
@@ -2792,8 +2890,9 @@
2792 2890 $query_args = array(
2793 2891 'post_type' => 'contact',
2794 2892 'nopaging' => true,
2795 2893 'post_status' => array( 'pending', 'auto-draft', 'draft', 'private', 'publish', 'future', 'trash' ),
2894 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Applicant preferences are serialized profile metadata; deletion/reassignment must inspect every applicant profile to preserve its other selections.
2796 2895 'meta_query' => array(
2797 2896 array(
2798 2897 'key' => '_contact_types',
2799 2898 'value' => 'applicant',
@@ -2850,8 +2949,9 @@
2850 2949 $query_args = array(
2851 2950 'post_type' => 'key_date',
2852 2951 'nopaging' => true,
2853 2952 'post_status' => array( 'pending', 'auto-draft', 'draft', 'private', 'publish', 'future', 'trash' ),
2953 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Term deletion/reassignment must locate all properties or key dates attached to this selected term before removing it.
2854 2954 'tax_query' => array(
2855 2955 array(
2856 2956 'taxonomy' => $taxonomy,
2857 2957 'field' => 'id',
@@ -2873,12 +2973,13 @@
2873 2973 $alternative_terms['none'] = '-- ' . __( 'Don\'t Reassign', 'propertyhive' ) . ' --';
2874 2974
2875 2975 $term_args = array(
2876 2976 'hide_empty' => false,
2977 + // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- This is a get_terms argument excluding the terms being deleted from reassignment choices, not a posts exclusion query.
2877 2978 'exclude' => $term_ids,
2878 2979 'parent' => 0
2879 2980 );
2880 - $terms = get_terms( $taxonomy, $term_args );
2981 + $terms = get_terms( array_merge( wp_parse_args( $term_args ), array( 'taxonomy' => $taxonomy ) ) );
2881 2982
2882 2983 if ( !empty( $terms ) && !is_wp_error( $terms ) )
2883 2984 {
2884 2985 foreach ($terms as $term)
@@ -2886,12 +2987,13 @@
2886 2987 $alternative_terms[$term->term_id] = $term->name;
2887 2988
2888 2989 $term_args = array(
2889 2990 'hide_empty' => false,
2991 + // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- This is a get_terms argument excluding the terms being deleted from reassignment choices, not a posts exclusion query.
2890 2992 'exclude' => $term_ids,
2891 2993 'parent' => $term->term_id
2892 2994 );
2893 - $subterms = get_terms( $taxonomy, $term_args );
2995 + $subterms = get_terms( array_merge( wp_parse_args( $term_args ), array( 'taxonomy' => $taxonomy ) ) );
2894 2996
2895 2997 if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
2896 2998 {
2897 2999 foreach ($subterms as $term)
@@ -2899,12 +3001,13 @@
2899 3001 $alternative_terms[$term->term_id] = '- ' . $term->name;
2900 3002
2901 3003 $term_args = array(
2902 3004 'hide_empty' => false,
3005 + // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- This is a get_terms argument excluding the terms being deleted from reassignment choices, not a posts exclusion query.
2903 3006 'exclude' => $term_ids,
2904 3007 'parent' => $term->term_id
2905 3008 );
2906 - $subsubterms = get_terms( $taxonomy, $term_args );
3009 + $subsubterms = get_terms( array_merge( wp_parse_args( $term_args ), array( 'taxonomy' => $taxonomy ) ) );
2907 3010
2908 3011 if ( !empty( $subsubterms ) && !is_wp_error( $subsubterms ) )
2909 3012 {
2910 3013 foreach ($subsubterms as $term)
@@ -2962,9 +3065,10 @@
2962 3065 * @return string
2963 3066 */
2964 3067 public function get_custom_fields_price_qualifier_setting()
2965 3068 {
2966 - $current_id = empty( $_REQUEST['id'] ) ? '' : sanitize_title( $_REQUEST['id'] );
3069 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This settings getter only loads stored configuration for rendering; it does not mutate state.
3070 + $current_id = ( isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] ) ) ? sanitize_title( wp_unslash( $_REQUEST['id'] ) ) : '';
2967 3071
2968 3072 $taxonomy = 'price_qualifier';
2969 3073 $term_name = '';
2970 3074 if ($current_id != '')
@@ -2974,9 +3078,9 @@
2974 3078 }
2975 3079
2976 3080 $args = array(
2977 3081
2978 - array( 'title' => __( ( $current_id == '' ? 'Add New Price Qualifier' : 'Edit Price Qualifier' ), 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_price_qualifier_settings' ),
3082 + array( 'title' => ( $current_id == '' ? __( 'Add New Price Qualifier', 'propertyhive' ) : __( 'Edit Price Qualifier', 'propertyhive' ) ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_price_qualifier_settings' ),
2979 3083
2980 3084 array(
2981 3085 'title' => __( 'Price Qualifier', 'propertyhive' ),
2982 3086 'id' => 'price_qualifier_name',
@@ -3005,9 +3109,10 @@
3005 3109 * @return string
3006 3110 */
3007 3111 public function get_custom_fields_sale_by_setting()
3008 3112 {
3009 - $current_id = empty( $_REQUEST['id'] ) ? '' : sanitize_title( $_REQUEST['id'] );
3113 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This settings getter only loads stored configuration for rendering; it does not mutate state.
3114 + $current_id = ( isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] ) ) ? sanitize_title( wp_unslash( $_REQUEST['id'] ) ) : '';
3010 3115
3011 3116 $taxonomy = 'sale_by';
3012 3117 $term_name = '';
3013 3118 if ($current_id != '')
@@ -3017,9 +3122,9 @@
3017 3122 }
3018 3123
3019 3124 $args = array(
3020 3125
3021 - array( 'title' => __( ( $current_id == '' ? 'Add New Sale By' : 'Edit Sale By' ), 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_sale_by_settings' ),
3126 + array( 'title' => ( $current_id == '' ? __( 'Add New Sale By', 'propertyhive' ) : __( 'Edit Sale By', 'propertyhive' ) ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_sale_by_settings' ),
3022 3127
3023 3128 array(
3024 3129 'title' => __( 'Sale By', 'propertyhive' ),
3025 3130 'id' => 'sale_by_name',
@@ -3048,9 +3153,10 @@
3048 3153 * @return string
3049 3154 */
3050 3155 public function get_custom_fields_tenure_setting()
3051 3156 {
3052 - $current_id = empty( $_REQUEST['id'] ) ? '' : sanitize_title( $_REQUEST['id'] );
3157 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This settings getter only loads stored configuration for rendering; it does not mutate state.
3158 + $current_id = ( isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] ) ) ? sanitize_title( wp_unslash( $_REQUEST['id'] ) ) : '';
3053 3159
3054 3160 $taxonomy = 'tenure';
3055 3161 $term_name = '';
3056 3162 if ($current_id != '')
@@ -3060,9 +3166,9 @@
3060 3166 }
3061 3167
3062 3168 $args = array(
3063 3169
3064 - array( 'title' => __( ( $current_id == '' ? 'Add New Tenure' : 'Edit Tenure' ), 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_tenure_settings' ),
3170 + array( 'title' => ( $current_id == '' ? __( 'Add New Tenure', 'propertyhive' ) : __( 'Edit Tenure', 'propertyhive' ) ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_tenure_settings' ),
3065 3171
3066 3172 array(
3067 3173 'title' => __( 'Tenure', 'propertyhive' ),
3068 3174 'id' => 'tenure_name',
@@ -3091,9 +3197,10 @@
3091 3197 * @return string
3092 3198 */
3093 3199 public function get_custom_fields_commercial_tenure_setting()
3094 3200 {
3095 - $current_id = empty( $_REQUEST['id'] ) ? '' : sanitize_title( $_REQUEST['id'] );
3201 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This settings getter only loads stored configuration for rendering; it does not mutate state.
3202 + $current_id = ( isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] ) ) ? sanitize_title( wp_unslash( $_REQUEST['id'] ) ) : '';
3096 3203
3097 3204 $taxonomy = 'commercial_tenure';
3098 3205 $term_name = '';
3099 3206 if ($current_id != '')
@@ -3103,9 +3210,9 @@
3103 3210 }
3104 3211
3105 3212 $args = array(
3106 3213
3107 - array( 'title' => __( ( $current_id == '' ? 'Add New Tenure' : 'Edit Tenure' ), 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_commercial_tenure_settings' ),
3214 + array( 'title' => ( $current_id == '' ? __( 'Add New Tenure', 'propertyhive' ) : __( 'Edit Tenure', 'propertyhive' ) ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_commercial_tenure_settings' ),
3108 3215
3109 3216 array(
3110 3217 'title' => __( 'Tenure', 'propertyhive' ),
3111 3218 'id' => 'commercial_tenure_name',
@@ -3134,9 +3241,10 @@
3134 3241 * @return string
3135 3242 */
3136 3243 public function get_custom_fields_furnished_setting()
3137 3244 {
3138 - $current_id = empty( $_REQUEST['id'] ) ? '' : sanitize_title( $_REQUEST['id'] );
3245 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This settings getter only loads stored configuration for rendering; it does not mutate state.
3246 + $current_id = ( isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] ) ) ? sanitize_title( wp_unslash( $_REQUEST['id'] ) ) : '';
3139 3247
3140 3248 $taxonomy = 'furnished';
3141 3249 $term_name = '';
3142 3250 if ($current_id != '')
@@ -3146,9 +3254,9 @@
3146 3254 }
3147 3255
3148 3256 $args = array(
3149 3257
3150 - array( 'title' => __( ( $current_id == '' ? 'Add New Furnished' : 'Edit Furnished' ), 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_furnished_settings' ),
3258 + array( 'title' => ( $current_id == '' ? __( 'Add New Furnished', 'propertyhive' ) : __( 'Edit Furnished', 'propertyhive' ) ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_furnished_settings' ),
3151 3259
3152 3260 array(
3153 3261 'title' => __( 'Furnished', 'propertyhive' ),
3154 3262 'id' => 'furnished_name',
@@ -3176,9 +3284,10 @@
3176 3284 * @return string
3177 3285 */
3178 3286 public function get_custom_fields_management_key_date_type_setting()
3179 3287 {
3180 - $current_id = empty( $_REQUEST['id'] ) ? '' : sanitize_title( $_REQUEST['id'] );
3288 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This settings getter only loads stored configuration for rendering; it does not mutate state.
3289 + $current_id = ( isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] ) ) ? sanitize_title( wp_unslash( $_REQUEST['id'] ) ) : '';
3181 3290
3182 3291 $taxonomy = 'management_key_date_type';
3183 3292 $term_name = '';
3184 3293 if ($current_id != '')
@@ -3211,9 +3320,9 @@
3211 3320 $interval_disabled = $recurrence['FREQ'] == 'ONCE' ? array('disabled' => 'disabled') : array();
3212 3321
3213 3322 $args = array(
3214 3323
3215 - array( 'title' => __( ( $current_id == '' ? 'Add New Management Date Type' : 'Edit Management Date Type' ), 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_management_key_date_type_settings' ),
3324 + array( 'title' => ( $current_id == '' ? __( 'Add New Management Date Type', 'propertyhive' ) : __( 'Edit Management Date Type', 'propertyhive' ) ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_management_key_date_type_settings' ),
3216 3325
3217 3326 array(
3218 3327 'title' => __( 'Description', 'propertyhive' ),
3219 3328 'id' => 'management_key_date_type_name',
@@ -3281,9 +3390,10 @@
3281 3390 * @return string
3282 3391 */
3283 3392 public function get_custom_fields_marketing_flag_setting()
3284 3393 {
3285 - $current_id = empty( $_REQUEST['id'] ) ? '' : sanitize_title( $_REQUEST['id'] );
3394 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This settings getter only loads stored configuration for rendering; it does not mutate state.
3395 + $current_id = ( isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] ) ) ? sanitize_title( wp_unslash( $_REQUEST['id'] ) ) : '';
3286 3396
3287 3397 $taxonomy = 'marketing_flag';
3288 3398 $term_name = '';
3289 3399 if ($current_id != '')
@@ -3293,9 +3403,9 @@
3293 3403 }
3294 3404
3295 3405 $args = array(
3296 3406
3297 - array( 'title' => __( ( $current_id == '' ? 'Add New Marketing Flag' : 'Edit Marketing Flag' ), 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_marketing_flag_settings' ),
3407 + array( 'title' => ( $current_id == '' ? __( 'Add New Marketing Flag', 'propertyhive' ) : __( 'Edit Marketing Flag', 'propertyhive' ) ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_marketing_flag_settings' ),
3298 3408
3299 3409 array(
3300 3410 'title' => __( 'Marketing Flag', 'propertyhive' ),
3301 3411 'id' => 'marketing_flag_name',
@@ -3324,9 +3434,10 @@
3324 3434 * @return string
3325 3435 */
3326 3436 public function get_custom_fields_property_feature_setting()
3327 3437 {
3328 - $current_id = empty( $_REQUEST['id'] ) ? '' : sanitize_title( $_REQUEST['id'] );
3438 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This settings getter only loads stored configuration for rendering; it does not mutate state.
3439 + $current_id = ( isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] ) ) ? sanitize_title( wp_unslash( $_REQUEST['id'] ) ) : '';
3329 3440
3330 3441 $taxonomy = 'property_feature';
3331 3442 $term_name = '';
3332 3443 if ($current_id != '')
@@ -3336,9 +3447,9 @@
3336 3447 }
3337 3448
3338 3449 $args = array(
3339 3450
3340 - array( 'title' => __( ( $current_id == '' ? 'Add New Property Feature' : 'Edit Property Feature' ), 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_property_feature_settings' ),
3451 + array( 'title' => ( $current_id == '' ? __( 'Add New Property Feature', 'propertyhive' ) : __( 'Edit Property Feature', 'propertyhive' ) ), 'type' => 'title', 'desc' => '', 'id' => 'custom_field_property_feature_settings' ),
3341 3452
3342 3453 array(
3343 3454 'title' => __( 'Property Feature', 'propertyhive' ),
3344 3455 'id' => 'property_feature_name',
@@ -3359,402 +3470,496 @@
3359 3470
3360 3471 return apply_filters( 'propertyhive_custom_field_property_feature_settings', $args );
3361 3472 }
3362 3473
3474 + private function normalize_custom_fields_field_type( $value ) {
3475 + if ( ! is_string( $value ) ) {
3476 + return 'text';
3477 + }
3478 +
3479 + $field_type = sanitize_key( $value );
3480 + $allowed_types = array( 'text', 'textarea', 'select', 'multiselect', 'checkbox', 'date', 'image', 'file' );
3481 +
3482 + return in_array( $field_type, $allowed_types, true ) ? $field_type : 'text';
3483 + }
3484 +
3485 + private function normalize_custom_fields_dropdown_options( $value ) {
3486 + if ( ! is_array( $value ) ) {
3487 + return '';
3488 + }
3489 +
3490 + $options = array();
3491 + foreach ( $value as $option ) {
3492 + if ( ! is_string( $option ) ) {
3493 + return '';
3494 + }
3495 + $options[] = sanitize_text_field( $option );
3496 + }
3497 +
3498 + return $options;
3499 + }
3500 +
3501 + private function normalize_custom_fields_checkbox( $value ) {
3502 + return ( is_string( $value ) && '1' === $value ) ? '1' : '';
3503 + }
3504 +
3505 + private function normalize_custom_fields_meta_box( $value ) {
3506 + return is_string( $value ) ? sanitize_key( $value ) : '';
3507 + }
3508 +
3509 + private function normalize_custom_fields_term_id( $value, $allow_empty = true ) {
3510 + if ( ! is_string( $value ) ) {
3511 + return null;
3512 + }
3513 +
3514 + $value = trim( $value );
3515 + if ( '' === $value ) {
3516 + return $allow_empty ? '' : null;
3517 + }
3518 +
3519 + if ( ! preg_match( '/^[0-9]+$/D', $value ) ) {
3520 + return null;
3521 + }
3522 +
3523 + $term_id = absint( $value );
3524 + return $term_id > 0 ? (string) $term_id : null;
3525 + }
3526 +
3527 + private function normalize_custom_fields_render_term_id( $value ) {
3528 + $term_id = $this->normalize_custom_fields_term_id( $value );
3529 + return ( null === $term_id || '' === $term_id ) ? '' : absint( $term_id );
3530 + }
3531 +
3532 + private function normalize_custom_fields_parent_id( $value ) {
3533 + if ( ! is_string( $value ) ) {
3534 + return null;
3535 + }
3536 +
3537 + $value = trim( $value );
3538 + if ( '' === $value ) {
3539 + return 0;
3540 + }
3541 +
3542 + if ( ! preg_match( '/^[0-9]+$/D', $value ) ) {
3543 + return null;
3544 + }
3545 +
3546 + return absint( $value );
3547 + }
3548 +
3549 + private function normalize_custom_fields_term_id_list( $value ) {
3550 + if ( ! is_string( $value ) || '' === trim( $value ) ) {
3551 + return array();
3552 + }
3553 +
3554 + $term_ids = array();
3555 + foreach ( explode( '-', trim( $value ) ) as $term_id ) {
3556 + if ( ! preg_match( '/^[0-9]+$/D', $term_id ) ) {
3557 + return array();
3558 + }
3559 +
3560 + $term_id = absint( $term_id );
3561 + if ( $term_id < 1 || in_array( (string) $term_id, $term_ids, true ) ) {
3562 + return array();
3563 + }
3564 +
3565 + $term_ids[] = (string) $term_id;
3566 + }
3567 +
3568 + return $term_ids;
3569 + }
3570 +
3571 + private function normalize_custom_fields_reassignment( $value, $deleted_ids ) {
3572 + if ( ! is_string( $value ) ) {
3573 + return null;
3574 + }
3575 +
3576 + $value = trim( $value );
3577 + if ( '' === $value || 'none' === $value || ! preg_match( '/^[0-9]+$/D', $value ) ) {
3578 + return null;
3579 + }
3580 +
3581 + $term_id = (string) absint( $value );
3582 + if ( '0' === $term_id || in_array( $term_id, $deleted_ids, true ) ) {
3583 + return null;
3584 + }
3585 +
3586 + return $term_id;
3587 + }
3588 +
3589 + private function get_custom_fields_taxonomy_for_section( $section ) {
3590 + $taxonomies = array(
3591 + 'availability' => 'availability',
3592 + 'property-type' => 'property_type',
3593 + 'commercial-property-type' => 'commercial_property_type',
3594 + 'location' => 'location',
3595 + 'parking' => 'parking',
3596 + 'outside-space' => 'outside_space',
3597 + 'price-qualifier' => 'price_qualifier',
3598 + 'sale-by' => 'sale_by',
3599 + 'tenure' => 'tenure',
3600 + 'commercial-tenure' => 'commercial_tenure',
3601 + 'furnished' => 'furnished',
3602 + 'management-key-date-type' => 'management_key_date_type',
3603 + 'marketing-flag' => 'marketing_flag',
3604 + 'property-feature' => 'property_feature',
3605 + );
3606 +
3607 + if ( isset( $taxonomies[ $section ] ) ) {
3608 + return $taxonomies[ $section ];
3609 + }
3610 +
3611 + if ( is_string( $section ) && '-delete' === substr( $section, -7 ) ) {
3612 + $base_section = substr( $section, 0, -7 );
3613 + return isset( $taxonomies[ $base_section ] ) ? $taxonomies[ $base_section ] : '';
3614 + }
3615 +
3616 + return '';
3617 + }
3618 +
3363 3619 /**
3364 - * Save settings
3620 + * Save settings.
3365 3621 */
3366 3622 public function save() {
3623 + if ( ! current_user_can( 'manage_options' ) || ! isset( $_REQUEST['_wpnonce'] ) || ! is_string( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'propertyhive-settings' ) ) {
3624 + return;
3625 + }
3626 +
3367 3627 global $current_section, $post;
3368 3628
3369 - if ( $current_section != '' )
3370 - {
3371 - switch ($current_section)
3372 - {
3373 - case "addadditionalfield":
3374 - case "editadditionalfield":
3375 - {
3376 - $current_settings = get_option( 'propertyhive_template_assistant', array() );
3629 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Existing shared settings-router global; normalization preserves the public settings page contract.
3630 + $current_section = is_string( $current_section ) ? $current_section : '';
3631 + $request_id_present = isset( $_REQUEST['id'] ) && is_string( $_REQUEST['id'] );
3632 + $request_id = $request_id_present ? sanitize_text_field( wp_unslash( $_REQUEST['id'] ) ) : '';
3633 + $post_data = is_array( $_POST ) ? wp_unslash( $_POST ) : array();
3377 3634
3378 - $current_id = ( !isset( $_REQUEST['id'] ) ) ? '' : sanitize_title( $_REQUEST['id'] );
3635 + if ( '' === $current_section ) {
3636 + return;
3637 + }
3379 3638
3380 - $existing_custom_fields = ( (isset($current_settings['custom_fields'])) ? $current_settings['custom_fields'] : array() );
3639 + switch ( $current_section ) {
3640 + case 'addadditionalfield':
3641 + case 'editadditionalfield':
3642 + $current_settings = get_option( 'propertyhive_template_assistant', array() );
3643 + if ( ! is_array( $current_settings ) ) {
3644 + $current_settings = array();
3645 + }
3381 3646
3382 - if ( $current_section == 'editadditionalfield' && $current_id != 'default' && !isset($existing_custom_fields[$current_id]) )
3383 - {
3384 - die("Trying to edit a non-existant custom field. Please go back and try again");
3385 - }
3647 + $current_id = $request_id_present ? sanitize_title( $request_id ) : '';
3648 + $existing_custom_fields = ( isset( $current_settings['custom_fields'] ) && is_array( $current_settings['custom_fields'] ) ) ? $current_settings['custom_fields'] : array();
3386 3649
3387 - $field_name = trim( ( ( isset($_POST['field_name']) ) ? sanitize_title( $_POST['field_name'] ) : '' ) );
3650 + if ( 'editadditionalfield' === $current_section && 'default' !== $current_id && ! isset( $existing_custom_fields[ $current_id ] ) ) {
3651 + die( 'Trying to edit a non-existant custom field. Please go back and try again' );
3652 + }
3388 3653
3389 - if ( $field_name == '' )
3390 - {
3391 - $field_name = str_replace("-", "_", sanitize_title( $_POST['field_label'] ) );
3392 - }
3654 + $field_label = ( isset( $post_data['field_label'] ) && is_string( $post_data['field_label'] ) ) ? sanitize_text_field( $post_data['field_label'] ) : '';
3655 + $field_name = ( isset( $post_data['field_name'] ) && is_string( $post_data['field_name'] ) ) ? sanitize_title( $post_data['field_name'] ) : '';
3656 + if ( '' === trim( $field_name ) ) {
3657 + $field_name = str_replace( '-', '_', sanitize_title( $field_label ) );
3658 + }
3659 + $field_name = '_' . ltrim( $field_name, '_' );
3393 3660
3394 - $field_name = '_' . ltrim( $field_name, '_' );
3661 + $field_type = $this->normalize_custom_fields_field_type( isset( $post_data['field_type'] ) ? $post_data['field_type'] : '' );
3662 + $dropdown_options = ( in_array( $field_type, array( 'select', 'multiselect' ), true ) && isset( $post_data['dropdown_options'] ) ) ? $this->normalize_custom_fields_dropdown_options( $post_data['dropdown_options'] ) : '';
3663 + $field_settings = array(
3664 + 'field_label' => $field_label,
3665 + 'field_name' => $field_name,
3666 + 'field_type' => $field_type,
3667 + 'dropdown_options' => $dropdown_options,
3668 + 'meta_box' => $this->normalize_custom_fields_meta_box( isset( $post_data['meta_box'] ) ? $post_data['meta_box'] : '' ),
3669 + 'display_on_website' => $this->normalize_custom_fields_checkbox( isset( $post_data['display_on_website'] ) ? $post_data['display_on_website'] : '' ),
3670 + 'display_on_applicant_requirements' => $this->normalize_custom_fields_checkbox( isset( $post_data['display_on_applicant_requirements'] ) ? $post_data['display_on_applicant_requirements'] : '' ),
3671 + 'exact_match' => $this->normalize_custom_fields_checkbox( isset( $post_data['exact_match'] ) ? $post_data['exact_match'] : '' ),
3672 + 'display_on_user_details' => $this->normalize_custom_fields_checkbox( isset( $post_data['display_on_user_details'] ) ? $post_data['display_on_user_details'] : '' ),
3673 + 'admin_list' => $this->normalize_custom_fields_checkbox( isset( $post_data['admin_list'] ) ? $post_data['admin_list'] : '' ),
3674 + 'admin_list_sortable' => $this->normalize_custom_fields_checkbox( isset( $post_data['admin_list_sortable'] ) ? $post_data['admin_list_sortable'] : '' ),
3675 + );
3395 3676
3396 - if ( $current_section == 'addadditionalfield' )
3397 - {
3398 - $existing_custom_fields[] = array(
3399 - 'field_label' => sanitize_text_field(wp_unslash($_POST['field_label'])),
3400 - 'field_name' => $field_name,
3401 - 'field_type' => ( ( isset($_POST['field_type']) && $_POST['field_type'] != '' ) ? sanitize_text_field($_POST['field_type']) : 'text' ),
3402 - 'dropdown_options' => ( ( isset($_POST['field_type']) && ( $_POST['field_type'] == 'select' || $_POST['field_type'] == 'multiselect' ) && isset($_POST['dropdown_options']) ) ? $_POST['dropdown_options'] : '' ),
3403 - 'meta_box' => sanitize_text_field($_POST['meta_box']),
3404 - 'display_on_website' => ( ( isset($_POST['display_on_website']) ) ? sanitize_text_field($_POST['display_on_website']) : '' ),
3405 - 'display_on_applicant_requirements' => ( ( isset($_POST['display_on_applicant_requirements']) ) ? sanitize_text_field($_POST['display_on_applicant_requirements']) : '' ),
3406 - 'exact_match' => ( ( isset($_POST['exact_match']) ) ? sanitize_text_field($_POST['exact_match']) : '' ),
3407 - 'display_on_user_details' => ( ( isset($_POST['display_on_user_details']) ) ? sanitize_text_field($_POST['display_on_user_details']) : '' ),
3408 - 'admin_list' => ( ( isset($_POST['admin_list']) ) ? sanitize_text_field($_POST['admin_list']) : '' ),
3409 - 'admin_list_sortable' => ( ( isset($_POST['admin_list_sortable']) ) ? sanitize_text_field($_POST['admin_list_sortable']) : '' ),
3410 - );
3411 - }
3412 - else
3413 - {
3414 - $existing_custom_fields[$current_id] = array(
3415 - 'field_label' => sanitize_text_field(wp_unslash($_POST['field_label'])),
3416 - 'field_name' => $field_name,
3417 - 'field_type' => ( ( isset($_POST['field_type']) && $_POST['field_type'] != '' ) ? sanitize_text_field($_POST['field_type']) : 'text' ),
3418 - 'dropdown_options' => ( ( isset($_POST['field_type']) && ( $_POST['field_type'] == 'select' || $_POST['field_type'] == 'multiselect' ) && isset($_POST['dropdown_options']) ) ? $_POST['dropdown_options'] : '' ),
3419 - 'meta_box' => sanitize_text_field($_POST['meta_box']),
3420 - 'display_on_website' => ( ( isset($_POST['display_on_website']) ) ? sanitize_text_field($_POST['display_on_website']) : '' ),
3421 - 'display_on_applicant_requirements' => ( ( isset($_POST['display_on_applicant_requirements']) ) ? sanitize_text_field($_POST['display_on_applicant_requirements']) : '' ),
3422 - 'exact_match' => ( ( isset($_POST['exact_match']) ) ? sanitize_text_field($_POST['exact_match']) : '' ),
3423 - 'display_on_user_details' => ( ( isset($_POST['display_on_user_details']) ) ? sanitize_text_field($_POST['display_on_user_details']) : '' ),
3424 - 'admin_list' => ( ( isset($_POST['admin_list']) ) ? sanitize_text_field($_POST['admin_list']) : '' ),
3425 - 'admin_list_sortable' => ( ( isset($_POST['admin_list_sortable']) ) ? sanitize_text_field($_POST['admin_list_sortable']) : '' ),
3426 - );
3427 - }
3677 + if ( 'addadditionalfield' === $current_section ) {
3678 + $existing_custom_fields[] = $field_settings;
3679 + } else {
3680 + $existing_custom_fields[ $current_id ] = $field_settings;
3681 + }
3428 3682
3429 - $current_settings['custom_fields'] = $existing_custom_fields;
3683 + $current_settings['custom_fields'] = $existing_custom_fields;
3430 3684
3431 - // see if this custom field in used in search forms and amend the type accordingly
3432 - if ( $current_section != 'addadditionalfield' )
3433 - {
3434 - if ( isset($current_settings['search_forms']) && !empty($current_settings['search_forms']) )
3435 - {
3436 - foreach ( $current_settings['search_forms'] as $search_form_id => $search_form )
3437 - {
3438 - // Active fields
3439 - if ( isset($search_form['active_fields']) && !empty($search_form['active_fields']) )
3440 - {
3441 - foreach ( $search_form['active_fields'] as $field_id => $field_data )
3442 - {
3443 - if ( $field_name == $field_id )
3444 - {
3445 - // we found this field. Set type
3446 - $current_settings['search_forms'][$search_form_id]['active_fields'][$field_id]['type'] = ( ( isset($_POST['field_type']) && $_POST['field_type'] != '' ) ? sanitize_text_field($_POST['field_type']) : 'text' );
3447 - }
3448 - }
3685 + if ( 'addadditionalfield' !== $current_section && ! empty( $current_settings['search_forms'] ) && is_array( $current_settings['search_forms'] ) ) {
3686 + foreach ( $current_settings['search_forms'] as $search_form_id => $search_form ) {
3687 + if ( isset( $search_form['active_fields'] ) && is_array( $search_form['active_fields'] ) ) {
3688 + foreach ( $search_form['active_fields'] as $field_id => $field_data ) {
3689 + if ( $field_name === $field_id ) {
3690 + $current_settings['search_forms'][ $search_form_id ]['active_fields'][ $field_id ]['type'] = $field_type;
3449 3691 }
3692 + }
3693 + }
3450 3694
3451 - // Inactive fields
3452 - if ( isset($search_form['inactive_fields']) && !empty($search_form['inactive_fields']) )
3453 - {
3454 - foreach ( $search_form['inactive_fields'] as $field_id => $field_data )
3455 - {
3456 - if ( $field_name == $field_id )
3457 - {
3458 - // we found this field. Set type
3459 - $current_settings['search_forms'][$search_form_id]['inactive_fields'][$field_id]['type'] = ( ( isset($_POST['field_type']) && $_POST['field_type'] != '' ) ? sanitize_text_field($_POST['field_type']) : 'text' );
3460 - }
3461 - }
3695 + if ( isset( $search_form['inactive_fields'] ) && is_array( $search_form['inactive_fields'] ) ) {
3696 + foreach ( $search_form['inactive_fields'] as $field_id => $field_data ) {
3697 + if ( $field_name === $field_id ) {
3698 + $current_settings['search_forms'][ $search_form_id ]['inactive_fields'][ $field_id ]['type'] = $field_type;
3462 3699 }
3463 3700 }
3464 3701 }
3465 3702 }
3703 + }
3466 3704
3467 - update_option( 'propertyhive_template_assistant', $current_settings );
3705 + update_option( 'propertyhive_template_assistant', $current_settings );
3706 + break;
3468 3707
3469 - break;
3708 + default:
3709 + if ( ! $request_id_present ) {
3710 + break;
3470 3711 }
3471 - default:
3472 - {
3473 - if (isset($_REQUEST['id'])) // we're either adding or editing
3474 - {
3475 - $current_id = empty( $_REQUEST['id'] ) ? '' : sanitize_text_field($_REQUEST['id']);
3476 -
3477 - switch ($current_section)
3478 - {
3479 - // With heirarchy
3480 - case "property-type":
3481 - case "commercial-property-type":
3482 - case "location":
3483 - {
3484 - // TODO: Validate (check for blank fields)
3485 -
3486 - if ($current_id == '')
3487 - {
3488 - // Adding new term
3489 -
3490 - // TODO: Check term doesn't exist already
3491 -
3492 - wp_insert_term(
3493 - ph_clean($_POST[ph_clean($_POST['taxonomy']) . '_name']), // the term
3494 - ph_clean($_POST['taxonomy']), // the taxonomy
3495 - array(
3496 - 'parent' => $_POST['parent_' . ph_clean($_POST['taxonomy']) . '_id']
3497 - )
3498 - );
3499 -
3500 - // TODO: Check for errors returned from wp_insert_term()
3501 - }
3502 - else
3503 - {
3504 - // Editing term
3505 - wp_update_term($current_id, ph_clean($_POST['taxonomy']), array(
3506 - 'name' => ph_clean($_POST[ph_clean($_POST['taxonomy']).'_name']),
3507 - 'parent' => ph_clean($_POST['parent_' . $_POST['taxonomy'] . '_id'])
3508 - ));
3509 -
3510 - // TODO: Check for errors returned from wp_update_term()
3511 - }
3512 - break;
3513 - }
3514 - // Without heirarchy
3515 - case "availability":
3516 - case "outside-space":
3517 - case "parking":
3518 - case "price-qualifier":
3519 - case "sale-by":
3520 - case "tenure":
3521 - case "commercial-tenure":
3522 - case "furnished":
3523 - case "management-key-date-type":
3524 - case "marketing-flag":
3525 - case "property-feature":
3526 - {
3527 - // TODO: Validate (check for blank fields)
3528 -
3529 - if ($current_id == '')
3530 - {
3531 - // Adding new term
3532 -
3533 - // TODO: Check term doesn't exist already
3534 -
3535 - $term = wp_insert_term(
3536 - ph_clean($_POST[ph_clean($_POST['taxonomy']) . '_name']), // the term
3537 - $_POST['taxonomy'] // the taxonomy
3538 - );
3539 -
3540 - // TODO: Check for errors returned from wp_insert_term()
3541 3712
3542 - if ( ! is_wp_error( $term ) )
3543 - {
3544 - $current_id = isset( $term['term_id'] ) ? $term['term_id'] : 0;
3545 - }
3546 - }
3547 - else
3548 - {
3549 - // Editing term
3550 - wp_update_term($current_id, ph_clean($_POST['taxonomy']), array(
3551 - 'name' => ph_clean($_POST[ph_clean($_POST['taxonomy']) . '_name'])
3552 - ));
3553 -
3554 - // TODO: Check for errors returned from wp_update_term()
3555 - }
3713 + $taxonomy = $this->get_custom_fields_taxonomy_for_section( $current_section );
3714 + $is_delete_section = '-delete' === substr( $current_section, -7 );
3556 3715
3557 - if ( $current_section == 'availability' )
3558 - {
3559 - $availability_departments = get_option( 'propertyhive_availability_departments', array() );
3560 - if ( !is_array($availability_departments) ) { $availability_departments = array(); }
3716 + if ( '' !== $taxonomy ) {
3717 + if ( $is_delete_section ) {
3718 + $term_ids = $this->normalize_custom_fields_term_id_list( $request_id );
3719 + if ( empty( $term_ids ) ) {
3720 + return;
3721 + }
3722 + $current_id = implode( '-', $term_ids );
3723 + } else {
3724 + $current_id = $this->normalize_custom_fields_term_id( $request_id );
3725 + if ( null === $current_id ) {
3726 + return;
3727 + }
3728 + }
3729 + } else {
3730 + $current_id = sanitize_text_field( $request_id );
3731 + }
3561 3732
3562 - $departments = ph_get_departments();
3733 + switch ( $current_section ) {
3734 + case 'property-type':
3735 + case 'commercial-property-type':
3736 + case 'location':
3737 + $term_name_key = $taxonomy . '_name';
3738 + $parent_key = 'parent_' . $taxonomy . '_id';
3739 + $term_name = ( isset( $post_data[ $term_name_key ] ) && is_string( $post_data[ $term_name_key ] ) ) ? sanitize_text_field( $post_data[ $term_name_key ] ) : '';
3740 + $parent_id = $this->normalize_custom_fields_parent_id( isset( $post_data[ $parent_key ] ) ? $post_data[ $parent_key ] : '' );
3741 + if ( '' === $term_name || null === $parent_id ) {
3742 + return;
3743 + }
3563 3744
3564 - $availability_departments[$current_id] = array();
3565 - foreach ( $departments as $key => $value )
3566 - {
3567 - if ( isset($_POST['department'][$key]) && $_POST['department'][$key] == '1' )
3568 - {
3569 - $availability_departments[$current_id][] = $key;
3570 - }
3571 - }
3745 + if ( '' === $current_id ) {
3746 + wp_insert_term(
3747 + $term_name,
3748 + $taxonomy,
3749 + array( 'parent' => $parent_id )
3750 + );
3751 + } else {
3752 + wp_update_term(
3753 + absint( $current_id ),
3754 + $taxonomy,
3755 + array(
3756 + 'name' => $term_name,
3757 + 'parent' => $parent_id,
3758 + )
3759 + );
3760 + }
3761 + break;
3572 3762
3573 - update_option( 'propertyhive_availability_departments', $availability_departments );
3763 + case 'availability':
3764 + case 'outside-space':
3765 + case 'parking':
3766 + case 'price-qualifier':
3767 + case 'sale-by':
3768 + case 'tenure':
3769 + case 'commercial-tenure':
3770 + case 'furnished':
3771 + case 'management-key-date-type':
3772 + case 'marketing-flag':
3773 + case 'property-feature':
3774 + $term_name_key = $taxonomy . '_name';
3775 + $term_name = ( isset( $post_data[ $term_name_key ] ) && is_string( $post_data[ $term_name_key ] ) ) ? sanitize_text_field( $post_data[ $term_name_key ] ) : '';
3776 + if ( '' === $term_name ) {
3777 + return;
3778 + }
3779 +
3780 + if ( '' === $current_id ) {
3781 + $term = wp_insert_term( $term_name, $taxonomy );
3782 + if ( ! is_wp_error( $term ) ) {
3783 + $current_id = isset( $term['term_id'] ) ? (string) absint( $term['term_id'] ) : '0';
3784 + }
3785 + } else {
3786 + wp_update_term( absint( $current_id ), $taxonomy, array( 'name' => $term_name ) );
3787 + }
3788 +
3789 + if ( 'availability' === $current_section ) {
3790 + $availability_departments = get_option( 'propertyhive_availability_departments', array() );
3791 + if ( ! is_array( $availability_departments ) ) {
3792 + $availability_departments = array();
3793 + }
3794 +
3795 + $departments = ph_get_departments();
3796 + $posted_departments = ( isset( $post_data['department'] ) && is_array( $post_data['department'] ) ) ? $post_data['department'] : array();
3797 + $availability_departments[ $current_id ] = array();
3798 + foreach ( $departments as $key => $value ) {
3799 + if ( isset( $posted_departments[ $key ] ) && is_string( $posted_departments[ $key ] ) && '1' === $posted_departments[ $key ] ) {
3800 + $availability_departments[ $current_id ][] = $key;
3574 3801 }
3802 + }
3803 + update_option( 'propertyhive_availability_departments', $availability_departments );
3804 + }
3575 3805
3576 - if ( $current_section == 'management-key-date-type' )
3577 - {
3578 - $options = get_option( 'propertyhive_key_date_type', array() );
3579 - if ( !is_array($options) ) { $options = array(); }
3806 + if ( 'management-key-date-type' === $current_section ) {
3807 + $options = get_option( 'propertyhive_key_date_type', array() );
3808 + if ( ! is_array( $options ) ) {
3809 + $options = array();
3810 + }
3580 3811
3581 - $recurrence = array();
3582 - if (isset($_POST['management_key_date_type_recurrence_freq'])) {
3583 - $recurrence[] = 'FREQ=' . $_POST['management_key_date_type_recurrence_freq'];
3584 - }
3585 - if (isset($_POST['management_key_date_type_recurrence_interval'])) {
3586 - $recurrence[] = 'INTERVAL=' . $_POST['management_key_date_type_recurrence_interval'];
3587 - }
3812 + $recurrence = array();
3813 + $frequency = ( isset( $post_data['management_key_date_type_recurrence_freq'] ) && is_string( $post_data['management_key_date_type_recurrence_freq'] ) ) ? strtoupper( sanitize_text_field( $post_data['management_key_date_type_recurrence_freq'] ) ) : '';
3814 + if ( in_array( $frequency, array( 'ONCE', 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY' ), true ) ) {
3815 + $recurrence[] = 'FREQ=' . $frequency;
3816 + }
3588 3817
3589 - $options[$current_id]['recurrence_rule'] = join(';', $recurrence);
3590 - $options[$current_id]['recurrence_type'] = $_POST['management_key_date_type_recurrence_type'];
3591 - update_option( 'propertyhive_key_date_type', $options );
3592 - }
3818 + $interval = ( isset( $post_data['management_key_date_type_recurrence_interval'] ) && is_string( $post_data['management_key_date_type_recurrence_interval'] ) ) ? trim( $post_data['management_key_date_type_recurrence_interval'] ) : '';
3819 + if ( '' !== $interval && preg_match( '/^[1-9][0-9]*$/D', $interval ) ) {
3820 + $recurrence[] = 'INTERVAL=' . absint( $interval );
3821 + }
3593 3822
3594 - break;
3823 + $recurrence_type = ( isset( $post_data['management_key_date_type_recurrence_type'] ) && is_string( $post_data['management_key_date_type_recurrence_type'] ) ) ? sanitize_key( $post_data['management_key_date_type_recurrence_type'] ) : '';
3824 + if ( ! in_array( $recurrence_type, array( 'tenancy_management', 'property_management' ), true ) ) {
3825 + $recurrence_type = '';
3595 3826 }
3596 - case "availability-delete":
3597 - case "property-type-delete":
3598 - case "commercial-property-type-delete":
3599 - case "location-delete":
3600 - case "parking-delete":
3601 - case "outside-space-delete":
3602 - case "price-qualifier-delete":
3603 - case "sale-by-delete":
3604 - case "tenure-delete":
3605 - case "commercial-tenure-delete":
3606 - case "furnished-delete":
3607 - case "management-key-date-type-delete":
3608 - case "marketing-flag-delete":
3609 - case "property-feature-delete":
3610 - {
3611 - if ( isset($_POST['confirm_removal']) && $_POST['confirm_removal'] == '1' )
3612 - {
3613 - $term_ids = explode("-", $current_id);
3614 3827
3615 - foreach ( $term_ids as $current_id )
3616 - {
3617 - // Update properties that have this taxonomy term set
3618 - $query_args = array(
3619 - 'post_type' => 'property',
3620 - 'nopaging' => true,
3621 - 'post_status' => array( 'pending', 'auto-draft', 'draft', 'private', 'publish', 'future', 'trash' ),
3622 - 'tax_query' => array(
3623 - array(
3624 - 'taxonomy' => $_POST['taxonomy'],
3625 - 'field' => 'id',
3626 - 'terms' => $current_id,
3627 - ),
3628 - ),
3629 - );
3630 - $property_query = new WP_Query( $query_args );
3631 -
3632 - if ( $property_query->have_posts() )
3633 - {
3634 - while ( $property_query->have_posts() )
3635 - {
3636 - $property_query->the_post();
3637 -
3638 - wp_remove_object_terms( $post->ID, $current_id, ph_clean($_POST['taxonomy']) );
3639 -
3640 - // Re-assign to another term
3641 - if ( isset($_POST['reassign_to_' . $current_id]) && ! empty( $_POST['reassign_to_' . $current_id] ) && $_POST['reassign_to_' . $current_id] != 'none' )
3642 - {
3643 - $new_id = $_POST['reassign_to_' . $current_id];
3644 -
3645 - wp_set_post_terms( $post->ID, $new_id, ph_clean($_POST['taxonomy']), TRUE );
3646 -
3647 - // TODO: Check for WP_ERROR
3648 - }
3649 - }
3650 - }
3651 -
3652 - wp_reset_postdata();
3828 + $options[ $current_id ]['recurrence_rule'] = join( ';', $recurrence );
3829 + $options[ $current_id ]['recurrence_type'] = $recurrence_type;
3830 + update_option( 'propertyhive_key_date_type', $options );
3831 + }
3832 + break;
3653 3833
3654 - if ( $current_section == 'availability-delete' )
3655 - {
3656 - // Remove from propertyhive_availability_departments option
3657 - $availability_departments = get_option( 'propertyhive_availability_departments', array() );
3834 + case 'availability-delete':
3835 + case 'property-type-delete':
3836 + case 'commercial-property-type-delete':
3837 + case 'location-delete':
3838 + case 'parking-delete':
3839 + case 'outside-space-delete':
3840 + case 'price-qualifier-delete':
3841 + case 'sale-by-delete':
3842 + case 'tenure-delete':
3843 + case 'commercial-tenure-delete':
3844 + case 'furnished-delete':
3845 + case 'management-key-date-type-delete':
3846 + case 'marketing-flag-delete':
3847 + case 'property-feature-delete':
3848 + if ( ! isset( $post_data['confirm_removal'] ) || ! is_string( $post_data['confirm_removal'] ) || '1' !== $post_data['confirm_removal'] ) {
3849 + break;
3850 + }
3658 3851
3659 - if ( isset($availability_departments[$current_id]) )
3660 - {
3661 - unset($availability_departments[$current_id]);
3662 - update_option( 'propertyhive_availability_departments', $availability_departments );
3663 - }
3664 - }
3852 + foreach ( $term_ids as $current_id ) {
3853 + $query_args = array(
3854 + 'post_type' => 'property',
3855 + 'nopaging' => true,
3856 + 'post_status' => array( 'pending', 'auto-draft', 'draft', 'private', 'publish', 'future', 'trash' ),
3857 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Term deletion/reassignment must locate all properties or key dates attached to this selected term before removing it.
3858 + 'tax_query' => array(
3859 + array(
3860 + 'taxonomy' => $taxonomy,
3861 + 'field' => 'id',
3862 + 'terms' => $current_id,
3863 + ),
3864 + ),
3865 + );
3866 + $property_query = new WP_Query( $query_args );
3665 3867
3666 - if ( $_POST['taxonomy'] == 'property_type' || $_POST['taxonomy'] == 'commercial_property_type' || $_POST['taxonomy'] == 'location' )
3667 - {
3668 - $query_args = array(
3669 - 'post_type' => 'contact',
3670 - 'nopaging' => true,
3671 - 'post_status' => array( 'pending', 'auto-draft', 'draft', 'private', 'publish', 'future', 'trash' ),
3672 - 'meta_query' => array(
3673 - array(
3674 - 'key' => '_contact_types',
3675 - 'value' => 'applicant',
3676 - 'compare' => 'LIKE'
3677 - ),
3678 - ),
3679 - );
3680 - $applicant_query = new WP_Query( $query_args );
3868 + if ( $property_query->have_posts() ) {
3869 + while ( $property_query->have_posts() ) {
3870 + $property_query->the_post();
3871 + wp_remove_object_terms( $post->ID, $current_id, $taxonomy );
3681 3872
3682 - if ( $applicant_query->have_posts() )
3683 - {
3684 - while ( $applicant_query->have_posts() )
3685 - {
3686 - $applicant_query->the_post();
3687 -
3688 - $num_applicant_profiles = get_post_meta( get_the_ID(), '_applicant_profiles', TRUE );
3689 - if ( $num_applicant_profiles == '' )
3690 - {
3691 - $num_applicant_profiles = 0;
3692 - }
3873 + $new_id = $this->normalize_custom_fields_reassignment( isset( $post_data[ 'reassign_to_' . $current_id ] ) ? $post_data[ 'reassign_to_' . $current_id ] : '', $term_ids );
3874 + if ( null !== $new_id ) {
3875 + wp_set_post_terms( $post->ID, $new_id, $taxonomy, true );
3876 + }
3877 + }
3878 + }
3879 + wp_reset_postdata();
3693 3880
3694 - if ( $num_applicant_profiles > 0 )
3695 - {
3696 - for ( $i = 0; $i < $num_applicant_profiles; ++$i )
3697 - {
3698 - $applicant_profile = get_post_meta( get_the_ID(), '_applicant_profile_' . $i, TRUE );
3881 + if ( 'availability-delete' === $current_section ) {
3882 + $availability_departments = get_option( 'propertyhive_availability_departments', array() );
3883 + if ( is_array( $availability_departments ) && isset( $availability_departments[ $current_id ] ) ) {
3884 + unset( $availability_departments[ $current_id ] );
3885 + update_option( 'propertyhive_availability_departments', $availability_departments );
3886 + }
3887 + }
3699 3888
3700 - if ( isset($applicant_profile[ph_clean($_POST['taxonomy']).'s']) && is_array($applicant_profile[ph_clean($_POST['taxonomy']).'s']) && !empty($applicant_profile[ph_clean($_POST['taxonomy']).'s']) )
3701 - {
3702 - if (in_array($current_id, $applicant_profile[ph_clean($_POST['taxonomy']).'s']))
3703 - {
3704 - // This profile has this term set
3705 - unset($applicant_profile[ph_clean($_POST['taxonomy']).'s'][$current_id]);
3889 + if ( in_array( $taxonomy, array( 'property_type', 'commercial_property_type', 'location' ), true ) ) {
3890 + $query_args = array(
3891 + 'post_type' => 'contact',
3892 + 'nopaging' => true,
3893 + 'post_status' => array( 'pending', 'auto-draft', 'draft', 'private', 'publish', 'future', 'trash' ),
3894 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Applicant preferences are serialized profile metadata; deletion/reassignment must inspect every applicant profile to preserve its other selections.
3895 + 'meta_query' => array(
3896 + array(
3897 + 'key' => '_contact_types',
3898 + 'value' => 'applicant',
3899 + 'compare' => 'LIKE',
3900 + ),
3901 + ),
3902 + );
3903 + $applicant_query = new WP_Query( $query_args );
3706 3904
3707 - if ( isset($_POST['reassign_to_' . $current_id]) && ! empty( $_POST['reassign_to_' . $current_id] ) && ph_clean($_POST['reassign_to_' . $current_id]) != 'none' )
3708 - {
3709 - $applicant_profile[ph_clean($_POST['taxonomy']).'s'][] = $_POST['reassign_to_' . $current_id];
3710 - $applicant_profile[ph_clean($_POST['taxonomy']).'s'] = array_unique($applicant_profile[ph_clean($_POST['taxonomy']).'s']);
3711 - }
3905 + if ( $applicant_query->have_posts() ) {
3906 + while ( $applicant_query->have_posts() ) {
3907 + $applicant_query->the_post();
3908 + $num_applicant_profiles = get_post_meta( get_the_ID(), '_applicant_profiles', true );
3909 + if ( '' === $num_applicant_profiles ) {
3910 + $num_applicant_profiles = 0;
3911 + }
3712 3912
3713 - $applicant_profile[ph_clean($_POST['taxonomy']).'s'] = array_values($applicant_profile[ph_clean($_POST['taxonomy']).'s']);
3913 + if ( $num_applicant_profiles > 0 ) {
3914 + for ( $i = 0; $i < $num_applicant_profiles; ++$i ) {
3915 + $applicant_profile = get_post_meta( get_the_ID(), '_applicant_profile_' . $i, true );
3916 + $profile_key = $taxonomy . 's';
3917 + if ( ! is_array( $applicant_profile ) || ! isset( $applicant_profile[ $profile_key ] ) || ! is_array( $applicant_profile[ $profile_key ] ) ) {
3918 + continue;
3919 + }
3714 3920
3715 - update_post_meta( get_the_ID(), '_applicant_profile_' . $i, $applicant_profile );
3716 - }
3717 - }
3718 - }
3921 + $profile_terms = array();
3922 + foreach ( $applicant_profile[ $profile_key ] as $profile_term_id ) {
3923 + if ( is_scalar( $profile_term_id ) ) {
3924 + $profile_terms[] = (string) absint( $profile_term_id );
3719 3925 }
3720 3926 }
3927 + if ( ! in_array( (string) $current_id, $profile_terms, true ) ) {
3928 + continue;
3929 + }
3930 +
3931 + $profile_terms = array_values( array_filter( $profile_terms, static function ( $profile_term_id ) use ( $current_id ) {
3932 + return (string) $profile_term_id !== (string) $current_id;
3933 + } ) );
3934 + $new_id = $this->normalize_custom_fields_reassignment( isset( $post_data[ 'reassign_to_' . $current_id ] ) ? $post_data[ 'reassign_to_' . $current_id ] : '', $term_ids );
3935 + if ( null !== $new_id ) {
3936 + $profile_terms[] = $new_id;
3937 + }
3938 + $applicant_profile[ $profile_key ] = array_values( array_unique( $profile_terms ) );
3939 + update_post_meta( get_the_ID(), '_applicant_profile_' . $i, wp_slash( $applicant_profile ) );
3721 3940 }
3722 3941 }
3723 -
3724 - wp_reset_postdata();
3725 -
3726 - wp_delete_term( $current_id, ph_clean($_POST['taxonomy']) );
3727 3942 }
3728 3943 }
3944 + wp_reset_postdata();
3945 + }
3729 3946
3730 - break;
3731 - }
3732 - default:
3733 - {
3734 - $section_found = apply_filters( 'propertyhive_custom_fields_save_section', false, $current_section, $current_id );
3947 + wp_delete_term( absint( $current_id ), $taxonomy );
3948 + }
3949 + break;
3735 3950
3736 - if ( !($section_found) )
3737 - {
3738 - echo 'UNKNOWN CUSTOM FIELD';
3739 - }
3740 - }
3951 + default:
3952 + $section_found = apply_filters( 'propertyhive_custom_fields_save_section', false, $current_section, $current_id );
3953 + if ( ! $section_found ) {
3954 + echo 'UNKNOWN CUSTOM FIELD';
3741 3955 }
3742 - }
3743 - else
3744 - {
3745 - // Nothing to save. Should always be an id set when editing custom fields.
3746 - // Even blank ids dictate something is being added
3747 - }
3956 + break;
3748 3957 }
3749 - }
3958 + break;
3750 3959 }
3751 - else
3752 - {
3753 - // Nothing to save. Should always be a section when editing custom fields
3754 - }
3755 3960 }
3756 3961 }
3757 3962
3758 3963 endif;
3759 3964
3760 -return new PH_Settings_Custom_Fields();
3965 +return new PH_Settings_Custom_Fields();