← All changes
|
includes/admin/class-ph-admin-applicant-list.php
+852
-306
1.4.55
→
2.3.0
View file →
| @@ -1,5 +1,8 @@ | ||
| 1 | - <?php | |
| 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 Admin Generate Applicant List Class. |
| 4 | 7 | * |
| 5 | 8 | * @author PropertyHive |
| @@ -14,8 +17,9 @@ | ||
| 14 | 17 | |
| 15 | 18 | /** |
| 16 | 19 | * PH_Admin_Applicant_List |
| 17 | 20 | */ |
| 21 | +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Legacy public global class PH_Admin_Applicant_List; preserving the existing PH_* class name is required for plugin and extension compatibility. | |
| 18 | 22 | class PH_Admin_Applicant_List { |
| 19 | 23 | |
| 20 | 24 | /** |
| 21 | 25 | * Handles the display of the main Property Hive reports page in admin. |
| @@ -22,11 +26,38 @@ | ||
| 22 | 26 | * |
| 23 | 27 | * @access public |
| 24 | 28 | * @return void |
| 25 | 29 | */ |
| 26 | - public static function output() { | |
| 30 | + public function output() { | |
| 27 | 31 | |
| 28 | - $property_types = array(); | |
| 32 | + // Applicant filters are read-only; the export endpoint verifies its nonce and capability. | |
| 33 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- This request only repopulates the read-only filter form and renders its results. | |
| 34 | + $request_post = wp_unslash( $_POST ); | |
| 35 | + $has_department_input = isset( $request_post['department'] ) && is_scalar( $request_post['department'] ); | |
| 36 | + $department_input = $has_department_input ? sanitize_text_field( $request_post['department'] ) : ''; | |
| 37 | + $maximum_price_input = ( isset( $request_post['maximum_price'] ) && is_scalar( $request_post['maximum_price'] ) ) ? sanitize_text_field( $request_post['maximum_price'] ) : ''; | |
| 38 | + $maximum_rent_input = ( isset( $request_post['maximum_rent'] ) && is_scalar( $request_post['maximum_rent'] ) ) ? sanitize_text_field( $request_post['maximum_rent'] ) : ''; | |
| 39 | + $minimum_bedrooms_input = ( isset( $request_post['minimum_bedrooms'] ) && is_scalar( $request_post['minimum_bedrooms'] ) ) ? sanitize_text_field( $request_post['minimum_bedrooms'] ) : ''; | |
| 40 | + $property_types_input = array(); | |
| 41 | + if ( isset( $request_post['property_types'] ) && is_array( $request_post['property_types'] ) ) { | |
| 42 | + foreach ( $request_post['property_types'] as $property_type_input ) { | |
| 43 | + if ( is_scalar( $property_type_input ) ) { | |
| 44 | + $property_types_input[] = absint( $property_type_input ); | |
| 45 | + } | |
| 46 | + } | |
| 47 | + } | |
| 48 | + $locations_input = array(); | |
| 49 | + if ( isset( $request_post['locations'] ) && is_array( $request_post['locations'] ) ) { | |
| 50 | + foreach ( $request_post['locations'] as $location_input ) { | |
| 51 | + if ( is_scalar( $location_input ) ) { | |
| 52 | + $locations_input[] = absint( $location_input ); | |
| 53 | + } | |
| 54 | + } | |
| 55 | + } | |
| 56 | + $include_non_send_matching_properties_input = ( isset( $request_post['include_non_send_matching_properties'] ) && is_scalar( $request_post['include_non_send_matching_properties'] ) ) ? sanitize_text_field( $request_post['include_non_send_matching_properties'] ) : ''; | |
| 57 | + $submitted_applicant_list = ( isset( $request_post['submitted_applicant_list'] ) && is_scalar( $request_post['submitted_applicant_list'] ) ) ? sanitize_key( $request_post['submitted_applicant_list'] ) : ''; | |
| 58 | + | |
| 59 | + $property_types = array(); | |
| 29 | 60 | $locations = array(); |
| 30 | 61 | ?> |
| 31 | 62 | <div class="wrap propertyhive"> |
| 32 | 63 | |
| @@ -31,16 +62,17 @@ | ||
| 31 | 62 | <div class="wrap propertyhive"> |
| 32 | 63 | |
| 33 | 64 | <h1>Generate Applicant List</h1> |
| 34 | 65 | |
| 35 | - <form method="post" id="mainform" action="" enctype="multipart/form-data" class="applicant-list-form"> | |
| 66 | + <form method="post" id="mainform" action="" class="applicant-list-form"> | |
| 36 | 67 | |
| 37 | - <input type="hidden" name="submitted" value="1"> | |
| 68 | + <input type="hidden" name="submitted_applicant_list" value="1"> | |
| 69 | + <?php wp_nonce_field( 'ph_applicant_export', 'ph_applicant_export_nonce' ); ?> | |
| 38 | 70 | |
| 39 | 71 | <div id="poststuff" class="propertyhive_meta_box"> |
| 40 | 72 | |
| 41 | 73 | <p class="form-field"> |
| 42 | - <label>Looking For</label> | |
| 74 | + <label><?php echo esc_html__( 'Looking For', 'propertyhive' ); ?></label> | |
| 43 | 75 | <select name="department"> |
| 44 | 76 | <?php |
| 45 | 77 | |
| 46 | 78 | $departments = ph_get_departments(); |
| @@ -55,18 +87,20 @@ | ||
| 55 | 87 | } |
| 56 | 88 | |
| 57 | 89 | foreach ( $department_options as $key => $department ) |
| 58 | 90 | { |
| 59 | - echo '<option value="' . $key . '"'; | |
| 60 | - if ( isset($_POST['department']) && $_POST['department'] == $key ) | |
| 91 | + echo '<option value="' . esc_attr($key) . '"'; | |
| 92 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 93 | + if ( $has_department_input && $department_input == $key ) | |
| 61 | 94 | { |
| 62 | 95 | echo ' selected'; |
| 63 | 96 | } |
| 64 | - elseif ( !isset($_POST['department']) && $key == get_option( 'propertyhive_primary_department' ) ) | |
| 97 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 98 | + elseif ( ! $has_department_input && $key == get_option( 'propertyhive_primary_department' ) ) | |
| 65 | 99 | { |
| 66 | 100 | echo ' selected'; |
| 67 | 101 | } |
| 68 | - echo '>' . $department . '</option>'; | |
| 102 | + echo '>' . esc_html($department) . '</option>'; | |
| 69 | 103 | } |
| 70 | 104 | ?> |
| 71 | 105 | |
| 72 | 106 | </select> |
| @@ -72,25 +106,31 @@ | ||
| 72 | 106 | </select> |
| 73 | 107 | </p> |
| 74 | 108 | |
| 75 | 109 | <p class="form-field sales-only"> |
| 76 | - <label>Price</label> | |
| 77 | - <input type="text" name="maximum_price" value="<?php if ( isset($_POST['maximum_price']) ) { echo esc_attr( $_POST['maximum_price'] ); } ?>"> | |
| 110 | + <label><?php echo esc_html__( 'Maximum Price', 'propertyhive' ); ?> <img class="help_tip" data-tip="This will search the applicant's Match Price Range if one is set and return applicants where the price entered falls into this range. Otherwise it will search the Maximum Price and return applicants that have maximum price higher than the value entered" src="<?php echo esc_url(PH()->plugin_url()); ?>/assets/images/help.png" height="16" width="16" /></label> | |
| 111 | + <input type="text" name="maximum_price" value="<?php | |
| 112 | +// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 113 | + if ( $maximum_price_input !== '' ) { echo esc_attr( $maximum_price_input ); } ?>"> | |
| 78 | 114 | </p> |
| 79 | 115 | |
| 80 | 116 | <p class="form-field lettings-only"> |
| 81 | - <label>Rent (PCM)</label> | |
| 82 | - <input type="text" name="maximum_rent" value="<?php if ( isset($_POST['maximum_rent']) ) { echo esc_attr( $_POST['maximum_rent'] ); } ?>"> | |
| 117 | + <label><?php echo esc_html__( 'Maximum Rent (PCM)', 'propertyhive' ); ?></label> | |
| 118 | + <input type="text" name="maximum_rent" value="<?php | |
| 119 | +// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 120 | + if ( $maximum_rent_input !== '' ) { echo esc_attr( $maximum_rent_input ); } ?>"> | |
| 83 | 121 | </p> |
| 84 | 122 | |
| 85 | 123 | <p class="form-field residential-only"> |
| 86 | - <label>Bedrooms</label> | |
| 87 | - <input type="number" name="minimum_bedrooms" class="short" value="<?php if ( isset($_POST['minimum_bedrooms']) ) { echo esc_attr( $_POST['minimum_bedrooms'] ); } ?>"> | |
| 124 | + <label><?php echo esc_html__( 'Minimum Bedrooms', 'propertyhive' ); ?></label> | |
| 125 | + <input type="number" name="minimum_bedrooms" class="short" value="<?php | |
| 126 | +// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 127 | + if ( $minimum_bedrooms_input !== '' ) { echo esc_attr( $minimum_bedrooms_input ); } ?>"> | |
| 88 | 128 | </p> |
| 89 | 129 | |
| 90 | 130 | <p class="form-field residential-only"> |
| 91 | - <label>Property Types</label> | |
| 92 | - <select id="property_types" name="property_types[]" multiple="multiple" data-placeholder="Start typing to add property types..." class="multiselect attribute_values"> | |
| 131 | + <label><?php echo esc_html__( 'Property Types', 'propertyhive' ); ?></label> | |
| 132 | + <select id="property_types" name="property_types[]" multiple="multiple" data-placeholder="<?php echo esc_attr__( 'Start typing to add property types', 'propertyhive' ); ?>..." class="multiselect attribute_values"> | |
| 93 | 133 | <?php |
| 94 | 134 | $options = array( '' => '' ); |
| 95 | 135 | $args = array( |
| 96 | 136 | 'hide_empty' => false, |
| @@ -95,18 +135,19 @@ | ||
| 95 | 135 | $args = array( |
| 96 | 136 | 'hide_empty' => false, |
| 97 | 137 | 'parent' => 0 |
| 98 | 138 | ); |
| 99 | - $terms = get_terms( 'property_type', $args ); | |
| 139 | + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_type' ) ) ); | |
| 100 | 140 | |
| 101 | 141 | if ( !empty( $terms ) && !is_wp_error( $terms ) ) |
| 102 | 142 | { |
| 103 | 143 | foreach ($terms as $term) |
| 104 | 144 | { |
| 105 | - $property_types[$term->term_id] = esc_html( $term->name ); | |
| 145 | + $property_types[$term->term_id] = $term->name; | |
| 106 | 146 | |
| 107 | 147 | echo '<option value="' . esc_attr( $term->term_id ) . '"'; |
| 108 | - if ( isset($_POST['property_types']) && in_array( $term->term_id, $_POST['property_types'] ) ) | |
| 148 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 149 | + if ( ! empty( $property_types_input ) && in_array( $term->term_id, $property_types_input ) ) | |
| 109 | 150 | { |
| 110 | 151 | echo ' selected'; |
| 111 | 152 | } |
| 112 | 153 | echo '>' . esc_html( $term->name ) . '</option>'; |
| @@ -114,18 +155,19 @@ | ||
| 114 | 155 | $args = array( |
| 115 | 156 | 'hide_empty' => false, |
| 116 | 157 | 'parent' => $term->term_id |
| 117 | 158 | ); |
| 118 | - $subterms = get_terms( 'property_type', $args ); | |
| 159 | + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_type' ) ) ); | |
| 119 | 160 | |
| 120 | 161 | if ( !empty( $subterms ) && !is_wp_error( $subterms ) ) |
| 121 | 162 | { |
| 122 | 163 | foreach ($subterms as $term) |
| 123 | 164 | { |
| 124 | - $property_types[$term->term_id] = esc_html( $term->name ); | |
| 165 | + $property_types[$term->term_id] = $term->name; | |
| 125 | 166 | |
| 126 | 167 | echo '<option value="' . esc_attr( $term->term_id ) . '"'; |
| 127 | - if ( isset($_POST['property_types']) && in_array( $term->term_id, $_POST['property_types'] ) ) | |
| 168 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 169 | + if ( ! empty( $property_types_input ) && in_array( $term->term_id, $property_types_input ) ) | |
| 128 | 170 | { |
| 129 | 171 | echo ' selected'; |
| 130 | 172 | } |
| 131 | 173 | echo '>- ' . esc_html( $term->name ) . '</option>'; |
| @@ -137,10 +179,10 @@ | ||
| 137 | 179 | </select> |
| 138 | 180 | </p> |
| 139 | 181 | |
| 140 | 182 | <p class="form-field"> |
| 141 | - <label>Location</label> | |
| 142 | - <select id="locations" name="locations[]" multiple="multiple" data-placeholder="Start typing to add locations..." class="multiselect attribute_values"> | |
| 183 | + <label><?php echo esc_html__( 'Location', 'propertyhive' ); ?></label> | |
| 184 | + <select id="locations" name="locations[]" multiple="multiple" data-placeholder="<?php echo esc_attr__( 'Start typing to add locations', 'propertyhive' ); ?>..." class="multiselect attribute_values"> | |
| 143 | 185 | <?php |
| 144 | 186 | $options = array( '' => '' ); |
| 145 | 187 | $args = array( |
| 146 | 188 | 'hide_empty' => false, |
| @@ -145,18 +187,19 @@ | ||
| 145 | 187 | $args = array( |
| 146 | 188 | 'hide_empty' => false, |
| 147 | 189 | 'parent' => 0 |
| 148 | 190 | ); |
| 149 | - $terms = get_terms( 'location', $args ); | |
| 191 | + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) ); | |
| 150 | 192 | |
| 151 | 193 | if ( !empty( $terms ) && !is_wp_error( $terms ) ) |
| 152 | 194 | { |
| 153 | 195 | foreach ($terms as $term) |
| 154 | 196 | { |
| 155 | - $locations[$term->term_id] = esc_html( $term->name ); | |
| 197 | + $locations[$term->term_id] = $term->name; | |
| 156 | 198 | |
| 157 | 199 | echo '<option value="' . esc_attr( $term->term_id ) . '"'; |
| 158 | - if ( isset($_POST['locations']) && in_array( $term->term_id, $_POST['locations'] ) ) | |
| 200 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 201 | + if ( ! empty( $locations_input ) && in_array( $term->term_id, $locations_input ) ) | |
| 159 | 202 | { |
| 160 | 203 | echo ' selected'; |
| 161 | 204 | } |
| 162 | 205 | echo '>' . esc_html( $term->name ) . '</option>'; |
| @@ -164,18 +207,19 @@ | ||
| 164 | 207 | $args = array( |
| 165 | 208 | 'hide_empty' => false, |
| 166 | 209 | 'parent' => $term->term_id |
| 167 | 210 | ); |
| 168 | - $subterms = get_terms( 'location', $args ); | |
| 211 | + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) ); | |
| 169 | 212 | |
| 170 | 213 | if ( !empty( $subterms ) && !is_wp_error( $subterms ) ) |
| 171 | 214 | { |
| 172 | 215 | foreach ($subterms as $term) |
| 173 | 216 | { |
| 174 | - $locations[$term->term_id] = esc_html( $term->name ); | |
| 217 | + $locations[$term->term_id] = $term->name; | |
| 175 | 218 | |
| 176 | 219 | echo '<option value="' . esc_attr( $term->term_id ) . '"'; |
| 177 | - if ( isset($_POST['locations']) && in_array( $term->term_id, $_POST['locations'] ) ) | |
| 220 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 221 | + if ( ! empty( $locations_input ) && in_array( $term->term_id, $locations_input ) ) | |
| 178 | 222 | { |
| 179 | 223 | echo ' selected'; |
| 180 | 224 | } |
| 181 | 225 | echo '>- ' . esc_html( $term->name ) . '</option>'; |
| @@ -183,18 +227,19 @@ | ||
| 183 | 227 | $args = array( |
| 184 | 228 | 'hide_empty' => false, |
| 185 | 229 | 'parent' => $term->term_id |
| 186 | 230 | ); |
| 187 | - $subsubterms = get_terms( 'location', $args ); | |
| 231 | + $subsubterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) ); | |
| 188 | 232 | |
| 189 | 233 | if ( !empty( $subsubterms ) && !is_wp_error( $subsubterms ) ) |
| 190 | 234 | { |
| 191 | 235 | foreach ($subsubterms as $term) |
| 192 | 236 | { |
| 193 | - $locations[$term->term_id] = esc_html( $term->name ); | |
| 237 | + $locations[$term->term_id] = $term->name; | |
| 194 | 238 | |
| 195 | 239 | echo '<option value="' . esc_attr( $term->term_id ) . '"'; |
| 196 | - if ( isset($_POST['locations']) && in_array( $term->term_id, $_POST['locations'] ) ) | |
| 240 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 241 | + if ( ! empty( $locations_input ) && in_array( $term->term_id, $locations_input ) ) | |
| 197 | 242 | { |
| 198 | 243 | echo ' selected'; |
| 199 | 244 | } |
| 200 | 245 | echo '>- - ' . esc_html( $term->name ) . '</option>'; |
| @@ -208,11 +253,22 @@ | ||
| 208 | 253 | </select> |
| 209 | 254 | </p> |
| 210 | 255 | |
| 211 | 256 | <p class="form-field"> |
| 212 | - <input type="submit" value="<?php echo __( 'Generate Applicant List', 'propertyhive' ); ?>" class="button-primary"> | |
| 257 | + <label><?php echo esc_html__( 'Include Applicants with \'Send Matching Properties\' Unticked', 'propertyhive' ); ?></label> | |
| 258 | + <input type="checkbox" name="include_non_send_matching_properties" value="yes"<?php | |
| 259 | +// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 260 | + if ( $include_non_send_matching_properties_input !== '' && sanitize_text_field($include_non_send_matching_properties_input) == 'yes' ) { echo ' checked'; } ?>> | |
| 213 | 261 | </p> |
| 214 | 262 | |
| 263 | + <?php do_action('propertyhive_applicant_list_additional_fields'); ?> | |
| 264 | + | |
| 265 | + <p class="form-field"> | |
| 266 | + <input type="submit" value="<?php echo esc_attr(__( 'Generate Applicant List', 'propertyhive' )); ?>" class="button-primary"> | |
| 267 | + <a href="" class="button" id="export_applicant_list_results_button"><?php echo esc_html__( 'Export To CSV', 'propertyhive' ); ?></a> | |
| 268 | + <input type="hidden" name="export_applicant_list_results" value=""> | |
| 269 | + </p> | |
| 270 | + | |
| 215 | 271 | </div> |
| 216 | 272 | |
| 217 | 273 | </form> |
| 218 | 274 | |
| @@ -218,243 +274,22 @@ | ||
| 218 | 274 | |
| 219 | 275 | <div class="applicant-list-results"> |
| 220 | 276 | |
| 221 | 277 | <?php |
| 222 | - if ( isset($_POST['submitted']) && $_POST['submitted'] == '1' ) | |
| 278 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 279 | + if ( $submitted_applicant_list !== '' && $submitted_applicant_list == '1' ) | |
| 223 | 280 | { |
| 224 | - $search_property_types = array(); | |
| 225 | - if ( isset($_POST['department']) && ( $_POST['department'] == 'residential-sales' || $_POST['department'] == 'residential-lettings' ) ) | |
| 226 | - { | |
| 227 | - if ( isset($_POST['property_types']) && is_array($_POST['property_types']) && !empty($_POST['property_types']) ) | |
| 228 | - { | |
| 229 | - foreach ( $_POST['property_types'] as $property_type ) | |
| 230 | - { | |
| 231 | - $search_property_types[] = (int)$property_type; | |
| 232 | - | |
| 233 | - $args = array( | |
| 234 | - 'hide_empty' => false, | |
| 235 | - 'parent' => $property_type | |
| 236 | - ); | |
| 237 | - $terms = get_terms( 'property_type', $args ); | |
| 238 | - | |
| 239 | - if ( !empty( $terms ) && !is_wp_error( $terms ) ) | |
| 240 | - { | |
| 241 | - foreach ($terms as $term) | |
| 242 | - { | |
| 243 | - $search_property_types[] = $term->term_id; | |
| 244 | - | |
| 245 | - $args = array( | |
| 246 | - 'hide_empty' => false, | |
| 247 | - 'parent' => $term->term_id | |
| 248 | - ); | |
| 249 | - $subterms = get_terms( 'property_type', $args ); | |
| 250 | - | |
| 251 | - if ( !empty( $subterms ) && !is_wp_error( $subterms ) ) | |
| 252 | - { | |
| 253 | - foreach ($subterms as $term) | |
| 254 | - { | |
| 255 | - $search_property_types[] = $term->term_id; | |
| 256 | - } | |
| 257 | - } | |
| 258 | - } | |
| 259 | - } | |
| 260 | - } | |
| 261 | - } | |
| 262 | - $search_property_types = array_unique($search_property_types); | |
| 263 | - } | |
| 264 | - | |
| 265 | - $search_locations = array(); | |
| 266 | - if ( isset($_POST['locations']) && is_array($_POST['locations']) && !empty($_POST['locations']) ) | |
| 267 | - { | |
| 268 | - foreach ( $_POST['locations'] as $location ) | |
| 269 | - { | |
| 270 | - $search_locations[] = (int)$location; | |
| 271 | - | |
| 272 | - $args = array( | |
| 273 | - 'hide_empty' => false, | |
| 274 | - 'parent' => $location | |
| 275 | - ); | |
| 276 | - $terms = get_terms( 'location', $args ); | |
| 277 | - | |
| 278 | - if ( !empty( $terms ) && !is_wp_error( $terms ) ) | |
| 279 | - { | |
| 280 | - foreach ($terms as $term) | |
| 281 | - { | |
| 282 | - $search_locations[] = $term->term_id; | |
| 283 | - | |
| 284 | - $args = array( | |
| 285 | - 'hide_empty' => false, | |
| 286 | - 'parent' => $term->term_id | |
| 287 | - ); | |
| 288 | - $subterms = get_terms( 'location', $args ); | |
| 289 | - | |
| 290 | - if ( !empty( $subterms ) && !is_wp_error( $subterms ) ) | |
| 291 | - { | |
| 292 | - foreach ($subterms as $term) | |
| 293 | - { | |
| 294 | - $search_locations[] = $term->term_id; | |
| 295 | - } | |
| 296 | - } | |
| 297 | - } | |
| 298 | - } | |
| 299 | - } | |
| 300 | - } | |
| 301 | - $search_locations = array_unique($search_locations); | |
| 302 | - | |
| 303 | - $args = array( | |
| 304 | - 'post_type' => 'contact', | |
| 305 | - 'fields' => 'ids', | |
| 306 | - 'nopaging' => true, | |
| 307 | - ); | |
| 308 | - | |
| 309 | - $args['meta_query'] = array(); | |
| 310 | - | |
| 311 | - $args['meta_query'][] = array( | |
| 312 | - 'key' => '_contact_types', | |
| 313 | - 'value' => 'applicant', | |
| 314 | - 'compare' => 'LIKE' | |
| 315 | - ); | |
| 316 | - | |
| 317 | - $applicant_query = new WP_Query($args); | |
| 318 | - | |
| 319 | - $results = array(); | |
| 320 | - | |
| 321 | - if ( $applicant_query->have_posts() ) | |
| 322 | - { | |
| 323 | - while ( $applicant_query->have_posts() ) | |
| 324 | - { | |
| 325 | - $applicant_query->the_post(); | |
| 326 | - | |
| 327 | - $num_applicant_profiles = get_post_meta( get_the_ID(), '_applicant_profiles', TRUE ); | |
| 328 | - if ( $num_applicant_profiles == '' ) | |
| 329 | - { | |
| 330 | - $num_applicant_profiles = 0; | |
| 331 | - } | |
| 332 | - | |
| 333 | - for ( $i = 0; $i < $num_applicant_profiles; ++$i ) | |
| 334 | - { | |
| 335 | - $profile = get_post_meta( get_the_ID(), '_applicant_profile_' . $i, TRUE ); | |
| 336 | - | |
| 337 | - $match = true; | |
| 338 | - | |
| 339 | - if ( !isset($profile['send_matching_properties']) || ( isset($profile['send_matching_properties']) && $profile['send_matching_properties'] != 'yes' ) ) | |
| 340 | - { | |
| 341 | - $match = false; | |
| 342 | - } | |
| 343 | - | |
| 344 | - if ( isset($_POST['department']) ) | |
| 345 | - { | |
| 346 | - if ( isset($profile['department']) && $profile['department'] != ph_clean($_POST['department']) ) | |
| 347 | - { | |
| 348 | - $match = false; | |
| 349 | - } | |
| 350 | - } | |
| 351 | - | |
| 352 | - if ( isset($_POST['department']) && $_POST['department'] == 'residential-sales' ) | |
| 353 | - { | |
| 354 | - if ( isset($_POST['maximum_price']) && ph_clean($_POST['maximum_price']) != '' ) | |
| 355 | - { | |
| 356 | - $price = preg_replace("/[^0-9]/", '', ph_clean($_POST['maximum_price'])); | |
| 357 | - | |
| 358 | - if ( isset($profile['max_price_actual']) && $profile['max_price_actual'] != '' && $profile['max_price_actual'] != 0 && $profile['max_price_actual'] < $price ) | |
| 359 | - { | |
| 360 | - $match = false; | |
| 361 | - } | |
| 362 | - } | |
| 363 | - } | |
| 364 | - if ( isset($_POST['department']) && $_POST['department'] == 'residential-lettings' ) | |
| 365 | - { | |
| 366 | - if ( isset($_POST['maximum_rent']) && ph_clean($_POST['maximum_rent']) != '' ) | |
| 367 | - { | |
| 368 | - $price = preg_replace("/[^0-9]/", '', ph_clean($_POST['maximum_rent'])); | |
| 369 | - | |
| 370 | - if ( isset($profile['max_rent_actual']) && $profile['max_rent_actual'] != '' && $profile['max_rent_actual'] != 0 && $profile['max_rent_actual'] < $price ) | |
| 371 | - { | |
| 372 | - $match = false; | |
| 373 | - } | |
| 374 | - } | |
| 375 | - } | |
| 376 | - if ( isset($_POST['department']) && ( $_POST['department'] == 'residential-sales' || $_POST['department'] == 'residential-lettings' ) ) | |
| 377 | - { | |
| 378 | - if ( isset($_POST['minimum_bedrooms']) && ph_clean($_POST['minimum_bedrooms']) != '' ) | |
| 379 | - { | |
| 380 | - $beds = preg_replace("/[^0-9]/", '', ph_clean($_POST['minimum_bedrooms'])); | |
| 381 | - | |
| 382 | - if ( isset($profile['min_beds']) && $profile['min_beds'] != '' && $profile['min_beds'] != 0 && $profile['min_beds'] > $beds ) | |
| 383 | - { | |
| 384 | - $match = false; | |
| 385 | - } | |
| 386 | - } | |
| 387 | - | |
| 388 | - // Property Types | |
| 389 | - if ( isset($_POST['property_types']) && is_array($_POST['property_types']) && !empty($_POST['property_types']) ) | |
| 390 | - { | |
| 391 | - $found_type = false; | |
| 392 | - foreach ( $search_property_types as $search_property_type ) | |
| 393 | - { | |
| 394 | - if ( isset($profile['property_types']) && is_array($profile['property_types']) && in_array($search_property_type, $profile['property_types']) ) | |
| 395 | - { | |
| 396 | - $found_type = true; | |
| 397 | - } | |
| 398 | - } | |
| 399 | - | |
| 400 | - if ( !$found_type ) | |
| 401 | - { | |
| 402 | - $match = false; | |
| 403 | - } | |
| 404 | - } | |
| 405 | - } | |
| 406 | - | |
| 407 | - if ( isset($_POST['locations']) && is_array($_POST['locations']) && !empty($_POST['locations']) ) | |
| 408 | - { | |
| 409 | - $found_type = false; | |
| 410 | - foreach ( $search_locations as $search_location ) | |
| 411 | - { | |
| 412 | - if ( isset($profile['locations']) && is_array($profile['locations']) && in_array($search_location, $profile['locations']) ) | |
| 413 | - { | |
| 414 | - $found_type = true; | |
| 415 | - } | |
| 416 | - } | |
| 417 | - | |
| 418 | - if ( !$found_type ) | |
| 419 | - { | |
| 420 | - $match = false; | |
| 421 | - } | |
| 422 | - } | |
| 423 | - | |
| 424 | - if ( $match ) | |
| 425 | - { | |
| 426 | - $contact_details = array(); | |
| 427 | - if ( get_post_meta( get_the_ID(), '_telephone_number', TRUE ) != '' ) | |
| 428 | - { | |
| 429 | - $contact_details[] = 'T: ' . get_post_meta( get_the_ID(), '_telephone_number', TRUE ); | |
| 430 | - } | |
| 431 | - if ( get_post_meta( get_the_ID(), '_email_address', TRUE ) != '' ) | |
| 432 | - { | |
| 433 | - $contact_details[] = 'E: ' . get_post_meta( get_the_ID(), '_email_address', TRUE ); | |
| 434 | - } | |
| 435 | - $results[] = array( | |
| 436 | - 'name' => get_the_title(), | |
| 437 | - 'edit_link' => get_edit_post_link(get_the_ID()), | |
| 438 | - 'contact_details' => implode("<br>", $contact_details), | |
| 439 | - 'profile' => $profile | |
| 440 | - ); | |
| 441 | - } | |
| 442 | - } | |
| 443 | - } | |
| 444 | - } | |
| 445 | - | |
| 446 | - wp_reset_postdata(); | |
| 281 | + $results = $this->generate_results(); | |
| 447 | 282 | ?> |
| 448 | 283 | <br> |
| 449 | 284 | <div class="applicant-list-results"> |
| 450 | - <h3><?php echo number_format(count($results)); ?> Applicants Found Matching Your Criteria</h3> | |
| 285 | + <h3><?php echo esc_html(number_format(count($results))); ?> Applicants Found Matching Your Criteria</h3> | |
| 451 | 286 | <table width="100%" cellpadding="8" cellspacing="0"> |
| 452 | 287 | <thead> |
| 453 | 288 | <tr> |
| 454 | - <th style="text-align:left;">Applicant Name</th> | |
| 455 | - <th style="text-align:left;">Contact Details</th> | |
| 456 | - <th style="text-align:left;">Requirements</th> | |
| 289 | + <th style="text-align:left;"><?php echo esc_html__( 'Applicant Name', 'propertyhive' ); ?></th> | |
| 290 | + <th style="text-align:left;"><?php echo esc_html__( 'Contact Details', 'propertyhive' ); ?></th> | |
| 291 | + <th style="text-align:left;"><?php echo esc_html__( 'Requirements', 'propertyhive' ); ?></th> | |
| 457 | 292 | </tr> |
| 458 | 293 | </thead> |
| 459 | 294 | <tbody> |
| 460 | 295 | <?php |
| @@ -459,15 +294,40 @@ | ||
| 459 | 294 | <tbody> |
| 460 | 295 | <?php |
| 461 | 296 | if ( !empty($results) ) |
| 462 | 297 | { |
| 298 | + $percentage_lower = get_option( 'propertyhive_applicant_match_price_range_percentage_lower', '' ); | |
| 299 | + $percentage_higher = get_option( 'propertyhive_applicant_match_price_range_percentage_higher', '' ); | |
| 300 | + | |
| 463 | 301 | foreach ( $results as $result ) |
| 464 | - { | |
| 302 | + { | |
| 303 | + $currency = '£'; | |
| 304 | + if ( isset($result['profile']['currency']) && !empty($result['profile']['currency']) ) | |
| 305 | + { | |
| 306 | + $PH_Countries = new PH_Countries(); | |
| 307 | + $selected_currency = $PH_Countries->get_currency($result['profile']['currency']); | |
| 308 | + if ( $selected_currency !== false ) | |
| 309 | + { | |
| 310 | + $currency = $selected_currency['currency_symbol']; | |
| 311 | + } | |
| 312 | + } | |
| 465 | 313 | ?> |
| 466 | 314 | <tr> |
| 467 | - <td><a href="<?php echo $result['edit_link'] ?>" target="_blank"><?php echo $result['name']; ?></a></td> | |
| 468 | - <td><?php echo ( ( $result['contact_details'] != '' ) ? $result['contact_details'] : '-' ); ?></td> | |
| 315 | + <td><a href="<?php echo esc_url($result['edit_link']); ?>" target="_blank"><?php echo esc_html($result['name']); ?></a></td> | |
| 469 | 316 | <td><?php |
| 317 | + $contact_details = array(); | |
| 318 | + if ( $result['telephone_number'] != '' ) | |
| 319 | + { | |
| 320 | + $contact_details[] = 'T: ' . esc_html($result['telephone_number']); | |
| 321 | + } | |
| 322 | + if ( $result['email_address'] != '' ) | |
| 323 | + { | |
| 324 | + $contact_details[] = 'E: ' . esc_html($result['email_address']); | |
| 325 | + } | |
| 326 | + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Telephone and email text are escaped above; only fixed br markup joins them. | |
| 327 | + echo !empty($contact_details) ? implode("<br>", $contact_details) : '-'; | |
| 328 | + ?></td> | |
| 329 | + <td><?php | |
| 470 | 330 | if ( isset($result['profile']['department']) ) |
| 471 | 331 | { |
| 472 | 332 | switch ( $result['profile']['department'] ) |
| 473 | 333 | { |
| @@ -475,13 +335,49 @@ | ||
| 475 | 335 | { |
| 476 | 336 | $output = array(); |
| 477 | 337 | if ( isset($result['profile']['max_price']) && $result['profile']['max_price'] != '' && $result['profile']['max_price'] != 0 ) |
| 478 | 338 | { |
| 479 | - $output[] = '<strong>Max Price:</strong> £' . number_format($result['profile']['max_price']); | |
| 339 | + $output[] = '<strong>Max Price:</strong> ' . $currency . esc_html(ph_display_price_field($result['profile']['max_price'])); | |
| 340 | + | |
| 341 | + if ( $percentage_lower != '' && $percentage_higher != '' ) | |
| 342 | + { | |
| 343 | + $match_price_range_lower = ''; | |
| 344 | + if ( !isset($result['profile']['match_price_range_lower_actual']) || ( isset($result['profile']['match_price_range_lower_actual']) && $result['profile']['match_price_range_lower_actual'] == '' ) ) | |
| 345 | + { | |
| 346 | + if ( isset($result['profile']['max_price']) && $result['profile']['max_price'] != '' ) | |
| 347 | + { | |
| 348 | + $match_price_range_lower = (float) $result['profile']['max_price'] - ( (float) $result['profile']['max_price'] * ( (float) $percentage_lower / 100 ) ); | |
| 349 | + } | |
| 350 | + } | |
| 351 | + else | |
| 352 | + { | |
| 353 | + $match_price_range_lower = $result['profile']['match_price_range_lower']; | |
| 354 | + } | |
| 355 | + | |
| 356 | + $match_price_range_higher = ''; | |
| 357 | + if ( !isset($result['profile']['match_price_range_higher_actual']) || ( isset($result['profile']['match_price_range_higher_actual']) && $result['profile']['match_price_range_higher_actual'] == '' ) ) | |
| 358 | + { | |
| 359 | + if ( isset($result['profile']['max_price']) && $result['profile']['max_price'] != '' ) | |
| 360 | + { | |
| 361 | + $match_price_range_higher = (float) $result['profile']['max_price'] + ( (float) $result['profile']['max_price'] * ( (float) $percentage_higher / 100 ) ); | |
| 362 | + } | |
| 363 | + } | |
| 364 | + else | |
| 365 | + { | |
| 366 | + $match_price_range_higher = $result['profile']['match_price_range_higher']; | |
| 367 | + } | |
| 368 | + | |
| 369 | + if ( | |
| 370 | + $match_price_range_lower != '' && $match_price_range_higher != '' | |
| 371 | + ) | |
| 372 | + { | |
| 373 | + $output[] = '<strong>Max Price Range:</strong> ' . $currency . esc_html(ph_display_price_field($match_price_range_lower)) . ' to ' . $currency . esc_html(ph_display_price_field($match_price_range_higher)); | |
| 374 | + } | |
| 375 | + } | |
| 480 | 376 | } |
| 481 | 377 | if ( isset($result['profile']['min_beds']) && $result['profile']['min_beds'] != '' && $result['profile']['min_beds'] != 0 ) |
| 482 | 378 | { |
| 483 | - $output[] = '<strong>Min Beds:</strong> ' . number_format($result['profile']['min_beds']); | |
| 379 | + $output[] = '<strong>Min Beds:</strong> ' . esc_html(number_format( (float) $result['profile']['min_beds'] )); | |
| 484 | 380 | } |
| 485 | 381 | if ( isset($result['profile']['property_types']) && is_array($result['profile']['property_types']) && !empty($result['profile']['property_types']) ) |
| 486 | 382 | { |
| 487 | 383 | $output_types = array(); |
| @@ -486,11 +382,13 @@ | ||
| 486 | 382 | { |
| 487 | 383 | $output_types = array(); |
| 488 | 384 | foreach ( $result['profile']['property_types'] as $profile_type ) |
| 489 | 385 | { |
| 490 | - $output_types[] = $property_types[$profile_type]; | |
| 386 | + if ( is_scalar( $profile_type ) && isset( $property_types[$profile_type] ) ) { | |
| 387 | + $output_types[] = $property_types[$profile_type]; | |
| 388 | + } | |
| 491 | 389 | } |
| 492 | - $output[] = '<strong>Property Types:</strong> ' . implode(", ", $output_types); | |
| 390 | + $output[] = '<strong>Property Types:</strong> ' . esc_html(implode(", ", $output_types)); | |
| 493 | 391 | } |
| 494 | 392 | if ( isset($result['profile']['locations']) && is_array($result['profile']['locations']) && !empty($result['profile']['locations']) ) |
| 495 | 393 | { |
| 496 | 394 | $output_locations = array(); |
| @@ -495,16 +393,19 @@ | ||
| 495 | 393 | { |
| 496 | 394 | $output_locations = array(); |
| 497 | 395 | foreach ( $result['profile']['locations'] as $profile_location ) |
| 498 | 396 | { |
| 499 | - $output_locations[] = $locations[$profile_location]; | |
| 397 | + if ( is_scalar( $profile_location ) && isset( $locations[$profile_location] ) ) { | |
| 398 | + $output_locations[] = $locations[$profile_location]; | |
| 399 | + } | |
| 500 | 400 | } |
| 501 | - $output[] = '<strong>Locations:</strong> ' . implode(", ", $output_locations); | |
| 401 | + $output[] = '<strong>Locations:</strong> ' . esc_html(implode(", ", $output_locations)); | |
| 502 | 402 | } |
| 503 | 403 | if ( isset($result['profile']['notes']) && $result['profile']['notes'] != '' ) |
| 504 | 404 | { |
| 505 | - $output[] = '<strong>Additional Requirements:</strong> ' . nl2br($result['profile']['notes']); | |
| 405 | + $output[] = '<strong>Additional Requirements:</strong> ' . nl2br(esc_html($result['profile']['notes'])); | |
| 506 | 406 | } |
| 407 | + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Requirement text is escaped when assembled above; strong/br markup is fixed and currency-symbol filter HTML remains trusted. | |
| 507 | 408 | echo( !empty($output) ? implode("<br>", $output) : '-' ); |
| 508 | 409 | break; |
| 509 | 410 | } |
| 510 | 411 | case "residential-lettings": |
| @@ -511,13 +412,13 @@ | ||
| 511 | 412 | { |
| 512 | 413 | $output = array(); |
| 513 | 414 | if ( isset($result['profile']['max_rent']) && $result['profile']['max_rent'] != '' && $result['profile']['max_rent'] != 0 ) |
| 514 | 415 | { |
| 515 | - $output[] = '<strong>Max Rent:</strong> £' . number_format($result['profile']['max_rent']) . $result['profile']['rent_frequency']; | |
| 416 | + $output[] = '<strong>Max Rent:</strong> ' . $currency . esc_html(ph_display_price_field($result['profile']['max_rent']) . $result['profile']['rent_frequency']); | |
| 516 | 417 | } |
| 517 | 418 | if ( isset($result['profile']['min_beds']) && $result['profile']['min_beds'] != '' && $result['profile']['min_beds'] != 0 ) |
| 518 | 419 | { |
| 519 | - $output[] = '<strong>Min Beds:</strong> ' . number_format($result['profile']['min_beds']); | |
| 420 | + $output[] = '<strong>Min Beds:</strong> ' . esc_html(number_format( (float) $result['profile']['min_beds'] )); | |
| 520 | 421 | } |
| 521 | 422 | if ( isset($result['profile']['property_types']) && is_array($result['profile']['property_types']) && !empty($result['profile']['property_types']) ) |
| 522 | 423 | { |
| 523 | 424 | $output_types = array(); |
| @@ -522,11 +423,13 @@ | ||
| 522 | 423 | { |
| 523 | 424 | $output_types = array(); |
| 524 | 425 | foreach ( $result['profile']['property_types'] as $profile_type ) |
| 525 | 426 | { |
| 526 | - $output_types[] = $property_types[$profile_type]; | |
| 427 | + if ( is_scalar( $profile_type ) && isset( $property_types[$profile_type] ) ) { | |
| 428 | + $output_types[] = $property_types[$profile_type]; | |
| 429 | + } | |
| 527 | 430 | } |
| 528 | - $output[] = '<strong>Property Types:</strong> ' . implode(", ", $output_types); | |
| 431 | + $output[] = '<strong>Property Types:</strong> ' . esc_html(implode(", ", $output_types)); | |
| 529 | 432 | } |
| 530 | 433 | if ( isset($result['profile']['locations']) && is_array($result['profile']['locations']) && !empty($result['profile']['locations']) ) |
| 531 | 434 | { |
| 532 | 435 | $output_locations = array(); |
| @@ -531,16 +434,19 @@ | ||
| 531 | 434 | { |
| 532 | 435 | $output_locations = array(); |
| 533 | 436 | foreach ( $result['profile']['locations'] as $profile_location ) |
| 534 | 437 | { |
| 535 | - $output_locations[] = $locations[$profile_location]; | |
| 438 | + if ( is_scalar( $profile_location ) && isset( $locations[$profile_location] ) ) { | |
| 439 | + $output_locations[] = $locations[$profile_location]; | |
| 440 | + } | |
| 536 | 441 | } |
| 537 | - $output[] = '<strong>Locations:</strong> ' . implode(", ", $output_locations); | |
| 442 | + $output[] = '<strong>Locations:</strong> ' . esc_html(implode(", ", $output_locations)); | |
| 538 | 443 | } |
| 539 | 444 | if ( isset($result['profile']['notes']) && $result['profile']['notes'] != '' ) |
| 540 | 445 | { |
| 541 | - $output[] = '<strong>Additional Requirements:</strong> ' . nl2br($result['profile']['notes']); | |
| 446 | + $output[] = '<strong>Additional Requirements:</strong> ' . nl2br(esc_html($result['profile']['notes'])); | |
| 542 | 447 | } |
| 448 | + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Requirement text is escaped when assembled above; strong/br markup is fixed and currency-symbol filter HTML remains trusted. | |
| 543 | 449 | echo( !empty($output) ? implode("<br>", $output) : '-' ); |
| 544 | 450 | break; |
| 545 | 451 | } |
| 546 | 452 | case "commercial": |
| @@ -545,38 +451,24 @@ | ||
| 545 | 451 | } |
| 546 | 452 | case "commercial": |
| 547 | 453 | { |
| 548 | 454 | $output = array(); |
| 549 | - /*if ( isset($result['profile']['max_price']) && $result['profile']['max_price'] != '' && $result['profile']['max_price'] != 0 ) | |
| 550 | - { | |
| 551 | - $output[] = '<strong>Max Price:</strong> £' . number_format($result['profile']['max_price']); | |
| 552 | - } | |
| 553 | - if ( isset($result['profile']['min_beds']) && $result['profile']['min_beds'] != '' && $result['profile']['min_beds'] != 0 ) | |
| 554 | - { | |
| 555 | - $output[] = '<strong>Min Beds:</strong> ' . number_format($result['profile']['min_beds']); | |
| 556 | - } | |
| 557 | - if ( isset($result['profile']['property_types']) && is_array($result['profile']['property_types']) && !empty($result['profile']['property_types']) ) | |
| 558 | - { | |
| 559 | - $output_types = array(); | |
| 560 | - foreach ( $result['profile']['property_types'] as $profile_type ) | |
| 561 | - { | |
| 562 | - $output_types[] = $property_types[$profile_type]; | |
| 563 | - } | |
| 564 | - $output[] = '<strong>Property Types:</strong> ' . implode(", ", $output_types); | |
| 565 | - }*/ | |
| 566 | 455 | if ( isset($result['profile']['locations']) && is_array($result['profile']['locations']) && !empty($result['profile']['locations']) ) |
| 567 | 456 | { |
| 568 | 457 | $output_locations = array(); |
| 569 | 458 | foreach ( $result['profile']['locations'] as $profile_location ) |
| 570 | 459 | { |
| 571 | - $output_locations[] = $locations[$profile_location]; | |
| 460 | + if ( is_scalar( $profile_location ) && isset( $locations[$profile_location] ) ) { | |
| 461 | + $output_locations[] = $locations[$profile_location]; | |
| 462 | + } | |
| 572 | 463 | } |
| 573 | - $output[] = '<strong>Locations:</strong> ' . implode(", ", $output_locations); | |
| 464 | + $output[] = '<strong>Locations:</strong> ' . esc_html(implode(", ", $output_locations)); | |
| 574 | 465 | } |
| 575 | 466 | if ( isset($result['profile']['notes']) && $result['profile']['notes'] != '' ) |
| 576 | 467 | { |
| 577 | - $output[] = '<strong>Additional Requirements:</strong> ' . nl2br($result['profile']['notes']); | |
| 468 | + $output[] = '<strong>Additional Requirements:</strong> ' . nl2br(esc_html($result['profile']['notes'])); | |
| 578 | 469 | } |
| 470 | + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Requirement text is escaped when assembled above; strong/br markup is fixed and currency-symbol filter HTML remains trusted. | |
| 579 | 471 | echo( !empty($output) ? implode("<br>", $output) : '-' ); |
| 580 | 472 | break; |
| 581 | 473 | } |
| 582 | 474 | } |
| @@ -589,9 +481,9 @@ | ||
| 589 | 481 | else |
| 590 | 482 | { |
| 591 | 483 | ?> |
| 592 | 484 | <tr> |
| 593 | - <td colspan="3" style="text-align:center"><?php echo __( 'No matching applicants found', 'propertyhive' ); ?></td> | |
| 485 | + <td colspan="3" style="text-align:center"><?php echo esc_html(__( 'No matching applicants found', 'propertyhive' )); ?></td> | |
| 594 | 486 | </tr> |
| 595 | 487 | <?php |
| 596 | 488 | } |
| 597 | 489 | ?> |
| @@ -606,8 +498,9 @@ | ||
| 606 | 498 | |
| 607 | 499 | </div> |
| 608 | 500 | <script> |
| 609 | 501 | |
| 502 | +var custom_departments = <?php echo json_encode(ph_get_custom_departments()); ?>; | |
| 610 | 503 | function toggleDepartmentFields() |
| 611 | 504 | { |
| 612 | 505 | if (jQuery('#mainform').length > 0) |
| 613 | 506 | { |
| @@ -631,19 +524,19 @@ | ||
| 631 | 524 | // first visible component (that isnt sales/lettings-only) and |
| 632 | 525 | // use that display |
| 633 | 526 | var display = 'block'; |
| 634 | 527 | |
| 635 | - if (selectedDepartment == 'residential-sales') | |
| 528 | + if ( selectedDepartment == 'residential-sales' || ( custom_departments[selectedDepartment] && custom_departments[selectedDepartment].based_on == 'residential-sales' ) ) | |
| 636 | 529 | { |
| 637 | 530 | jQuery(this).find('.sales-only').css('display', display); |
| 638 | 531 | jQuery(this).find('.residential-only').css('display', display); |
| 639 | 532 | } |
| 640 | - else if (selectedDepartment == 'residential-lettings') | |
| 533 | + else if ( selectedDepartment == 'residential-lettings' || ( custom_departments[selectedDepartment] && custom_departments[selectedDepartment].based_on == 'residential-lettings' ) ) | |
| 641 | 534 | { |
| 642 | 535 | jQuery(this).find('.lettings-only').css('display', display); |
| 643 | 536 | jQuery(this).find('.residential-only').css('display', display); |
| 644 | 537 | } |
| 645 | - else if (selectedDepartment == 'commercial') | |
| 538 | + else if ( selectedDepartment == 'commercial' || ( custom_departments[selectedDepartment] && custom_departments[selectedDepartment].based_on == 'commercial' ) ) | |
| 646 | 539 | { |
| 647 | 540 | jQuery(this).find('.commercial-only').css('display', display); |
| 648 | 541 | } |
| 649 | 542 | } |
| @@ -662,8 +555,16 @@ | ||
| 662 | 555 | { |
| 663 | 556 | toggleDepartmentFields(); |
| 664 | 557 | }); |
| 665 | 558 | |
| 559 | + jQuery('#export_applicant_list_results_button').click(function(e) | |
| 560 | + { | |
| 561 | + e.preventDefault(); | |
| 562 | + jQuery('input[name=\'export_applicant_list_results\']').val('1'); | |
| 563 | + jQuery('#mainform').submit(); | |
| 564 | + | |
| 565 | + setTimeout(function() { jQuery('input[name=\'export_applicant_list_results\']').val(''); }, 1000); | |
| 566 | + }); | |
| 666 | 567 | }); |
| 667 | 568 | |
| 668 | 569 | jQuery(window).resize(function() { |
| 669 | 570 | toggleDepartmentFields(); |
| @@ -672,7 +573,652 @@ | ||
| 672 | 573 | </script> |
| 673 | 574 | <?php |
| 674 | 575 | } |
| 675 | 576 | |
| 577 | + public function generate_results() | |
| 578 | + { | |
| 579 | + // Results are read-only. Normalize the submitted filters before they are used in comparisons or queries. | |
| 580 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- This method only reads applicant filters; the separate export endpoint verifies its nonce and capability. | |
| 581 | + $request_post = wp_unslash( $_POST ); | |
| 582 | + $has_department = isset( $request_post['department'] ) && is_scalar( $request_post['department'] ); | |
| 583 | + $department_input = $has_department ? sanitize_text_field( $request_post['department'] ) : ''; | |
| 584 | + $maximum_price_input = ( isset( $request_post['maximum_price'] ) && is_scalar( $request_post['maximum_price'] ) ) ? sanitize_text_field( $request_post['maximum_price'] ) : ''; | |
| 585 | + $maximum_rent_input = ( isset( $request_post['maximum_rent'] ) && is_scalar( $request_post['maximum_rent'] ) ) ? sanitize_text_field( $request_post['maximum_rent'] ) : ''; | |
| 586 | + $minimum_bedrooms_input = ( isset( $request_post['minimum_bedrooms'] ) && is_scalar( $request_post['minimum_bedrooms'] ) ) ? sanitize_text_field( $request_post['minimum_bedrooms'] ) : ''; | |
| 587 | + $has_property_types = isset( $request_post['property_types'] ) && is_array( $request_post['property_types'] ); | |
| 588 | + $property_types_input = array(); | |
| 589 | + if ( $has_property_types ) { | |
| 590 | + foreach ( $request_post['property_types'] as $property_type_input ) { | |
| 591 | + if ( is_scalar( $property_type_input ) ) { | |
| 592 | + $property_types_input[] = absint( $property_type_input ); | |
| 593 | + } | |
| 594 | + } | |
| 595 | + } | |
| 596 | + $has_locations = isset( $request_post['locations'] ) && is_array( $request_post['locations'] ); | |
| 597 | + $locations_input = array(); | |
| 598 | + if ( $has_locations ) { | |
| 599 | + foreach ( $request_post['locations'] as $location_input ) { | |
| 600 | + if ( is_scalar( $location_input ) ) { | |
| 601 | + $locations_input[] = absint( $location_input ); | |
| 602 | + } | |
| 603 | + } | |
| 604 | + } | |
| 605 | + $has_include_non_send_matching_properties = isset( $request_post['include_non_send_matching_properties'] ); | |
| 606 | + | |
| 607 | + $search_property_types = array(); | |
| 608 | + if ( | |
| 609 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 610 | + $has_department && | |
| 611 | + ( | |
| 612 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 613 | + $department_input == 'residential-sales' || | |
| 614 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 615 | + $department_input == 'residential-lettings' || | |
| 616 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 617 | + ph_get_custom_department_based_on($department_input) == 'residential-sales' || | |
| 618 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 619 | + ph_get_custom_department_based_on($department_input) == 'residential-lettings' | |
| 620 | + ) | |
| 621 | + ) | |
| 622 | + { | |
| 623 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 624 | + if ( $has_property_types && ! empty( $property_types_input ) ) | |
| 625 | + { | |
| 626 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 627 | + foreach ( $property_types_input as $property_type ) | |
| 628 | + { | |
| 629 | + $search_property_types[] = (int)$property_type; | |
| 630 | + | |
| 631 | + $args = array( | |
| 632 | + 'hide_empty' => false, | |
| 633 | + 'parent' => $property_type | |
| 634 | + ); | |
| 635 | + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_type' ) ) ); | |
| 636 | + | |
| 637 | + if ( !empty( $terms ) && !is_wp_error( $terms ) ) | |
| 638 | + { | |
| 639 | + foreach ($terms as $term) | |
| 640 | + { | |
| 641 | + $search_property_types[] = $term->term_id; | |
| 642 | + | |
| 643 | + $args = array( | |
| 644 | + 'hide_empty' => false, | |
| 645 | + 'parent' => $term->term_id | |
| 646 | + ); | |
| 647 | + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_type' ) ) ); | |
| 648 | + | |
| 649 | + if ( !empty( $subterms ) && !is_wp_error( $subterms ) ) | |
| 650 | + { | |
| 651 | + foreach ($subterms as $term) | |
| 652 | + { | |
| 653 | + $search_property_types[] = $term->term_id; | |
| 654 | + } | |
| 655 | + } | |
| 656 | + } | |
| 657 | + } | |
| 658 | + } | |
| 659 | + } | |
| 660 | + $search_property_types = array_unique($search_property_types); | |
| 661 | + } | |
| 662 | + | |
| 663 | + $search_locations = array(); | |
| 664 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 665 | + if ( $has_locations && ! empty( $locations_input ) ) | |
| 666 | + { | |
| 667 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 668 | + foreach ( $locations_input as $location ) | |
| 669 | + { | |
| 670 | + $search_locations[] = (int)$location; | |
| 671 | + | |
| 672 | + $args = array( | |
| 673 | + 'hide_empty' => false, | |
| 674 | + 'parent' => $location | |
| 675 | + ); | |
| 676 | + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) ); | |
| 677 | + | |
| 678 | + if ( !empty( $terms ) && !is_wp_error( $terms ) ) | |
| 679 | + { | |
| 680 | + foreach ($terms as $term) | |
| 681 | + { | |
| 682 | + $search_locations[] = $term->term_id; | |
| 683 | + | |
| 684 | + $args = array( | |
| 685 | + 'hide_empty' => false, | |
| 686 | + 'parent' => $term->term_id | |
| 687 | + ); | |
| 688 | + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) ); | |
| 689 | + | |
| 690 | + if ( !empty( $subterms ) && !is_wp_error( $subterms ) ) | |
| 691 | + { | |
| 692 | + foreach ($subterms as $term) | |
| 693 | + { | |
| 694 | + $search_locations[] = $term->term_id; | |
| 695 | + } | |
| 696 | + } | |
| 697 | + } | |
| 698 | + } | |
| 699 | + } | |
| 700 | + } | |
| 701 | + $search_locations = array_unique($search_locations); | |
| 702 | + | |
| 703 | + $args = array( | |
| 704 | + 'post_type' => 'contact', | |
| 705 | + 'fields' => 'ids', | |
| 706 | + 'nopaging' => true, | |
| 707 | + ); | |
| 708 | + | |
| 709 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Existing applicant membership is stored in serialized _contact_types metadata; preserve complete list/export results and their extension match checks. Query fetches IDs only. | |
| 710 | + $args['meta_query'] = array(); | |
| 711 | + | |
| 712 | + $args['meta_query'][] = array( | |
| 713 | + 'key' => '_contact_types', | |
| 714 | + 'value' => 'applicant', | |
| 715 | + 'compare' => 'LIKE' | |
| 716 | + ); | |
| 717 | + | |
| 718 | + $applicant_query = new WP_Query($args); | |
| 719 | + | |
| 720 | + $results = array(); | |
| 721 | + | |
| 722 | + if ( $applicant_query->have_posts() ) | |
| 723 | + { | |
| 724 | + $percentage_lower = get_option( 'propertyhive_applicant_match_price_range_percentage_lower', '' ); | |
| 725 | + $percentage_higher = get_option( 'propertyhive_applicant_match_price_range_percentage_higher', '' ); | |
| 726 | + | |
| 727 | + while ( $applicant_query->have_posts() ) | |
| 728 | + { | |
| 729 | + $applicant_query->the_post(); | |
| 730 | + | |
| 731 | + $num_applicant_profiles = get_post_meta( get_the_ID(), '_applicant_profiles', TRUE ); | |
| 732 | + if ( $num_applicant_profiles == '' ) | |
| 733 | + { | |
| 734 | + $num_applicant_profiles = 0; | |
| 735 | + } | |
| 736 | + | |
| 737 | + for ( $i = 0; $i < $num_applicant_profiles; ++$i ) | |
| 738 | + { | |
| 739 | + $profile = get_post_meta( get_the_ID(), '_applicant_profile_' . $i, TRUE ); | |
| 740 | + | |
| 741 | + $match = true; | |
| 742 | + | |
| 743 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 744 | + if ( !$has_include_non_send_matching_properties ) | |
| 745 | + { | |
| 746 | + if ( !isset($profile['send_matching_properties']) || ( isset($profile['send_matching_properties']) && $profile['send_matching_properties'] != 'yes' ) ) | |
| 747 | + { | |
| 748 | + $match = false; | |
| 749 | + } | |
| 750 | + } | |
| 751 | + | |
| 752 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 753 | + if ( $has_department ) | |
| 754 | + { | |
| 755 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 756 | + if ( isset($profile['department']) && $profile['department'] != ph_clean($department_input) ) | |
| 757 | + { | |
| 758 | + $match = false; | |
| 759 | + } | |
| 760 | + } | |
| 761 | + | |
| 762 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 763 | + if ( $has_department && ( $department_input == 'residential-sales' || ph_get_custom_department_based_on($department_input) == 'residential-sales' ) ) | |
| 764 | + { | |
| 765 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 766 | + if ( $maximum_price_input !== '' && ph_clean($maximum_price_input) != '' ) | |
| 767 | + { | |
| 768 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 769 | + $price = preg_replace("/[^0-9.]/", '', ph_clean($maximum_price_input)); | |
| 770 | + | |
| 771 | + if ( $percentage_lower != '' && $percentage_higher != '' ) | |
| 772 | + { | |
| 773 | + $match_price_range_lower = ''; | |
| 774 | + if ( !isset($profile['match_price_range_lower_actual']) || ( isset($profile['match_price_range_lower_actual']) && $profile['match_price_range_lower_actual'] == '' ) ) | |
| 775 | + { | |
| 776 | + if ( isset($profile['max_price_actual']) && $profile['max_price_actual'] != '' ) | |
| 777 | + { | |
| 778 | + if ( $percentage_lower != '' ) | |
| 779 | + { | |
| 780 | + $match_price_range_lower = $profile['max_price_actual'] - ( $profile['max_price_actual'] * ( $percentage_lower / 100 ) ); | |
| 781 | + } | |
| 782 | + } | |
| 783 | + } | |
| 784 | + else | |
| 785 | + { | |
| 786 | + $match_price_range_lower = $profile['match_price_range_lower_actual']; | |
| 787 | + } | |
| 788 | + | |
| 789 | + $match_price_range_higher = ''; | |
| 790 | + if ( !isset($profile['match_price_range_higher_actual']) || ( isset($profile['match_price_range_higher_actual']) && $profile['match_price_range_higher_actual'] == '' ) ) | |
| 791 | + { | |
| 792 | + if ( isset($profile['max_price_actual']) && $profile['max_price_actual'] != '' ) | |
| 793 | + { | |
| 794 | + if ( $percentage_higher != '' ) | |
| 795 | + { | |
| 796 | + $match_price_range_higher = $profile['max_price_actual'] + ( $profile['max_price_actual'] * ( $percentage_higher / 100 ) ); | |
| 797 | + } | |
| 798 | + } | |
| 799 | + } | |
| 800 | + else | |
| 801 | + { | |
| 802 | + $match_price_range_higher = $profile['match_price_range_higher_actual']; | |
| 803 | + } | |
| 804 | + | |
| 805 | + if ( | |
| 806 | + !($match_price_range_lower == '' && $match_price_range_higher == '') && // Both bounds are not empty | |
| 807 | + !( | |
| 808 | + $price >= $match_price_range_lower && | |
| 809 | + $price <= $match_price_range_higher | |
| 810 | + ) // Price is not within the bounds | |
| 811 | + ) { | |
| 812 | + $match = false; // Assuming you want to set match to false; adjust as needed | |
| 813 | + } | |
| 814 | + } | |
| 815 | + else | |
| 816 | + { | |
| 817 | + if ( | |
| 818 | + isset($profile['max_price_actual']) && | |
| 819 | + $profile['max_price_actual'] !== '' && // Checks if max_price_actual is not an empty string | |
| 820 | + $price > $profile['max_price_actual'] // Checks if price is greater than max_price_actual | |
| 821 | + ) | |
| 822 | + { | |
| 823 | + $match = false; | |
| 824 | + } | |
| 825 | + } | |
| 826 | + } | |
| 827 | + } | |
| 828 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 829 | + if ( $has_department && ( $department_input == 'residential-lettings' || ph_get_custom_department_based_on($department_input) == 'residential-lettings' ) ) | |
| 830 | + { | |
| 831 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 832 | + if ( $maximum_rent_input !== '' && ph_clean($maximum_rent_input) != '' ) | |
| 833 | + { | |
| 834 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 835 | + $price = preg_replace("/[^0-9.]/", '', ph_clean($maximum_rent_input)); | |
| 836 | + | |
| 837 | + if ( isset($profile['max_price_actual']) && $profile['max_price_actual'] != '' && $profile['max_price_actual'] != 0 && $profile['max_price_actual'] < $price ) | |
| 838 | + { | |
| 839 | + $match = false; | |
| 840 | + } | |
| 841 | + } | |
| 842 | + } | |
| 843 | + if ( | |
| 844 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 845 | + $has_department && | |
| 846 | + ( | |
| 847 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 848 | + $department_input == 'residential-sales' || | |
| 849 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 850 | + $department_input == 'residential-lettings' || | |
| 851 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 852 | + ph_get_custom_department_based_on($department_input) == 'residential-sales' || | |
| 853 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 854 | + ph_get_custom_department_based_on($department_input) == 'residential-lettings' | |
| 855 | + ) | |
| 856 | + ) | |
| 857 | + { | |
| 858 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 859 | + if ( $minimum_bedrooms_input !== '' && ph_clean($minimum_bedrooms_input) != '' ) | |
| 860 | + { | |
| 861 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 862 | + $beds = preg_replace("/[^0-9.]/", '', ph_clean($minimum_bedrooms_input)); | |
| 863 | + | |
| 864 | + if ( isset($profile['min_beds']) && $profile['min_beds'] != '' && $profile['min_beds'] != 0 && $profile['min_beds'] > $beds ) | |
| 865 | + { | |
| 866 | + $match = false; | |
| 867 | + } | |
| 868 | + } | |
| 869 | + | |
| 870 | + // Property Types | |
| 871 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 872 | + if ( $has_property_types && ! empty( $property_types_input ) ) | |
| 873 | + { | |
| 874 | + $found_type = false; | |
| 875 | + foreach ( $search_property_types as $search_property_type ) | |
| 876 | + { | |
| 877 | + if ( isset($profile['property_types']) && is_array($profile['property_types']) && in_array($search_property_type, $profile['property_types']) ) | |
| 878 | + { | |
| 879 | + $found_type = true; | |
| 880 | + } | |
| 881 | + } | |
| 882 | + | |
| 883 | + if ( !$found_type ) | |
| 884 | + { | |
| 885 | + $match = false; | |
| 886 | + } | |
| 887 | + } | |
| 888 | + } | |
| 889 | + | |
| 890 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only applicant filters and form display; CSV export separately verifies its nonce and CRM capability. | |
| 891 | + if ( $has_locations && ! empty( $locations_input ) ) | |
| 892 | + { | |
| 893 | + $found_type = false; | |
| 894 | + foreach ( $search_locations as $search_location ) | |
| 895 | + { | |
| 896 | + if ( isset($profile['locations']) && is_array($profile['locations']) && in_array($search_location, $profile['locations']) ) | |
| 897 | + { | |
| 898 | + $found_type = true; | |
| 899 | + } | |
| 900 | + } | |
| 901 | + | |
| 902 | + if ( !$found_type ) | |
| 903 | + { | |
| 904 | + $match = false; | |
| 905 | + } | |
| 906 | + } | |
| 907 | + | |
| 908 | + $match = apply_filters( 'propertyhive_applicant_list_check', $match, get_the_ID(), $profile ); | |
| 909 | + | |
| 910 | + if ( $match ) | |
| 911 | + { | |
| 912 | + $contact = new PH_Contact( get_the_ID() ); | |
| 913 | + | |
| 914 | + $results[] = array( | |
| 915 | + 'contact_id' => get_the_ID(), | |
| 916 | + 'applicant_profile_id' => $i, | |
| 917 | + 'name' => get_the_title(), | |
| 918 | + 'edit_link' => get_edit_post_link(get_the_ID()), | |
| 919 | + 'telephone_number' => $contact->_telephone_number, | |
| 920 | + 'email_address' => $contact->_email_address, | |
| 921 | + 'address' => $contact->get_formatted_full_address(), | |
| 922 | + 'profile' => $profile | |
| 923 | + ); | |
| 924 | + } | |
| 925 | + } | |
| 926 | + } | |
| 927 | + } | |
| 928 | + | |
| 929 | + wp_reset_postdata(); | |
| 930 | + | |
| 931 | + return $results; | |
| 932 | + } | |
| 933 | + | |
| 934 | + private function array_2_csv($results) | |
| 935 | + { | |
| 936 | + // export() verifies the nonce and capability before calling this formatter. | |
| 937 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- This private formatter only reads the already-authorized export filters. | |
| 938 | + $request_post = wp_unslash( $_POST ); | |
| 939 | + $has_department = isset( $request_post['department'] ) && is_scalar( $request_post['department'] ); | |
| 940 | + $department_input = $has_department ? sanitize_text_field( $request_post['department'] ) : ''; | |
| 941 | + | |
| 942 | + $locations = array(); | |
| 943 | + $args = array( | |
| 944 | + 'hide_empty' => false, | |
| 945 | + 'parent' => 0 | |
| 946 | + ); | |
| 947 | + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) ); | |
| 948 | + | |
| 949 | + if ( !empty( $terms ) && !is_wp_error( $terms ) ) | |
| 950 | + { | |
| 951 | + foreach ($terms as $term) | |
| 952 | + { | |
| 953 | + $locations[$term->term_id] = html_entity_decode( $term->name, ENT_QUOTES, get_bloginfo( 'charset' ) ); | |
| 954 | + | |
| 955 | + $args = array( | |
| 956 | + 'hide_empty' => false, | |
| 957 | + 'parent' => $term->term_id | |
| 958 | + ); | |
| 959 | + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) ); | |
| 960 | + | |
| 961 | + if ( !empty( $subterms ) && !is_wp_error( $subterms ) ) | |
| 962 | + { | |
| 963 | + foreach ($subterms as $term) | |
| 964 | + { | |
| 965 | + $locations[$term->term_id] = html_entity_decode( $term->name, ENT_QUOTES, get_bloginfo( 'charset' ) ); | |
| 966 | + | |
| 967 | + $args = array( | |
| 968 | + 'hide_empty' => false, | |
| 969 | + 'parent' => $term->term_id | |
| 970 | + ); | |
| 971 | + $subsubterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) ); | |
| 972 | + | |
| 973 | + if ( !empty( $subsubterms ) && !is_wp_error( $subsubterms ) ) | |
| 974 | + { | |
| 975 | + foreach ($subsubterms as $term) | |
| 976 | + { | |
| 977 | + $locations[$term->term_id] = html_entity_decode( $term->name, ENT_QUOTES, get_bloginfo( 'charset' ) ); | |
| 978 | + } | |
| 979 | + } | |
| 980 | + } | |
| 981 | + } | |
| 982 | + } | |
| 983 | + } | |
| 984 | + | |
| 985 | + $property_types = array(); | |
| 986 | + $args = array( | |
| 987 | + 'hide_empty' => false, | |
| 988 | + 'parent' => 0 | |
| 989 | + ); | |
| 990 | + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_type' ) ) ); | |
| 991 | + | |
| 992 | + if ( !empty( $terms ) && !is_wp_error( $terms ) ) | |
| 993 | + { | |
| 994 | + foreach ($terms as $term) | |
| 995 | + { | |
| 996 | + $property_types[$term->term_id] = html_entity_decode( $term->name, ENT_QUOTES, get_bloginfo( 'charset' ) ); | |
| 997 | + | |
| 998 | + $args = array( | |
| 999 | + 'hide_empty' => false, | |
| 1000 | + 'parent' => $term->term_id | |
| 1001 | + ); | |
| 1002 | + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_type' ) ) ); | |
| 1003 | + | |
| 1004 | + if ( !empty( $subterms ) && !is_wp_error( $subterms ) ) | |
| 1005 | + { | |
| 1006 | + foreach ($subterms as $term) | |
| 1007 | + { | |
| 1008 | + $property_types[$term->term_id] = html_entity_decode( $term->name, ENT_QUOTES, get_bloginfo( 'charset' ) ); | |
| 1009 | + } | |
| 1010 | + } | |
| 1011 | + } | |
| 1012 | + } | |
| 1013 | + | |
| 1014 | + $percentage_lower = get_option( 'propertyhive_applicant_match_price_range_percentage_lower', '' ); | |
| 1015 | + $percentage_higher = get_option( 'propertyhive_applicant_match_price_range_percentage_higher', '' ); | |
| 1016 | + | |
| 1017 | + ob_start(); | |
| 1018 | + | |
| 1019 | + $df = fopen("php://output", 'w'); | |
| 1020 | + | |
| 1021 | + $columns = array( | |
| 1022 | + 'name' => __( 'Name', 'propertyhive' ), | |
| 1023 | + 'email_address' => __( 'Email Address', 'propertyhive' ), | |
| 1024 | + 'telephone_number' => __( 'Telephone Number', 'propertyhive' ), | |
| 1025 | + 'address' => __( 'Address', 'propertyhive' ), | |
| 1026 | + 'department' => __( 'Department', 'propertyhive' ), | |
| 1027 | + ); | |
| 1028 | + | |
| 1029 | + if ( $has_department ) | |
| 1030 | + { | |
| 1031 | + $department = ph_clean( $department_input ); | |
| 1032 | + if ( ph_get_custom_department_based_on($department) !== FALSE ) | |
| 1033 | + { | |
| 1034 | + $department = ph_get_custom_department_based_on($department); | |
| 1035 | + } | |
| 1036 | + } | |
| 1037 | + if ( isset($department) ) | |
| 1038 | + { | |
| 1039 | + switch ( $department ) | |
| 1040 | + { | |
| 1041 | + case "residential-sales": | |
| 1042 | + { | |
| 1043 | + $columns['maximum_price'] = __( 'Maximum Price', 'propertyhive' ); | |
| 1044 | + if ( $percentage_lower != '' && $percentage_higher != '' ) { | |
| 1045 | + $columns['maximum_price_range'] = __( 'Maximum Price Range', 'propertyhive' ); | |
| 1046 | + } | |
| 1047 | + break; | |
| 1048 | + } | |
| 1049 | + case "residential-lettings": | |
| 1050 | + { | |
| 1051 | + $columns['maximum_rent'] = __( 'Maximum Rent (PCM)', 'propertyhive' ); | |
| 1052 | + break; | |
| 1053 | + } | |
| 1054 | + } | |
| 1055 | + if ( $department == 'residential-sales' || $department == 'residential-lettings' ) | |
| 1056 | + { | |
| 1057 | + $columns['currency'] = __( 'Currency', 'propertyhive' ); | |
| 1058 | + $columns['minimum_bedrooms'] = __( 'Minimum Bedrooms', 'propertyhive' ); | |
| 1059 | + $columns['property_types'] = __( 'Property Types', 'propertyhive' ); | |
| 1060 | + } | |
| 1061 | + } | |
| 1062 | + | |
| 1063 | + $columns['locations'] = __( 'Locations', 'propertyhive' ); | |
| 1064 | + $columns['additional_requirements'] = __( 'Additional Requirements', 'propertyhive' ); | |
| 1065 | + | |
| 1066 | + // Keep the historical raw POST value as the filter contract for extensions. | |
| 1067 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Export nonce and capability are verified by export() before this private formatter is called; the raw value is retained for extension compatibility. | |
| 1068 | + $columns = apply_filters( 'propertyhive_export_applicant_list_columns', $columns, $_POST ); | |
| 1069 | + | |
| 1070 | + fputcsv( $df, $columns, ',', '"', '' ); | |
| 1071 | + | |
| 1072 | + foreach ($results as $result) | |
| 1073 | + { | |
| 1074 | + $columns = array( | |
| 1075 | + 'name' => $result['name'], | |
| 1076 | + 'email_address' => $result['email_address'], | |
| 1077 | + 'telephone_number' => $result['telephone_number'], | |
| 1078 | + 'address' => $result['address'], | |
| 1079 | + 'department' => ( isset($result['profile']['department']) ? propertyhive_get_department_label( $result['profile']['department'] ) : '-' ), | |
| 1080 | + ); | |
| 1081 | + | |
| 1082 | + if ( isset($department) ) | |
| 1083 | + { | |
| 1084 | + switch ( $department ) | |
| 1085 | + { | |
| 1086 | + case "residential-sales": | |
| 1087 | + { | |
| 1088 | + $columns['maximum_price'] = ( isset($result['profile']['max_price']) ? $result['profile']['max_price'] : '' ); | |
| 1089 | + if ( $percentage_lower != '' && $percentage_higher != '' ) { | |
| 1090 | + $columns['maximum_price_range'] = ''; | |
| 1091 | + } | |
| 1092 | + | |
| 1093 | + if ( !empty($columns['maximum_price']) ) | |
| 1094 | + { | |
| 1095 | + if ( $percentage_lower != '' && $percentage_higher != '' ) | |
| 1096 | + { | |
| 1097 | + $match_price_range_lower = ''; | |
| 1098 | + if ( !isset($result['profile']['match_price_range_lower_actual']) || ( isset($result['profile']['match_price_range_lower_actual']) && $result['profile']['match_price_range_lower_actual'] == '' ) ) | |
| 1099 | + { | |
| 1100 | + if ( isset($result['profile']['max_price']) && $result['profile']['max_price'] != '' ) | |
| 1101 | + { | |
| 1102 | + $match_price_range_lower = (float) $result['profile']['max_price'] - ( (float) $result['profile']['max_price'] * ( (float) $percentage_lower / 100 ) ); | |
| 1103 | + } | |
| 1104 | + } | |
| 1105 | + else | |
| 1106 | + { | |
| 1107 | + $match_price_range_lower = $result['profile']['match_price_range_lower']; | |
| 1108 | + } | |
| 1109 | + | |
| 1110 | + $match_price_range_higher = ''; | |
| 1111 | + if ( !isset($result['profile']['match_price_range_higher_actual']) || ( isset($result['profile']['match_price_range_higher_actual']) && $result['profile']['match_price_range_higher_actual'] == '' ) ) | |
| 1112 | + { | |
| 1113 | + if ( isset($result['profile']['max_price']) && $result['profile']['max_price'] != '' ) | |
| 1114 | + { | |
| 1115 | + $match_price_range_higher = (float) $result['profile']['max_price'] + ( (float) $result['profile']['max_price'] * ( (float) $percentage_higher / 100 ) ); | |
| 1116 | + } | |
| 1117 | + } | |
| 1118 | + else | |
| 1119 | + { | |
| 1120 | + $match_price_range_higher = $result['profile']['match_price_range_higher']; | |
| 1121 | + } | |
| 1122 | + | |
| 1123 | + if ( | |
| 1124 | + $match_price_range_lower != '' && $match_price_range_higher != '' | |
| 1125 | + ) | |
| 1126 | + { | |
| 1127 | + $columns['maximum_price_range'] = $match_price_range_lower . ' - ' . $match_price_range_higher; | |
| 1128 | + } | |
| 1129 | + } | |
| 1130 | + } | |
| 1131 | + | |
| 1132 | + $columns['currency'] = ( isset($result['profile']['currency']) ? $result['profile']['currency'] : 'GBP' ); | |
| 1133 | + | |
| 1134 | + break; | |
| 1135 | + } | |
| 1136 | + case "residential-lettings": | |
| 1137 | + { | |
| 1138 | + $columns['maximum_rent'] = ( isset($result['profile']['max_rent']) ? $result['profile']['max_rent'] : '' ); | |
| 1139 | + $columns['currency'] = ( isset($result['profile']['currency']) ? $result['profile']['currency'] : 'GBP' ); | |
| 1140 | + break; | |
| 1141 | + } | |
| 1142 | + } | |
| 1143 | + if ( $department == 'residential-sales' || $department == 'residential-lettings' ) | |
| 1144 | + { | |
| 1145 | + $columns['minimum_bedrooms'] = ( isset($result['profile']['min_beds']) ? $result['profile']['min_beds'] : '' ); | |
| 1146 | + | |
| 1147 | + $output_types = array(); | |
| 1148 | + if ( isset($result['profile']['property_types']) && is_array($result['profile']['property_types']) && !empty($result['profile']['property_types']) ) | |
| 1149 | + { | |
| 1150 | + foreach ( $result['profile']['property_types'] as $profile_type ) | |
| 1151 | + { | |
| 1152 | + if ( is_scalar( $profile_type ) && isset( $property_types[$profile_type] ) ) { | |
| 1153 | + $output_types[] = $property_types[$profile_type]; | |
| 1154 | + } | |
| 1155 | + } | |
| 1156 | + } | |
| 1157 | + $columns['property_types'] = implode(", ", $output_types); | |
| 1158 | + } | |
| 1159 | + } | |
| 1160 | + | |
| 1161 | + $output_locations = array(); | |
| 1162 | + if ( isset($result['profile']['locations']) && is_array($result['profile']['locations']) && !empty($result['profile']['locations']) ) | |
| 1163 | + { | |
| 1164 | + foreach ( $result['profile']['locations'] as $profile_location ) | |
| 1165 | + { | |
| 1166 | + if ( is_scalar( $profile_location ) && isset( $locations[$profile_location] ) ) { | |
| 1167 | + $output_locations[] = $locations[$profile_location]; | |
| 1168 | + } | |
| 1169 | + } | |
| 1170 | + } | |
| 1171 | + $columns['locations'] = implode(", ", $output_locations); | |
| 1172 | + | |
| 1173 | + $columns['additional_requirements'] = isset($result['profile']['notes']) ? $result['profile']['notes'] : ''; | |
| 1174 | + | |
| 1175 | + // Keep the historical raw POST value as the filter contract for extensions. | |
| 1176 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Export nonce and capability are verified by export() before this private formatter is called; the raw value is retained for extension compatibility. | |
| 1177 | + $columns = apply_filters( 'propertyhive_export_applicant_list_row_data', $columns, $_POST, $result['contact_id'], $result['applicant_profile_id'] ); | |
| 1178 | + | |
| 1179 | + fputcsv( $df, $columns, ',', '"', '' ); | |
| 1180 | + } | |
| 1181 | + fclose($df); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closes the php://output CSV stream. | |
| 1182 | + | |
| 1183 | + return ob_get_clean(); | |
| 1184 | + } | |
| 1185 | + | |
| 1186 | + public function export() | |
| 1187 | + { | |
| 1188 | + if ( !isset( $_POST['ph_applicant_export_nonce'] ) || ! check_admin_referer( 'ph_applicant_export', 'ph_applicant_export_nonce', false ) ) | |
| 1189 | + { | |
| 1190 | + wp_die( esc_html__( 'Invalid request (nonce failure)', 'propertyhive' ), 403 ); | |
| 1191 | + } | |
| 1192 | + | |
| 1193 | + if ( !current_user_can( 'manage_propertyhive' ) ) | |
| 1194 | + { | |
| 1195 | + wp_die( esc_html__( 'Insufficient permissions', 'propertyhive' ), 403 ); | |
| 1196 | + } | |
| 1197 | + | |
| 1198 | + $filename = 'applicant-list-' . gmdate("YmdHis") . '.csv'; | |
| 1199 | + | |
| 1200 | + // disable caching | |
| 1201 | + $now = gmdate("D, d M Y H:i:s"); | |
| 1202 | + header("Expires: Tue, 03 Jul 2001 06:00:00 GMT"); | |
| 1203 | + header("Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate"); | |
| 1204 | + header("Last-Modified: {$now} GMT"); | |
| 1205 | + | |
| 1206 | + // force download | |
| 1207 | + header("Content-Type: application/force-download"); | |
| 1208 | + header("Content-Type: application/octet-stream"); | |
| 1209 | + header("Content-Type: application/download"); | |
| 1210 | + | |
| 1211 | + // disposition / encoding on response body | |
| 1212 | + header("Content-Disposition: attachment;filename={$filename}"); | |
| 1213 | + header("Content-Transfer-Encoding: binary"); | |
| 1214 | + | |
| 1215 | + $results = $this->generate_results(); | |
| 1216 | + | |
| 1217 | + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Attachment response is CSV encoded by fputcsv, not HTML; HTML escaping would corrupt exported values. | |
| 1218 | + echo $this->array_2_csv($results); | |
| 1219 | + | |
| 1220 | + die(); | |
| 1221 | + } | |
| 676 | 1222 | } |
| 677 | 1223 | |
| 678 | -endif; | |
| 1224 | +endif; | |