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/class-ph-admin-post-types.php +1536 -153 1.4.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 * Post Types Admin
4 7 *
5 8 * @author PropertyHive
@@ -14,8 +17,9 @@
14 17
15 18 /**
16 19 * PH_Admin_Post_Types Class
17 20 */
21 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Legacy public global class PH_Admin_Post_Types; preserving the existing PH_* class name is required for plugin and extension compatibility.
18 22 class PH_Admin_Post_Types {
19 23
20 24 /**
21 25 * Constructor
@@ -22,8 +26,9 @@
22 26 */
23 27 public function __construct() {
24 28 add_action( 'admin_init', array( $this, 'include_post_type_handlers' ) );
25 29 add_filter( 'post_updated_messages', array( $this, 'post_updated_messages' ) );
30 + add_action( 'pre_get_posts', array( $this, 'refresh_property_office_filtering' ));
26 31 add_action( 'admin_print_scripts', array( $this, 'remove_month_filter' ) );
27 32 add_action( 'admin_print_scripts', array( $this, 'disable_autosave' ) );
28 33
29 34 // Filters
@@ -28,19 +33,277 @@
28 33
29 34 // Filters
30 35 add_action( 'restrict_manage_posts', array( $this, 'restrict_manage_posts' ) );
31 36 add_filter( 'request', array( $this, 'request_query' ) );
37 + add_filter( 'posts_join', array( $this, 'posts_join' ), 10, 2 );
38 + add_filter( 'posts_where', array( $this, 'posts_where' ), 10, 2 );
32 39
33 - add_filter( 'posts_where', array( $this, 'search_property_reference' ) );
34 -
35 40 // Status transitions
36 41 add_action( 'delete_post', array( $this, 'delete_post' ) );
37 42 add_action( 'wp_trash_post', array( $this, 'trash_post' ) );
38 43 add_action( 'untrash_post', array( $this, 'untrash_post' ) );
44 +
45 + add_action( 'admin_init', array( $this, 'handle_archive_action' ) );
46 + add_action( 'admin_init', array( $this, 'handle_unarchive_action' ) );
47 +
48 + $post_types = array('property', 'contact', 'appraisal', 'viewing', 'offer', 'sale', 'tenancy', 'key_date');
49 + $post_types = apply_filters( 'propertyhive_post_types_with_archive', $post_types );
50 +
51 + foreach ( $post_types as $post_type )
52 + {
53 + add_filter( 'views_edit-' . $post_type, array( $this, 'adjust_post_status_views' ) );
54 + add_filter( "bulk_actions-edit-$post_type", array( $this, 'register_bulk_action_move_to_archive' ) );
55 + add_filter( "handle_bulk_actions-edit-$post_type", array( $this, 'handle_bulk_action_archive_and_unarchive' ), 10, 3 );
56 + }
57 +
58 + add_filter( 'post_row_actions', array( $this, 'modify_post_row_actions_for_archived' ), 10, 2 );
59 + }
60 +
61 + /**
62 + * Read one scalar admin query value after WordPress unslashes and sanitizes it.
63 + *
64 + * Admin list filters are read-only, but their values still flow into markup and
65 + * query arguments. Returning an empty value for arrays keeps scalar filters
66 + * from accidentally accepting a malformed request while preserving the
67 + * existing empty-filter behaviour.
68 + *
69 + * @param string $key Query-string key.
70 + * @return string
71 + */
72 + private function get_admin_query_value( $key ) {
73 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
74 + if ( ! isset( $_GET[ $key ] ) || ! is_scalar( $_GET[ $key ] ) ) {
75 + return '';
76 + }
77 +
78 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Read-only admin list value is copied, unslashed immediately below, and sanitized before use; the sniffer reports the source assignment instead of the sanitization boundary.
79 + $raw_value = $_GET[ $key ];
80 + $raw_value = wp_unslash( (string) $raw_value );
81 +
82 + return sanitize_text_field( $raw_value );
83 + }
84 +
85 + public function handle_bulk_action_archive_and_unarchive($redirect_to, $doaction, $post_ids)
86 + {
87 + if ($doaction === 'move_to_archive')
88 + {
89 + foreach ($post_ids as $post_id)
90 + {
91 + // Check permissions
92 + if (!current_user_can('edit_post', $post_id)) {
93 + continue;
94 + }
95 +
96 + // Update the post status to 'archive'
97 + $updated_post = array(
98 + 'ID' => $post_id,
99 + 'post_status' => 'archive',
100 + );
101 +
102 + wp_update_post($updated_post);
103 + }
104 +
105 + $redirect_to = add_query_arg('bulk_archived_posts', count($post_ids), $redirect_to);
106 + }
107 + elseif ($doaction === 'unarchive')
108 + {
109 + foreach ($post_ids as $post_id)
110 + {
111 + // Check permissions
112 + if (!current_user_can('edit_post', $post_id)) {
113 + continue;
114 + }
115 +
116 + // Update the post status to 'publish' (or whatever the original status should be)
117 + $updated_post = array(
118 + 'ID' => $post_id,
119 + 'post_status' => 'publish',
120 + );
121 +
122 + wp_update_post($updated_post);
123 + }
124 +
125 + $redirect_to = add_query_arg('bulk_unarchived_posts', count($post_ids), $redirect_to);
126 + }
127 +
128 + return $redirect_to;
129 + }
130 +
131 + public function register_bulk_action_move_to_archive( $bulk_actions )
132 + {
133 + global $post_status;
134 +
135 + // Define our custom actions
136 + $custom_actions = array();
137 +
138 + if ($post_status === 'archive') {
139 + $custom_actions['unarchive'] = __('Unarchive', 'propertyhive');
140 + } else {
141 + $custom_actions['move_to_archive'] = __('Move to Archive', 'propertyhive');
142 + }
143 +
144 + // Check if 'trash' exists and insert custom actions before it
145 + if (isset($bulk_actions['trash']))
146 + {
147 + $new_actions = array();
148 + foreach ($bulk_actions as $key => $value) {
149 + if ($key === 'trash') {
150 + $new_actions = array_merge($new_actions, $custom_actions);
151 + }
152 + $new_actions[$key] = $value;
153 + }
154 + return $new_actions;
155 + }
156 + elseif (isset($bulk_actions['untrash']))
157 + {
158 + $new_actions = array();
159 + foreach ($bulk_actions as $key => $value) {
160 + if ($key === 'untrash') {
161 + $new_actions = array_merge($new_actions, $custom_actions);
162 + }
163 + $new_actions[$key] = $value;
164 + }
165 + return $new_actions;
166 + }
167 + else
168 + {
169 + // If 'trash' doesn't exist, append custom actions at the end
170 + return array_merge($bulk_actions, $custom_actions);
171 + }
172 + }
173 +
174 + public function modify_post_row_actions_for_archived( $actions, $post )
175 + {
176 + // Define the post types that can be archived
177 + $post_types = array('property', 'contact', 'appraisal', 'viewing', 'offer', 'sale', 'tenancy', 'key_date');
178 + $post_types = apply_filters('propertyhive_post_types_with_archive', $post_types);
179 +
180 + // Check if the current post type is in the allowed post types and if the post is archived
181 + if ( in_array($post->post_type, $post_types) && $post->post_status == 'archive' )
182 + {
183 + // Remove the "View" link
184 + if (isset($actions['view'])) {
185 + unset($actions['view']);
186 + }
187 +
188 + // Add the "Unarchive" link
189 + $unarchive_url = wp_nonce_url(admin_url('post.php?post=' . $post->ID . '&action=unarchive&return=archive'), 'unarchive-post_' . $post->ID);
190 + $actions['unarchive'] = '<a href="' . esc_url($unarchive_url) . '">' . __('Unarchive', 'propertyhive') . '</a>';
191 + }
192 +
193 + return $actions;
194 + }
195 +
196 + public function adjust_post_status_views( $views )
197 + {
198 + if (isset($views['archive']))
199 + {
200 + $archive = $views['archive'];
201 + unset($views['archive']);
202 +
203 + $new_views = array();
204 + $bin_exists = false;
205 +
206 + foreach ($views as $key => $view) {
207 + if ($key === 'trash') {
208 + $bin_exists = true;
209 + $new_views['archive'] = $archive;
210 + }
211 + $new_views[$key] = $view;
212 + }
213 +
214 + // Ensure 'archive' is added to the end if 'trash' is not present
215 + if (!$bin_exists) {
216 + $new_views['archive'] = $archive;
217 + }
218 +
219 + return $new_views;
220 + }
221 +
222 + return $views;
223 + }
224 +
225 + public function handle_archive_action()
226 + {
227 + // Check if the action and nonce are set and valid
228 + if ( !isset($_GET['action']) || $_GET['action'] !== 'archive_single' )
229 + return;
39 230
231 + $post_id = isset($_GET['post']) ? intval($_GET['post']) : 0;
232 + $post_type = get_post_type($post_id);
233 +
234 + if ( !wp_verify_nonce( ( isset( $_GET['_wpnonce'] ) && is_string( $_GET['_wpnonce'] ) ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '', 'archive-post_' . $post_id) )
235 + {
236 + wp_die(esc_html(__('Security check failed.', 'propertyhive')));
237 + }
238 +
239 + if ( !current_user_can('edit_post', $post_id) )
240 + {
241 + wp_die(esc_html(__('You do not have permission to edit this post.', 'propertyhive')));
242 + }
243 +
244 + // Update the post status to 'archive'
245 + $updated_post = array(
246 + 'ID' => $post_id,
247 + 'post_status' => 'archive',
248 + );
249 +
250 + $result = wp_update_post($updated_post, true);
251 +
252 + if ( is_wp_error($result) )
253 + {
254 + wp_die(esc_html(__('An error occurred while archiving the post.', 'propertyhive')));
255 + }
256 +
257 + // Redirect to the main list of contacts
258 + wp_safe_redirect(admin_url('edit.php?post_type=' . $post_type));
259 + exit;
260 + }
261 +
262 + public function handle_unarchive_action()
263 + {
264 + // Check if the action and nonce are set and valid
265 + if ( !isset($_GET['action']) || $_GET['action'] !== 'unarchive_single' )
266 + return;
40 267
41 - }
268 + $post_id = isset($_GET['post']) ? intval($_GET['post']) : 0;
269 + $post_type = get_post_type($post_id);
42 270
271 + if ( !wp_verify_nonce( ( isset( $_GET['_wpnonce'] ) && is_string( $_GET['_wpnonce'] ) ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '', 'unarchive-post_' . $post_id) )
272 + {
273 + wp_die(esc_html(__('Security check failed.', 'propertyhive')));
274 + }
275 +
276 + if ( !current_user_can('edit_post', $post_id) )
277 + {
278 + wp_die(esc_html(__('You do not have permission to edit this post.', 'propertyhive')));
279 + }
280 +
281 + // Update the post status to 'publish'
282 + $updated_post = array(
283 + 'ID' => $post_id,
284 + 'post_status' => 'publish',
285 + );
286 +
287 + $result = wp_update_post($updated_post, true);
288 +
289 + if ( is_wp_error($result) )
290 + {
291 + wp_die(esc_html(__('An error occurred while unarchiving the post.', 'propertyhive')));
292 + }
293 +
294 + // Redirect to the main list of contacts
295 + if ( isset($_GET['return']) && $_GET['return'] === 'archive' )
296 + {
297 + wp_safe_redirect(admin_url('edit.php?post_status=archive&post_type=' . get_post_type($post_id)));
298 + }
299 + else
300 + {
301 + wp_safe_redirect(admin_url('edit.php?post_type=' . get_post_type($post_id)));
302 + }
303 + exit;
304 + }
305 +
43 306 /**
44 307 * Conditonally load classes and functions only needed when viewing a post type.
45 308 */
46 309 public function include_post_type_handlers() {
@@ -50,11 +313,14 @@
50 313 include( 'post-types/class-ph-admin-cpt-property.php' );
51 314 include( 'post-types/class-ph-admin-cpt-contact.php' );
52 315 include( 'post-types/class-ph-admin-cpt-enquiry.php' );
53 316 include( 'post-types/class-ph-admin-cpt-office.php' );
317 + include( 'post-types/class-ph-admin-cpt-appraisal.php' );
54 318 include( 'post-types/class-ph-admin-cpt-viewing.php' );
55 319 include( 'post-types/class-ph-admin-cpt-offer.php' );
56 320 include( 'post-types/class-ph-admin-cpt-sale.php' );
321 + include( 'post-types/class-ph-admin-cpt-tenancy.php' );
322 + include( 'post-types/class-ph-admin-cpt-key-date.php' );
57 323 }
58 324
59 325 /**
60 326 * Change messages when a post type is updated.
@@ -66,19 +332,24 @@
66 332 global $post, $post_ID;
67 333
68 334 $messages['property'] = array(
69 335 0 => '', // Unused. Messages start at index 1.
70 - 1 => sprintf( __( 'Property updated. <a href="%s">View Property</a>', 'propertyhive' ), esc_url( get_permalink($post_ID) ) ),
336 + /* translators: %s: URL to view the property */
337 + 1 => sprintf( __( 'Property updated. <a href="%s">View property</a>', 'propertyhive' ), esc_url( get_permalink($post_ID) ) ),
71 338 2 => __( 'Custom field updated.', 'propertyhive' ),
72 339 3 => __( 'Custom field deleted.', 'propertyhive' ),
73 340 4 => __( 'Property updated.', 'propertyhive' ),
74 - 5 => isset($_GET['revision']) ? sprintf( __( 'Property restored to revision from %s', 'propertyhive' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
75 - 6 => sprintf( __( 'Property published. <a href="%s">View Property</a>', 'propertyhive' ), esc_url( get_permalink($post_ID) ) ),
341 + 5 => __( 'Revision restored.', 'propertyhive' ),
342 + /* translators: %s: URL to view the property */
343 + 6 => sprintf( __( 'Property published. <a href="%s">View property</a>', 'propertyhive' ), esc_url( get_permalink($post_ID) ) ),
76 344 7 => __( 'Property saved.', 'propertyhive' ),
77 - 8 => sprintf( __( 'Property submitted. <a target="_blank" href="%s">Preview Property</a>', 'propertyhive' ), esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),
78 - 9 => sprintf( __( 'Property scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview Property</a>', 'propertyhive' ),
345 + /* translators: %s: URL to preview the property */
346 + 8 => sprintf( __( 'Property submitted. <a target="_blank" href="%s">Preview property</a>', 'propertyhive' ), esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),
347 + /* translators: 1: formatted date, 2: URL to preview the property */
348 + 9 => sprintf( __( 'Property scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview property</a>', 'propertyhive' ),
79 349 date_i18n( __( 'M j, Y @ G:i', 'propertyhive' ), strtotime( $post->post_date ) ), esc_url( get_permalink($post_ID) ) ),
80 - 10 => sprintf( __( 'Property draft updated. <a target="_blank" href="%s">Preview Property</a>', 'propertyhive' ), esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),
350 + /* translators: %s: URL to preview the property */
351 + 10 => sprintf( __( 'Property draft updated. <a target="_blank" href="%s">Preview property</a>', 'propertyhive' ), esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),
81 352 );
82 353
83 354 $messages['contact'] = array(
84 355 0 => '', // Unused. Messages start at index 1.
@@ -85,12 +356,13 @@
85 356 1 => __( 'Contact updated.', 'propertyhive' ),
86 357 2 => __( 'Custom field updated.', 'propertyhive' ),
87 358 3 => __( 'Custom field deleted.', 'propertyhive' ),
88 359 4 => __( 'Contact updated.', 'propertyhive' ),
89 - 5 => isset($_GET['revision']) ? sprintf( __( 'Contact restored to revision from %s', 'propertyhive' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
360 + 5 => __( 'Revision restored.', 'propertyhive' ),
90 361 6 => __( 'Contact published.', 'propertyhive' ),
91 362 7 => __( 'Contact saved.', 'propertyhive' ),
92 363 8 => __( 'Contact submitted.', 'propertyhive' ),
364 + /* translators: 1: formatted date */
93 365 9 => sprintf( __( 'Contact scheduled for: <strong>%1$s</strong>.', 'propertyhive' ), date_i18n( __( 'M j, Y @ G:i', 'propertyhive' ), strtotime( $post->post_date ) )),
94 366 10 => __( 'Contact draft updated.', 'propertyhive' ),
95 367 );
96 368
@@ -99,12 +371,13 @@
99 371 1 => __( 'Office updated.', 'propertyhive' ),
100 372 2 => __( 'Custom field updated.', 'propertyhive' ),
101 373 3 => __( 'Custom field deleted.', 'propertyhive' ),
102 374 4 => __( 'Office updated.', 'propertyhive' ),
103 - 5 => isset($_GET['revision']) ? sprintf( __( 'Office restored to revision from %s', 'propertyhive' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
375 + 5 => __( 'Revision restored.', 'propertyhive' ),
104 376 6 => sprintf( __( 'Office published.', 'propertyhive' ), esc_url( get_permalink($post_ID) ) ),
105 377 7 => __( 'Office saved.', 'propertyhive' ),
106 378 8 => sprintf( __( 'Office submitted.', 'propertyhive' ), esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),
379 + /* translators: 1: formatted date */
107 380 9 => sprintf( __( 'Office scheduled for: <strong>%1$s</strong>.', 'propertyhive' ),
108 381 date_i18n( __( 'M j, Y @ G:i', 'propertyhive' ), strtotime( $post->post_date ) ), esc_url( get_permalink($post_ID) ) ),
109 382 10 => sprintf( __( 'Office draft updated. ', 'propertyhive' ), esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),
110 383 );
@@ -114,12 +387,13 @@
114 387 1 => sprintf( __( 'Enquiry updated.', 'propertyhive' ), esc_url( get_permalink($post_ID) ) ),
115 388 2 => __( 'Custom field updated.', 'propertyhive' ),
116 389 3 => __( 'Custom field deleted.', 'propertyhive' ),
117 390 4 => __( 'Enquiry updated.', 'propertyhive' ),
118 - 5 => isset($_GET['revision']) ? sprintf( __( 'Enquiry restored to revision from %s', 'propertyhive' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
391 + 5 => __( 'Revision restored.', 'propertyhive' ),
119 392 6 => sprintf( __( 'Enquiry published.', 'propertyhive' ), esc_url( get_permalink($post_ID) ) ),
120 393 7 => __( 'Enquiry saved.', 'propertyhive' ),
121 394 8 => sprintf( __( 'Enquiry submitted.', 'propertyhive' ), esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),
395 + /* translators: 1: formatted date */
122 396 9 => sprintf( __( 'Enquiry scheduled for: <strong>%1$s</strong>.', 'propertyhive' ),
123 397 date_i18n( __( 'M j, Y @ G:i', 'propertyhive' ), strtotime( $post->post_date ) ), esc_url( get_permalink($post_ID) ) ),
124 398 10 => sprintf( __( 'Enquiry draft updated.', 'propertyhive' ), esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),
125 399 );
@@ -131,10 +405,13 @@
131 405 * Remove month filter from some property hive pages
132 406 */
133 407 public function remove_month_filter() {
134 408 global $typenow;
135 -
136 - if ($typenow == 'property' || $typenow == 'contact' || $typenow == 'viewing' || $typenow == 'offer' || $typenow == 'sale')
409 +
410 + $post_types_to_hide_months_dropdown = array('property', 'contact', 'enquiry', 'appraisal', 'viewing', 'offer', 'sale', 'tenancy', 'key_date');
411 + $post_types_to_hide_months_dropdown = apply_filters( 'propertyhive_post_types_to_hide_months_dropdown', $post_types_to_hide_months_dropdown );
412 +
413 + if ( in_array($typenow, $post_types_to_hide_months_dropdown) )
137 414 {
138 415 add_filter('months_dropdown_results', '__return_empty_array');
139 416 }
140 417 }
@@ -168,8 +445,26 @@
168 445 break;
169 446 case 'enquiry' :
170 447 $this->enquiry_filters();
171 448 break;
449 + case 'appraisal' :
450 + $this->appraisal_filters();
451 + break;
452 + case 'viewing' :
453 + $this->viewing_filters();
454 + break;
455 + case 'offer' :
456 + $this->offer_filters();
457 + break;
458 + case 'sale' :
459 + $this->sale_filters();
460 + break;
461 + case 'tenancy' :
462 + $this->tenancy_filters();
463 + break;
464 + case 'key_date' :
465 + $this->key_date_filters();
466 + break;
172 467 default :
173 468 break;
174 469 }
175 470 }
@@ -183,13 +478,15 @@
183 478 // Department filtering
184 479 $output = '';
185 480
186 481 $output .= $this->property_department_filter();
482 + $output .= $this->property_marketing_filter();
187 483 $output .= $this->property_availability_filter();
188 484 $output .= $this->property_location_filter();
189 485 $output .= $this->property_office_filter();
190 - $output .= $this->property_negotiator_filter();
486 + $output .= $this->negotiator_filter();
191 487
488 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
192 489 echo apply_filters( 'propertyhive_property_filters', $output );
193 490 }
194 491
195 492 /**
@@ -196,41 +493,30 @@
196 493 * Show a property department filter box
197 494 */
198 495 public function property_department_filter() {
199 496 global $wp_query;
497 +
498 + $departments = ph_get_departments();
499 +
500 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
501 + $requested_value = isset( $_GET['_department'] ) && is_string( $_GET['_department'] ) ? sanitize_text_field( wp_unslash( $_GET['_department'] ) ) : '';
502 + $selected_department = array_key_exists( $requested_value, $departments ) ? $requested_value : '';
200 503
201 504 // Department filtering
202 505 $output = '<select name="_department" id="dropdown_property_department">';
203 506
204 - $output .= '<option value="">' . __( 'All Departments', 'propertyhive' ) . '</option>';
205 -
206 - if ( get_option( 'propertyhive_active_departments_sales' ) == 'yes' )
507 + $output .= '<option value="">' . esc_html__( 'All Departments', 'propertyhive' ) . '</option>';
508 +
509 + foreach ( $departments as $key => $value )
207 510 {
208 - $output .= '<option value="residential-sales"';
209 - if ( isset( $_GET['_department'] ) && ! empty( $_GET['_department'] ) )
511 + if ( get_option( 'propertyhive_active_departments_' . str_replace("residential-", "", $key) ) == 'yes' )
210 512 {
211 - $output .= selected( 'residential-sales', $_GET['_department'], false );
513 + $output .= '<option value="' . esc_attr($key) . '"';
514 + $output .= selected( $key, $selected_department, false );
515 + $output .= '>' . esc_html($value) . '</option>';
212 516 }
213 - $output .= '>' . __( 'Residential Sales', 'propertyhive' ) . '</option>';
214 517 }
215 - if ( get_option( 'propertyhive_active_departments_lettings' ) == 'yes' )
216 - {
217 - $output .= '<option value="residential-lettings"';
218 - if ( isset( $_GET['_department'] ) && ! empty( $_GET['_department'] ) )
219 - {
220 - $output .= selected( 'residential-lettings', $_GET['_department'], false );
221 - }
222 - $output .= '>' . __( 'Residential Lettings', 'propertyhive' ) . '</option>';
223 - }
224 - if ( get_option( 'propertyhive_active_departments_commercial' ) == 'yes' )
225 - {
226 - $output .= '<option value="commercial"';
227 - if ( isset( $_GET['_department'] ) && ! empty( $_GET['_department'] ) )
228 - {
229 - $output .= selected( 'commercial', $_GET['_department'], false );
230 - }
231 - $output .= '>' . __( 'Commercial', 'propertyhive' ) . '</option>';
232 - }
518 +
233 519 $output .= '</select>';
234 520
235 521 return $output;
236 522 }
@@ -243,9 +529,9 @@
243 529
244 530 // Department filtering
245 531 $output = '<select name="_office_id" id="dropdown_property_office_id">';
246 532
247 - $output .= '<option value="">' . __( 'All Offices', 'propertyhive' ) . '</option>';
533 + $output .= '<option value="">' . esc_html__( 'All Offices', 'propertyhive' ) . '</option>';
248 534
249 535 $args = array(
250 536 'post_type' => 'office',
251 537 'nopaging' => true,
@@ -259,14 +545,16 @@
259 545 while ($office_query->have_posts())
260 546 {
261 547 $office_query->the_post();
262 548
263 - $output .= '<option value="' . $post->ID . '"';
549 + $output .= '<option value="' . esc_attr($post->ID) . '"';
550 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
264 551 if ( isset( $_GET['_office_id'] ) && ! empty( $_GET['_office_id'] ) )
265 552 {
266 - $output .= selected( $post->ID, $_GET['_office_id'], false );
553 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
554 + $output .= selected( $post->ID, (int)$_GET['_office_id'], false );
267 555 }
268 - $output .= '>' . get_the_title() . '</option>';
556 + $output .= '>' . esc_html(get_the_title()) . '</option>';
269 557 }
270 558 }
271 559
272 560 wp_reset_postdata();
@@ -276,32 +564,51 @@
276 564 return $output;
277 565 }
278 566
279 567 /**
280 - * Show a property negotiator filter box
568 + * Show a negotiator filter box
281 569 */
282 - public function property_negotiator_filter() {
283 - global $wp_query, $post;
284 -
285 - $selected = '';
286 - if ( isset( $_GET['_negotiator_id'] ) && ! empty( $_GET['_negotiator_id'] ) )
287 - {
288 - $selected = $_GET['_negotiator_id'];
289 - }
290 -
291 - $args = array(
570 + public function negotiator_filter() {
571 +
572 + return wp_dropdown_users(array(
292 573 'name' => '_negotiator_id',
293 574 'id' => 'dropdown_property_negotiator_id',
294 - 'show_option_all' => __( 'All Negotiators', 'propertyhive' ),
295 - 'selected' => $selected,
575 + 'show_option_all' => esc_html__( 'All Negotiators', 'propertyhive' ),
576 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
577 + 'selected' => empty( $_GET['_negotiator_id'] ) ? '' : (int)$_GET['_negotiator_id'],
296 578 'echo' => false,
297 - 'role__not_in' => array('property_hive_contact')
298 - );
299 - $output = wp_dropdown_users($args);
579 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Legacy Property Negotiator compatibility filter; existing role filters depend on this exact public hook name.
580 + 'role__not_in' => apply_filters( 'property_negotiator_exclude_roles', array('property_hive_contact', 'subscriber') )
581 + ));
582 + }
300 583
301 - return $output;
302 - }
584 + /**
585 + * Show a date range selector
586 + */
587 + public function date_range_filter() {
303 588
589 + $date_range_label = $this->get_admin_query_value( '_date_range_label' );
590 + $date_range_label = empty( $date_range_label ) ? __( 'Any Time', 'propertyhive' ) : $date_range_label;
591 +
592 + // The date picker doesn't have a concept of 'Any Time', so valid dates must be used
593 + // I've used the last and first date of the month (reversed) as it's a range that is not selectable, but is within the current month
594 + // If I used an already labelled date range (e.g. 'Today'), it would show as 'Today' when selected
595 + // If I use a nearby date range (e.g. 'Yesterday'), if someone actually selected that range it would show as 'Any Time'
596 + // If I use a unlikely date range (e.g. 01-01-1970 - 31-12-2070), the custom date range picker would open showing Jan 1970.
597 + $date_range_from = $this->get_admin_query_value( '_date_range_from' );
598 + $date_range_from = empty( $date_range_from ) ? gmdate('Y-m-d', strtotime('last day of this month')) : $date_range_from;
599 + $date_range_to = $this->get_admin_query_value( '_date_range_to' );
600 + $date_range_to = empty( $date_range_to ) ? gmdate('Y-m-d', strtotime('first day of this month')) : $date_range_to;
601 +
602 + return "
603 + <select name='_date_range_label' id='date_range' style='max-width:25rem;'>
604 + <option selected>" . esc_html($date_range_label) . "</option>
605 + <select/>
606 + <input type='hidden' name='_date_range_from' id='date_range_from' value='" . esc_attr($date_range_from) . "'>
607 + <input type='hidden' name='_date_range_to' id='date_range_to' value='" . esc_attr($date_range_to) . "'>
608 + ";
609 + }
610 +
304 611 /**
305 612 * Show a property location filter box
306 613 */
307 614 public function property_location_filter() {
@@ -314,9 +621,9 @@
314 621 $args = array(
315 622 'hide_empty' => false,
316 623 'parent' => 0
317 624 );
318 - $terms = get_terms( 'location', $args );
625 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) );
319 626
320 627 if ( !empty( $terms ) && !is_wp_error( $terms ) )
321 628 {
322 629 foreach ($terms as $term)
@@ -326,9 +633,9 @@
326 633 $args = array(
327 634 'hide_empty' => false,
328 635 'parent' => $term->term_id
329 636 );
330 - $subterms = get_terms( 'location', $args );
637 + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) );
331 638
332 639 if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
333 640 {
334 641 foreach ($subterms as $term)
@@ -338,9 +645,9 @@
338 645 $args = array(
339 646 'hide_empty' => false,
340 647 'parent' => $term->term_id
341 648 );
342 - $subsubterms = get_terms( 'location', $args );
649 + $subsubterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) );
343 650
344 651 if ( !empty( $subsubterms ) && !is_wp_error( $subsubterms ) )
345 652 {
346 653 foreach ($subsubterms as $term)
@@ -352,20 +659,22 @@
352 659 }
353 660 }
354 661 }
355 662
356 - $output .= '<option value="">' . __( 'All Locations', 'propertyhive' ) . '</option>';
663 + $output .= '<option value="">' . esc_html(__( 'All Locations', 'propertyhive' )) . '</option>';
357 664
358 665 if ( !empty($options) )
359 666 {
360 667 foreach ( $options as $value => $label )
361 668 {
362 - $output .= '<option value="' . $value . '"';
669 + $output .= '<option value="' . esc_attr($value) . '"';
670 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
363 671 if ( isset( $_GET['_location_id'] ) && ! empty( $_GET['_location_id'] ) )
364 672 {
365 - $output .= selected( $value, $_GET['_location_id'], false );
673 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
674 + $output .= selected( $value, (int)$_GET['_location_id'], false );
366 675 }
367 - $output .= '>' . $label . '</option>';
676 + $output .= '>' . esc_html($label) . '</option>';
368 677 }
369 678 }
370 679
371 680 $output .= '</select>';
@@ -378,9 +687,9 @@
378 687 */
379 688 public function property_availability_filter() {
380 689 global $wp_query, $post;
381 690
382 - // Department filtering
691 + // Availability filtering
383 692 $output = '<select name="_availability_id" id="dropdown_property_availability_id">';
384 693
385 694 $options = array( );
386 695 $args = array(
@@ -386,9 +695,9 @@
386 695 $args = array(
387 696 'hide_empty' => false,
388 697 'parent' => 0
389 698 );
390 - $terms = get_terms( 'availability', $args );
699 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'availability' ) ) );
391 700
392 701 if ( !empty( $terms ) && !is_wp_error( $terms ) )
393 702 {
394 703 foreach ($terms as $term)
@@ -396,20 +705,22 @@
396 705 $options[$term->term_id] = $term->name;
397 706 }
398 707 }
399 708
400 - $output .= '<option value="">' . __( 'All Availabilities', 'propertyhive' ) . '</option>';
709 + $output .= '<option value="">' . esc_html(__( 'All Availabilities', 'propertyhive' )) . '</option>';
401 710
402 711 if ( !empty($options) )
403 712 {
404 713 foreach ( $options as $value => $label )
405 714 {
406 - $output .= '<option value="' . $value . '"';
715 + $output .= '<option value="' . esc_attr($value) . '"';
716 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
407 717 if ( isset( $_GET['_availability_id'] ) && ! empty( $_GET['_availability_id'] ) )
408 718 {
409 - $output .= selected( $value, $_GET['_availability_id'], false );
719 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
720 + $output .= selected( $value, (int)$_GET['_availability_id'], false );
410 721 }
411 - $output .= '>' . $label . '</option>';
722 + $output .= '>' . esc_html($label) . '</option>';
412 723 }
413 724 }
414 725
415 726 $output .= '</select>';
@@ -415,8 +726,57 @@
415 726 $output .= '</select>';
416 727
417 728 return $output;
418 729 }
730 +
731 + /**
732 + * Show a property marketing filter box
733 + */
734 + public function property_marketing_filter() {
735 + global $wp_query, $post;
736 +
737 + // Availability filtering
738 + $output = '<select name="_marketing" id="dropdown_property_marketing">';
739 +
740 + $output .= '<option value="">' . esc_html__( 'All Marketing Statuses', 'propertyhive' ) . '</option>';
741 +
742 + $options = array(
743 + 'on_market' => __( 'On Market Only', 'propertyhive' ),
744 + 'off_market' => __( 'Not On Market Only', 'propertyhive' ),
745 + 'featured' => __( 'Featured Only', 'propertyhive' ),
746 + );
747 +
748 + $args = array(
749 + 'hide_empty' => false,
750 + 'parent' => 0
751 + );
752 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'marketing_flag' ) ) );
753 +
754 + if ( !empty( $terms ) && !is_wp_error( $terms ) )
755 + {
756 + foreach ($terms as $term)
757 + {
758 + $options['marketing_flag_' . $term->term_id] = __( 'Has Marketing Flag', 'propertyhive') . ' - ' . $term->name;
759 + }
760 + }
761 +
762 + $options = apply_filters( 'propertyhive_property_filter_marketing_options', $options );
763 + $selected_marketing = $this->get_admin_query_value( '_marketing' );
764 +
765 + foreach ( $options as $key => $value )
766 + {
767 + $output .= '<option value="' . esc_attr($key) . '"';
768 + if ( ! empty( $selected_marketing ) )
769 + {
770 + $output .= selected( $key, $selected_marketing, false );
771 + }
772 + $output .= '>' . esc_html($value) . '</option>';
773 + }
774 +
775 + $output .= '</select>';
776 +
777 + return $output;
778 + }
419 779
420 780 /**
421 781 * Show a contact filter box
422 782 */
@@ -421,8 +781,12 @@
421 781 * Show a contact filter box
422 782 */
423 783 public function contact_filters() {
424 784 global $wp_query;
785 +
786 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
787 + $requested_value = isset( $_GET['_contact_type'] ) && is_string( $_GET['_contact_type'] ) ? sanitize_text_field( wp_unslash( $_GET['_contact_type'] ) ) : '';
788 + $selected_contact_type = in_array( $requested_value, array( 'owner', 'potentialowner', 'applicant', 'hotapplicant', 'thirdparty' ), true ) ? $requested_value : '';
425 789
426 790 // Type filtering
427 791 $options = array();
428 792
@@ -427,33 +791,38 @@
427 791 $options = array();
428 792
429 793 // Owners
430 794 $option = '<option value="owner"';
431 - if ( isset( $_GET['_contact_type'] ) && ! empty( $_GET['_contact_type'] ) )
432 - {
433 - $option .= selected( 'owner', $_GET['_contact_type'], false );
434 - }
435 - $option .= '>' . __( 'Owners and Landlords', 'propertyhive' ) . '</option>';
795 + $option .= selected( 'owner', $selected_contact_type, false );
796 + $option .= '>' . esc_html(__( 'Owners and Landlords', 'propertyhive' )) . '</option>';
436 797
437 798 $options[] = $option;
438 799
800 + // Potential Owners
801 + $option = '<option value="potentialowner"';
802 + $option .= selected( 'potentialowner', $selected_contact_type, false );
803 + $option .= '>' . esc_html(__( 'Potential Owners and Landlords', 'propertyhive' )) . '</option>';
804 +
805 + $options[] = $option;
806 +
439 807 // Applicants
440 808 $option = '<option value="applicant"';
441 - if ( isset( $_GET['_contact_type'] ) && ! empty( $_GET['_contact_type'] ) )
442 - {
443 - $option .= selected( 'applicant', $_GET['_contact_type'], false );
444 - }
445 - $option .= '>' . __( 'Applicants', 'propertyhive' ) . '</option>';
809 + $option .= selected( 'applicant', $selected_contact_type, false );
810 + $option .= '>' . esc_html(__( 'Applicants', 'propertyhive' )) . '</option>';
446 811
447 812 $options[] = $option;
448 813
814 + // Hot Applicants
815 + $option = '<option value="hotapplicant"';
816 + $option .= selected( 'hotapplicant', $selected_contact_type, false );
817 + $option .= '>- ' . esc_html(__( 'Hot Applicants', 'propertyhive' )) . '</option>';
818 +
819 + $options[] = $option;
820 +
449 821 // Third Parties
450 822 $option = '<option value="thirdparty"';
451 - if ( isset( $_GET['_contact_type'] ) && ! empty( $_GET['_contact_type'] ) )
452 - {
453 - $option .= selected( 'thirdparty', $_GET['_contact_type'], false );
454 - }
455 - $option .= '>' . __( 'Third Party Contacts', 'propertyhive' ) . '</option>';
823 + $option .= selected( 'thirdparty', $selected_contact_type, false );
824 + $option .= '>' . esc_html(__( 'Third Party Contacts', 'propertyhive' )) . '</option>';
456 825
457 826 $options[] = $option;
458 827
459 828 $options = apply_filters( 'propertyhive_contact_filter_options', $options );
@@ -462,9 +831,9 @@
462 831 if (count($options) > 1)
463 832 {
464 833 $output = '<select name="_contact_type" id="dropdown_contact_type">';
465 834
466 - $output .= '<option value="">' . __( 'Show all contact types', 'propertyhive' ) . '</option>';
835 + $output .= '<option value="">' . esc_html(__( 'Show all contact types', 'propertyhive' )) . '</option>';
467 836
468 837 $output .= implode("", $options);
469 838
470 839 $output .= '</select>';
@@ -469,9 +838,12 @@
469 838
470 839 $output .= '</select>';
471 840 }
472 841
473 - echo $output;
842 + $output .= $this->date_range_filter('Date Created');
843 +
844 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
845 + echo apply_filters( 'propertyhive_contact_filters', $output );
474 846 }
475 847
476 848 /**
477 849 * Show an enquiry filter box
@@ -481,11 +853,15 @@
481 853
482 854 // Department filtering
483 855 $output = '';
484 856
857 + $output .= $this->date_range_filter();
485 858 $output .= $this->enquiry_status_filter();
486 859 $output .= $this->enquiry_source_filter();
860 + $output .= $this->enquiry_office_filter();
861 + $output .= $this->enquiry_negotiator_filter();
487 862
863 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
488 864 echo apply_filters( 'propertyhive_enquiry_filters', $output );
489 865 }
490 866
491 867 /**
@@ -492,58 +868,517 @@
492 868 * Show an enquiry status filter box
493 869 */
494 870 public function enquiry_status_filter() {
495 871 global $wp_query;
872 +
873 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
874 + $requested_value = isset( $_GET['_status'] ) && is_string( $_GET['_status'] ) ? sanitize_text_field( wp_unslash( $_GET['_status'] ) ) : '';
875 + $selected_status = in_array( $requested_value, array( 'all', 'open', 'closed' ), true ) ? $requested_value : '';
876 +
877 + // Status filtering
878 + $output = '<select name="_status" id="dropdown_enquiry_status">
879 + <option value="all"' . selected( 'all', $selected_status, false ) . '>All</option>';
880 +
881 + $enquiry_statuses = ph_get_enquiry_statuses();
882 +
883 + foreach ( $enquiry_statuses as $status => $display_status )
884 + {
885 + $output .= '<option value="' . esc_attr($status) . '"';
886 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
887 + if ( $status == $selected_status || ( $status == 'open' && ( !isset($_GET['_status']) || empty($_GET['_status']) ) ) )
888 + {
889 + $output .= ' selected';
890 + }
891 + $output .= selected( $status, $selected_status, false );
892 + $output .= '>' . esc_html($display_status) . '</option>';
893 + }
894 +
895 + $output .= '</select>';
896 +
897 + return $output;
898 + }
899 +
900 + /**
901 + * Show an enquiry source filter box
902 + */
903 + public function enquiry_source_filter() {
904 + global $wp_query;
905 +
906 + $sources = array(
907 + 'office' => __( 'Office', 'propertyhive' ),
908 + 'website' => __( 'Website', 'propertyhive' )
909 + );
910 +
911 + $sources = apply_filters( 'propertyhive_enquiry_sources', $sources );
912 +
913 + asort($sources);
496 914
497 915 // Status filtering
498 - $output = '<select name="_status" id="dropdown_enquiry_status">';
916 + $output = '<select name="_source" id="dropdown_enquiry_source">';
917 + $selected_source = $this->get_admin_query_value( '_source' );
499 918
500 - $output .= '<option value="open"';
501 - if ( isset( $_GET['_status'] ) && ! empty( $_GET['_status'] ) )
919 + $output .= '<option value="">' . esc_html__( 'Show all sources', 'propertyhive' ) . '</option>';
920 +
921 + foreach ( $sources as $key => $value )
502 922 {
503 - $output .= selected( 'open', $_GET['_status'], false );
923 + $output .= '<option value="' . esc_attr($key) . '"';
924 + if ( ! empty( $selected_source ) )
925 + {
926 + $output .= selected( $key, $selected_source, false );
927 + }
928 + $output .= '>' . esc_html( $value ) . '</option>';
504 929 }
505 - $output .= '>' . __( 'Open', 'propertyhive' ) . '</option>';
506 - $output .= '<option value="closed"';
507 - if ( isset( $_GET['_status'] ) && ! empty( $_GET['_status'] ) )
930 +
931 + $output .= '</select>';
932 +
933 + return $output;
934 + }
935 +
936 + /**
937 + * Show an enquiry office filter box
938 + */
939 + public function enquiry_office_filter() {
940 + global $wp_query, $post;
941 +
942 + // Department filtering
943 + $output = '<select name="_office_id" id="dropdown_enquiry_office_id">';
944 +
945 + $output .= '<option value="">' . esc_html__( 'All Offices', 'propertyhive' ) . '</option>';
946 +
947 + $args = array(
948 + 'post_type' => 'office',
949 + 'nopaging' => true,
950 + 'orderby' => 'title',
951 + 'order' => 'ASC'
952 + );
953 + $office_query = new WP_Query($args);
954 +
955 + if ($office_query->have_posts())
956 + {
957 + while ($office_query->have_posts())
508 958 {
509 - $output .= selected( 'closed', $_GET['_status'], false );
959 + $office_query->the_post();
960 +
961 + $output .= '<option value="' . esc_attr($post->ID) . '"';
962 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
963 + if ( isset( $_GET['_office_id'] ) && ! empty( $_GET['_office_id'] ) )
964 + {
965 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
966 + $output .= selected( $post->ID, (int)$_GET['_office_id'], false );
967 + }
968 + $output .= '>' . esc_html(get_the_title()) . '</option>';
510 969 }
511 - $output .= '>' . __( 'Closed', 'propertyhive' ) . '</option>';
512 -
970 + }
971 +
972 + wp_reset_postdata();
973 +
513 974 $output .= '</select>';
514 975
515 976 return $output;
516 977 }
517 -
978 +
518 979 /**
519 - * Show an enquiry source filter box
980 + * Show an enquiry negotiator filter box
520 981 */
521 - public function enquiry_source_filter() {
982 + public function enquiry_negotiator_filter() {
983 + return wp_dropdown_users(array(
984 + 'name' => '_negotiator_id',
985 + 'id' => 'dropdown_enquiry_negotiator_id',
986 + 'show_option_all' => esc_html__( 'All Negotiators', 'propertyhive' ),
987 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
988 + 'selected' => empty( $_GET['_negotiator_id'] ) ? '' : (int)$_GET['_negotiator_id'],
989 + 'echo' => false,
990 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Legacy Property Negotiator compatibility filter; existing role filters depend on this exact public hook name.
991 + 'role__not_in' => apply_filters( 'property_negotiator_exclude_roles', array('property_hive_contact', 'subscriber') )
992 + ));
993 + }
994 +
995 + /**
996 + * Show am appraisal filter box
997 + */
998 + public function appraisal_filters() {
522 999 global $wp_query;
523 1000
1001 + $output = '';
1002 +
1003 + $output .= $this->appraisal_status_filter();
1004 + $output .= $this->negotiator_filter();
1005 + $output .= $this->date_range_filter();
1006 +
1007 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
1008 + echo apply_filters( 'propertyhive_appraisal_filters', $output );
1009 + }
1010 +
1011 + /**
1012 + * Show an appraisal status filter box
1013 + */
1014 + public function appraisal_status_filter() {
1015 + global $wp_query;
1016 +
1017 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1018 + $requested_value = isset( $_GET['_status'] ) && is_string( $_GET['_status'] ) ? sanitize_text_field( wp_unslash( $_GET['_status'] ) ) : '';
1019 + $selected_status = in_array( $requested_value, array( 'pending', 'carried_out', 'won', 'lost', 'instructed', 'cancelled' ), true ) ? $requested_value : '';
1020 +
524 1021 // Status filtering
525 - $output = '<select name="_source" id="dropdown_enquiry_source">';
1022 + $output = '<select name="_status" id="dropdown_appraisal_status">';
526 1023
527 - $output .= '<option value="">' . __( 'Show all sources', 'propertyhive' ) . '</option>';
1024 + $output .= '<option value="">' . esc_html__( 'All Statuses', 'propertyhive' ) . '</option>';
1025 +
1026 + $output .= '<option value="pending"';
1027 + $output .= selected( 'pending', $selected_status, false );
1028 + $output .= '>' . esc_html(__( 'Pending', 'propertyhive' )) . '</option>';
1029 +
1030 + $output .= '<option value="carried_out"';
1031 + $output .= selected( 'carried_out', $selected_status, false );
1032 + $output .= '>' . esc_html(__( 'Carried Out', 'propertyhive' )) . '</option>';
1033 +
1034 + $output .= '<option value="won"';
1035 + $output .= selected( 'won', $selected_status, false );
1036 + $output .= '>- ' . esc_html(__( 'Won', 'propertyhive' )) . '</option>';
1037 +
1038 + $output .= '<option value="lost"';
1039 + $output .= selected( 'lost', $selected_status, false );
1040 + $output .= '>- ' . esc_html(__( 'Lost', 'propertyhive' )) . '</option>';
1041 +
1042 + $output .= '<option value="instructed"';
1043 + $output .= selected( 'instructed', $selected_status, false );
1044 + $output .= '>- ' . esc_html(__( 'Instructed', 'propertyhive' )) . '</option>';
1045 +
1046 + $output .= '<option value="cancelled"';
1047 + $output .= selected( 'cancelled', $selected_status, false );
1048 + $output .= '>' . esc_html(__( 'Cancelled', 'propertyhive' )) . '</option>';
528 1049
529 - $output .= '<option value="office"';
530 - if ( isset( $_GET['_source'] ) && ! empty( $_GET['_source'] ) )
1050 + $output .= '</select>';
1051 +
1052 + return $output;
1053 + }
1054 +
1055 + /**
1056 + * Show a viewing filter box
1057 + */
1058 + public function viewing_filters() {
1059 + global $wp_query;
1060 +
1061 + // Department filtering
1062 + $output = '';
1063 +
1064 + $output .= $this->viewing_status_filter();
1065 + $output .= $this->property_office_filter();
1066 + $output .= $this->negotiator_filter();
1067 + $output .= $this->date_range_filter();
1068 +
1069 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
1070 + echo apply_filters( 'propertyhive_viewing_filters', $output );
1071 + }
1072 +
1073 + /**
1074 + * Show a viewing status filter box
1075 + */
1076 + public function viewing_status_filter() {
1077 + global $wp_query;
1078 +
1079 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1080 + $requested_value = isset( $_GET['_status'] ) && is_string( $_GET['_status'] ) ? sanitize_text_field( wp_unslash( $_GET['_status'] ) ) : '';
1081 + $selected_status = in_array( $requested_value, array( 'pending', 'confirmed', 'unconfirmed', 'carried_out', 'awaiting_feedback', 'feedback_passed_on', 'feedback_not_passed_on', 'cancelled', 'no_show' ), true ) ? $requested_value : '';
1082 +
1083 + // Status filtering
1084 + $output = '<select name="_status" id="dropdown_viewing_status">';
1085 +
1086 + $output .= '<option value="">' . esc_html__( 'All Statuses', 'propertyhive' ) . '</option>';
1087 +
1088 + $viewing_statuses = ph_get_viewing_statuses();
1089 +
1090 + foreach ( $viewing_statuses as $status => $display_status )
531 1091 {
532 - $output .= selected( 'office', $_GET['_source'], false );
1092 + $output .= '<option value="' . esc_attr($status) . '"';
1093 + $output .= selected( $status, $selected_status, false );
1094 + $output .= '>' . esc_html($display_status) . '</option>';
533 1095 }
534 - $output .= '>' . __( 'Office', 'propertyhive' ) . '</option>';
535 - $output .= '<option value="website"';
536 - if ( isset( $_GET['_source'] ) && ! empty( $_GET['_source'] ) )
1096 +
1097 + $output .= '</select>';
1098 +
1099 + return $output;
1100 + }
1101 +
1102 +
1103 + public function refresh_property_office_filtering( $query ) {
1104 + remove_filter('posts_join', array( $this, 'filter_by_property_office') );
1105 +
1106 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1107 + if ( ! empty( $_GET['_office_id'] ) && in_array( $query->query['post_type'], array(
1108 + 'viewing',
1109 + 'offer',
1110 + 'sale',
1111 + ))) {
1112 + add_filter('posts_join', array( $this, 'filter_by_property_office' ) );
1113 + };
1114 + }
1115 +
1116 +
1117 + public function filter_by_property_office($query) {
1118 + global $wpdb;
1119 +
1120 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only office filtering; no state change.
1121 + $office_id = isset( $_GET['_office_id'] ) && is_scalar( $_GET['_office_id'] ) ? absint( $_GET['_office_id'] ) : 0;
1122 +
1123 + return $query . '
1124 + INNER JOIN ' . $wpdb->postmeta . ' AS property_meta ON property_meta.post_id = ' . $wpdb->posts . '.ID AND property_meta.meta_key = "_property_id"
1125 + INNER JOIN ' . $wpdb->postmeta . ' AS property_office_meta ON property_office_meta.post_id = property_meta.meta_value AND property_office_meta.meta_key = "_office_id"
1126 + AND property_office_meta.meta_value = ' . $office_id;
1127 + }
1128 +
1129 + /**
1130 + * Show an offer filter box
1131 + */
1132 + public function offer_filters() {
1133 + global $wp_query;
1134 +
1135 + $output = '';
1136 +
1137 + $output .= $this->offer_status_filter();
1138 + $output .= $this->property_office_filter();
1139 + $output .= $this->date_range_filter();
1140 +
1141 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
1142 + echo apply_filters( 'propertyhive_offer_filters', $output );
1143 + }
1144 +
1145 + /**
1146 + * Show an offer status filter box
1147 + */
1148 + public function offer_status_filter() {
1149 + global $wp_query;
1150 +
1151 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1152 + $requested_value = isset( $_GET['_status'] ) && is_string( $_GET['_status'] ) ? sanitize_text_field( wp_unslash( $_GET['_status'] ) ) : '';
1153 + $selected_status = in_array( $requested_value, array( 'pending', 'accepted', 'declined' ), true ) ? $requested_value : '';
1154 +
1155 + // Status filtering
1156 + $output = '<select name="_status" id="dropdown_offer_status">';
1157 +
1158 + $output .= '<option value="">' . esc_html(__( 'All Statuses', 'propertyhive' )) . '</option>';
1159 +
1160 + $offer_statuses = ph_get_offer_statuses();
1161 +
1162 + foreach ( $offer_statuses as $status => $display_status )
537 1163 {
538 - $output .= selected( 'website', $_GET['_source'], false );
1164 + $output .= '<option value="' . esc_attr($status) . '"';
1165 + $output .= selected( $status, $selected_status, false );
1166 + $output .= '>' . esc_html($display_status) . '</option>';
539 1167 }
540 - $output .= '>' . __( 'Website', 'propertyhive' ) . '</option>';
1168 +
1169 + $output .= '</select>';
1170 +
1171 + return $output;
1172 + }
1173 +
1174 + /**
1175 + * Show an sale filter box
1176 + */
1177 + public function sale_filters() {
1178 + global $wp_query;
1179 +
1180 + $output = '';
1181 +
1182 + $output .= $this->sale_status_filter();
1183 + $output .= $this->property_office_filter();
1184 + $output .= $this->date_range_filter();
1185 +
1186 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
1187 + echo apply_filters( 'propertyhive_sale_filters', $output );
1188 + }
1189 +
1190 + /**
1191 + * Show an sale status filter box
1192 + */
1193 + public function sale_status_filter() {
1194 + global $wp_query;
1195 +
1196 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1197 + $requested_value = isset( $_GET['_status'] ) && is_string( $_GET['_status'] ) ? sanitize_text_field( wp_unslash( $_GET['_status'] ) ) : '';
1198 + $selected_status = in_array( $requested_value, array( 'current', 'exchanged', 'completed', 'fallen_through' ), true ) ? $requested_value : '';
1199 +
1200 + // Status filtering
1201 + $output = '<select name="_status" id="dropdown_sale_status">';
541 1202
1203 + $output .= '<option value="">' . esc_html__( 'All Statuses', 'propertyhive' ) . '</option>';
1204 +
1205 + $sale_statuses = ph_get_sale_statuses();
1206 +
1207 + foreach ( $sale_statuses as $status => $display_status )
1208 + {
1209 + $output .= '<option value="' . esc_attr($status) . '"';
1210 + $output .= selected( $status, $selected_status, false );
1211 + $output .= '>' . esc_html($display_status) . '</option>';
1212 + }
1213 +
542 1214 $output .= '</select>';
543 1215
544 1216 return $output;
545 1217 }
1218 +
1219 + /**
1220 + * Show an tenancy filter box
1221 + */
1222 + public function tenancy_filters() {
1223 + global $wp_query;
1224 +
1225 + $output = '';
1226 +
1227 + $output .= $this->tenancy_status_filter();
1228 + $output .= $this->tenancy_management_type_filter();
1229 +
1230 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
1231 + echo apply_filters( 'propertyhive_tenancy_filters', $output );
1232 + }
1233 +
1234 + /**
1235 + * Show an tenancy status filter box
1236 + */
1237 + public function tenancy_status_filter() {
1238 + global $wp_query;
1239 +
1240 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1241 + $requested_value = isset( $_GET['_status'] ) && is_string( $_GET['_status'] ) ? sanitize_text_field( wp_unslash( $_GET['_status'] ) ) : '';
1242 + $selected_status = in_array( $requested_value, array( 'pending', 'current', 'finished'), true ) ? $requested_value : '';
1243 +
1244 + // Status filtering
1245 + $output = '<select name="_status" id="dropdown_tenancy_status">';
1246 +
1247 + $output .= '<option value="">' . esc_html(__( 'All Statuses', 'propertyhive' )) . '</option>';
1248 +
1249 + $output .= '<option value="pending"';
1250 + $output .= selected( 'pending', $selected_status, false );
1251 + $output .= '>' . esc_html(__( 'Pending', 'propertyhive' )) . '</option>';
1252 +
1253 + $output .= '<option value="current"';
1254 + $output .= selected( 'current', $selected_status, false );
1255 + $output .= '> ' . esc_html(__( 'Current', 'propertyhive' )) . '</option>';
1256 +
1257 + $output .= '<option value="finished"';
1258 + $output .= selected( 'finished', $selected_status, false );
1259 + $output .= '> ' . esc_html(__( 'Finished', 'propertyhive' )) . '</option>';
1260 +
1261 + $output .= '</select>';
1262 +
1263 + return $output;
1264 + }
1265 +
1266 + /**
1267 + * Show an tenancy management type filter box
1268 + */
1269 + public function tenancy_management_type_filter() {
1270 + global $wp_query;
1271 +
1272 + $management_types = apply_filters( 'propertyhive_tenancy_management_types', array(
1273 + 'let_only' => 'Let Only',
1274 + 'fully_managed' => 'Fully Managed'
1275 + ) );
1276 +
1277 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1278 + $requested_value = isset( $_GET['_management_type'] ) && is_string( $_GET['_management_type'] ) ? sanitize_text_field( wp_unslash( $_GET['_management_type'] ) ) : '';
1279 + $selected_management_type = array_key_exists( $requested_value, $management_types ) ? $requested_value : '';
1280 +
1281 + // Status filtering
1282 + $output = '<select name="_management_type" id="dropdown_tenancy_management_type">';
1283 +
1284 + $output .= '<option value="">' . esc_html(__( 'All Management Types', 'propertyhive' )) . '</option>';
1285 +
1286 + foreach ( $management_types as $key => $value )
1287 + {
1288 + $output .= '<option value="' . esc_attr($key) . '"';
1289 + $output .= selected( $key, $selected_management_type, false );
1290 + $output .= '>' . esc_html( $value ) . '</option>';
1291 + }
1292 +
1293 + $output .= '</select>';
1294 +
1295 + return $output;
1296 + }
1297 +
1298 + public function key_date_filters() {
1299 + global $wp_query;
1300 +
1301 + $output = '';
1302 +
1303 + $output .= $this->key_date_type_filter();
1304 + $output .= $this->key_date_status_filter();
1305 + $output .= $this->date_range_filter();
1306 +
1307 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
1308 + echo apply_filters( 'propertyhive_tenancy_filters', $output );
1309 + }
1310 +
1311 + public function key_date_type_filter() {
1312 +
1313 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1314 + $selected_value = ! empty($_GET['_key_date_type_id']) ? (int)$_GET['_key_date_type_id'] : '';
1315 + $terms = get_terms( array_merge( wp_parse_args( array(
1316 + 'hide_empty' => false,
1317 + 'parent' => 0
1318 + ) ), array( 'taxonomy' => 'management_key_date_type' ) ) );
1319 +
1320 + $output = '<select name="_key_date_type_id">';
1321 + $output .= '<option value="">' . esc_html(__( 'All Types', 'propertyhive' )) . '</option>';
1322 +
1323 + if ( !empty( $terms ) && !is_wp_error( $terms ) )
1324 + {
1325 + foreach ($terms as $term)
1326 + {
1327 + $output .= '<option value="' . esc_attr($term->term_id) . '"';
1328 + $output .= selected($term->term_id, $selected_value, false );
1329 + $output .= '>' . esc_html($term->name) . '</option>';
1330 + }
1331 + }
1332 +
1333 + $output .= '</select>';
1334 +
1335 + return $output;
1336 + }
1337 +
1338 +
1339 + public function key_date_status_filter() {
1340 +
1341 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1342 + $requested_value = isset( $_GET['status'] ) && is_string( $_GET['status'] ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : '';
1343 + $selected_status = in_array( $requested_value, array( 'upcoming_and_overdue', 'overdue', 'booked', 'complete', 'pending', 'on_hold', 'cancelled'), true ) ? $requested_value : '';
1344 +
1345 + $output = '<select name="status" id="dropdown_key_date_status">';
1346 +
1347 + $output .= '<option value="">' . esc_html(__( 'All Statuses', 'propertyhive' )) . '</option>';
1348 +
1349 + $output .= '<option value="upcoming_and_overdue"';
1350 + $output .= selected( 'upcoming_and_overdue', $selected_status, false );
1351 + $output .= '>' . esc_html(__( 'Upcoming & Overdue', 'propertyhive' )) . '</option>';
1352 +
1353 + $output .= '<option value="overdue"';
1354 + $output .= selected( 'overdue', $selected_status, false );
1355 + $output .= '>' . esc_html(__( 'Overdue', 'propertyhive' )) . '</option>';
1356 +
1357 + $output .= '<option value="booked"';
1358 + $output .= selected( 'booked', $selected_status, false );
1359 + $output .= '> ' . esc_html(__( 'Booked', 'propertyhive' )) . '</option>';
1360 +
1361 + $output .= '<option value="complete"';
1362 + $output .= selected( 'complete', $selected_status, false );
1363 + $output .= '> ' . esc_html(__( 'Complete', 'propertyhive' )) . '</option>';
1364 +
1365 + $output .= '<option value="pending"';
1366 + $output .= selected( 'pending', $selected_status, false );
1367 + $output .= '> ' . esc_html(__( 'Pending', 'propertyhive' )) . '</option>';
1368 +
1369 + $output .= '<option value="on_hold"';
1370 + $output .= selected( 'on_hold', $selected_status, false );
1371 + $output .= '> ' . esc_html(__( 'On Hold', 'propertyhive' )) . '</option>';
1372 +
1373 + $output .= '<option value="cancelled"';
1374 + $output .= selected( 'cancelled', $selected_status, false );
1375 + $output .= '> ' . esc_html(__( 'Cancelled', 'propertyhive' )) . '</option>';
1376 +
1377 + $output .= '</select>';
1378 +
1379 + return $output;
1380 + }
546 1381
547 1382 /**
548 1383 * Filters and sorting handler
549 1384 * @param array $vars
@@ -551,89 +1386,637 @@
551 1386 */
552 1387 public function request_query( $vars ) {
553 1388 global $typenow, $wp_query;
554 1389
1390 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- These hooks add status/department/taxonomy/date filters to the main admin list query. WordPress supplies the list query’s pagination; values are sanitized or selected from fixed post-type/date keys. These are request_query/filter_by_date_range values consumed by the core list table query rather than independent nopaging loops. The date meta key is chosen by post type.
555 1391 if ( !isset($vars['meta_query']) ) { $vars['meta_query'] = array(); }
1392 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- These hooks add status/department/taxonomy/date filters to the main admin list query. WordPress supplies the list query’s pagination; values are sanitized or selected from fixed post-type/date keys. These are request_query/filter_by_date_range values consumed by the core list table query rather than independent nopaging loops. The date meta key is chosen by post type.
556 1393 if ( !isset($vars['tax_query']) ) { $vars['tax_query'] = array(); }
557 1394
1395 + $department = $this->get_admin_query_value( '_department' );
1396 + $marketing = $this->get_admin_query_value( '_marketing' );
1397 + $contact_type = $this->get_admin_query_value( '_contact_type' );
1398 + $status = $this->get_admin_query_value( '_status' );
1399 + $source = $this->get_admin_query_value( '_source' );
1400 + $management_type = $this->get_admin_query_value( '_management_type' );
1401 + $key_date_status = $this->get_admin_query_value( 'status' );
1402 +
558 1403 if ( 'property' === $typenow )
559 1404 {
560 - if ( ! empty( $_GET['_department'] ) ) {
1405 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1406 + if ( ! empty( $department ) ) {
561 1407 $vars['meta_query'][] = array(
562 1408 'key' => '_department',
563 - 'value' => sanitize_text_field( $_GET['_department'] ),
1409 + 'value' => $department,
564 1410 );
565 1411 }
1412 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
566 1413 if ( ! empty( $_GET['_office_id'] ) ) {
567 1414 $vars['meta_query'][] = array(
568 1415 'key' => '_office_id',
569 - 'value' => sanitize_text_field( $_GET['_office_id'] ),
1416 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1417 + 'value' => (int)$_GET['_office_id'],
570 1418 );
571 1419 }
1420 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
572 1421 if ( ! empty( $_GET['_negotiator_id'] ) ) {
573 1422 $vars['meta_query'][] = array(
574 1423 'key' => '_negotiator_id',
575 - 'value' => sanitize_text_field( $_GET['_negotiator_id'] ),
1424 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1425 + 'value' => (int)$_GET['_negotiator_id'],
576 1426 );
577 1427 }
1428 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
578 1429 if ( ! empty( $_GET['_location_id'] ) ) {
579 1430 $vars['tax_query'][] = array(
580 1431 'taxonomy' => 'location',
581 - 'terms' => ( (is_array($_GET['_location_id'])) ? $_GET['_location_id'] : array( $_GET['_location_id'] ) )
1432 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1433 + 'terms' => ( (is_array($_GET['_location_id'])) ? (int)$_GET['_location_id'] : array( (int)$_GET['_location_id'] ) )
582 1434 );
583 1435 }
1436 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
584 1437 if ( ! empty( $_GET['_availability_id'] ) ) {
585 1438 $vars['tax_query'][] = array(
586 1439 'taxonomy' => 'availability',
587 - 'terms' => ( (is_array($_GET['_availability_id'])) ? $_GET['_availability_id'] : array( $_GET['_availability_id'] ) )
1440 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1441 + 'terms' => ( (is_array($_GET['_availability_id'])) ? (int)$_GET['_availability_id'] : array( (int)$_GET['_availability_id'] ) )
588 1442 );
589 1443 }
1444 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1445 + if ( 'on_market' === $marketing ) {
1446 + $vars['meta_query'][] = array(
1447 + 'key' => '_on_market',
1448 + 'value' => 'yes',
1449 + );
1450 + }
1451 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1452 + if ( 'off_market' === $marketing ) {
1453 + $vars['meta_query'][] = array(
1454 + 'key' => '_on_market',
1455 + 'value' => 'yes',
1456 + 'compare' => '!=',
1457 + );
1458 + }
1459 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1460 + if ( 'featured' === $marketing ) {
1461 + $vars['meta_query'][] = array(
1462 + 'key' => '_featured',
1463 + 'value' => 'yes',
1464 + );
1465 + }
1466 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1467 + if ( 0 === strpos( $marketing, 'marketing_flag_' ) ) {
1468 + $marketing_flag_id = str_replace( 'marketing_flag_', '', $marketing );
1469 + $vars['tax_query'][] = array(
1470 + 'taxonomy' => 'marketing_flag',
1471 + 'terms' => ( (is_array($marketing_flag_id)) ? $marketing_flag_id : array( $marketing_flag_id ) )
1472 + );
1473 + }
590 1474 }
591 1475 elseif ( 'contact' === $typenow )
592 1476 {
593 - if ( ! empty( $_GET['_contact_type'] ) ) {
594 - //$vars['meta_key '] = '_contact_types';
595 - //$vars['meta_value'] = sanitize_text_field( $_GET['_contact_type'] );
596 - //$vars['meta_compare '] = 'LIKE';
597 - $vars['meta_query'] = array(
598 - array(
599 - 'key' => '_contact_types',
600 - 'value' => sanitize_text_field( $_GET['_contact_type'] ),
601 - 'compare' => 'LIKE'
602 - )
1477 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1478 + if ( ! empty( $contact_type ) )
1479 + {
1480 + if ( $contact_type == 'hotapplicant' )
1481 + {
1482 + $contact_type = 'applicant';
1483 +
1484 + $vars['meta_query'][] = array(
1485 + 'key' => '_hot_applicant',
1486 + 'value' => 'yes',
1487 + );
1488 + }
1489 + $vars['meta_query'][] = array(
1490 + 'key' => '_contact_types',
1491 + 'value' => $contact_type,
1492 + 'compare' => 'LIKE'
603 1493 );
604 1494 }
1495 +
1496 + $vars = $this->filter_by_date_range($vars, 'date_query');
605 1497 }
606 - elseif ( 'enquiry' === $typenow )
1498 + elseif ( 'enquiry' === $typenow )
607 1499 {
608 - if ( ! empty( $_GET['_status'] ) ) {
609 - $vars['meta_key'] = '_status';
610 - $vars['meta_value'] = sanitize_text_field( $_GET['_status'] );
1500 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1501 + if ( ! empty( $status ) && $status != 'all' ) {
1502 +
1503 + $vars['meta_query'][] = array(
1504 + 'key' => '_status',
1505 + 'value' => $status,
1506 + );
611 1507 }
612 - if ( ! empty( $_GET['_source'] ) ) {
613 - $vars['meta_key'] = '_source';
614 - $vars['meta_value'] = sanitize_text_field( $_GET['_source'] );
1508 + else
1509 + {
1510 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1511 + if ( empty( $status ) )
1512 + {
1513 + $vars['meta_query'][] = array(
1514 + 'key' => '_status',
1515 + 'value' => 'open',
1516 + );
1517 + }
615 1518 }
1519 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1520 + if ( ! empty( $source ) ) {
1521 + $vars['meta_query'][] = array(
1522 + 'key' => '_source',
1523 + 'value' => $source,
1524 + );
1525 + }
1526 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1527 + if ( ! empty( $_GET['_office_id'] ) ) {
1528 + $vars['meta_query'][] = array(
1529 + 'key' => '_office_id',
1530 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1531 + 'value' => (int)$_GET['_office_id'],
1532 + );
1533 + }
1534 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1535 + if ( ! empty( $_GET['_negotiator_id'] ) ) {
1536 + $vars['meta_query'][] = array(
1537 + 'key' => '_negotiator_id',
1538 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1539 + 'value' => (int)$_GET['_negotiator_id'],
1540 + );
1541 + }
1542 +
1543 + $vars = $this->filter_by_date_range($vars, 'date_query');
616 1544 }
1545 + elseif ( 'appraisal' === $typenow )
1546 + {
1547 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1548 + if ( ! empty( $status ) ) {
1549 + switch ( $status )
1550 + {
1551 + case "confirmed":
1552 + {
1553 + $vars['meta_query'][] = array(
1554 + 'key' => '_status',
1555 + 'value' => 'pending',
1556 + );
1557 + $vars['meta_query'][] = array(
1558 + 'key' => '_all_confirmed',
1559 + 'value' => 'yes',
1560 + );
1561 + break;
1562 + }
1563 + case "unconfirmed":
1564 + {
1565 + $vars['meta_query'][] = array(
1566 + 'key' => '_status',
1567 + 'value' => 'pending',
1568 + );
1569 + $vars['meta_query'][] = array(
1570 + 'key' => '_all_confirmed',
1571 + 'value' => '',
1572 + );
1573 + break;
1574 + }
1575 + default:
1576 + {
1577 + $vars['meta_query'][] = array(
1578 + 'key' => '_status',
1579 + 'value' => $status,
1580 + );
1581 + }
1582 + }
1583 + }
1584 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1585 + if ( ! empty( $_GET['_negotiator_id'] ) )
1586 + {
1587 + $vars['meta_query'][] = array(
1588 + 'key' => '_negotiator_id',
1589 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1590 + 'value' => (int)$_GET['_negotiator_id'],
1591 + );
1592 + }
617 1593
1594 + $vars = $this->filter_by_date_range($vars);
1595 + }
1596 + elseif ( 'viewing' === $typenow )
1597 + {
1598 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1599 + if ( ! empty( $status ) ) {
1600 +
1601 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query,WordPress.Security.NonceVerification.Recommended -- Read-only status filtering of the paginated core viewing list uses the existing viewing metadata schema; no state change.
1602 + $vars['meta_query'] = add_viewing_status_meta_query( $vars['meta_query'], $status );
1603 +
1604 + }
1605 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1606 + if ( ! empty( $_GET['_negotiator_id'] ) )
1607 + {
1608 + $vars['meta_query'][] = array(
1609 + 'key' => '_negotiator_id',
1610 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1611 + 'value' => (int)$_GET['_negotiator_id'],
1612 + );
1613 + }
1614 +
1615 + $vars = $this->filter_by_date_range($vars);
1616 + }
1617 + elseif ( 'offer' === $typenow )
1618 + {
1619 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1620 + if ( ! empty( $status ) ) {
1621 + $vars['meta_query'][] = array(
1622 + 'key' => '_status',
1623 + 'value' => $status,
1624 + );
1625 + }
1626 +
1627 + $vars = $this->filter_by_date_range($vars, '_offer_date_time');
1628 + }
1629 + elseif ( 'sale' === $typenow )
1630 + {
1631 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1632 + if ( ! empty( $status ) ) {
1633 + $vars['meta_query'][] = array(
1634 + 'key' => '_status',
1635 + 'value' => $status,
1636 + );
1637 + }
1638 +
1639 + $vars = $this->filter_by_date_range($vars, '_sale_date_time');
1640 + }
1641 + elseif ( 'tenancy' === $typenow )
1642 + {
1643 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1644 + if ( ! empty( $status ) )
1645 + {
1646 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1647 + switch ( $status )
1648 + {
1649 + case 'pending' :
1650 + $vars['meta_query'][] = array(
1651 + 'key' => '_start_date',
1652 + 'value' => gmdate('Y-m-d'),
1653 + 'type' => 'date',
1654 + 'compare' => '>',
1655 + );
1656 + break;
1657 +
1658 + case 'current' :
1659 + $vars['meta_query'][] = array(
1660 + 'relation' => 'OR',
1661 + array(
1662 + array(
1663 + 'key' => '_start_date',
1664 + 'value' => gmdate('Y-m-d'),
1665 + 'type' => 'date',
1666 + 'compare' => '<=',
1667 + ),
1668 + array(
1669 + 'key' => '_end_date',
1670 + 'value' => gmdate('Y-m-d'),
1671 + 'type' => 'date',
1672 + 'compare' => '>=',
1673 + )
1674 + ),
1675 + array(
1676 + array(
1677 + 'key' => '_start_date',
1678 + 'value' => gmdate('Y-m-d'),
1679 + 'type' => 'date',
1680 + 'compare' => '<=',
1681 + ),
1682 + array(
1683 + 'key' => '_end_date',
1684 + 'value' => '',
1685 + 'compare' => '=',
1686 + )
1687 + )
1688 + );
1689 + break;
1690 +
1691 + case 'finished':
1692 + $vars['meta_query'][] = array(
1693 + 'key' => '_end_date',
1694 + 'value' => gmdate('Y-m-d'),
1695 + 'type' => 'date',
1696 + 'compare' => '<',
1697 + );
1698 + break;
1699 + }
1700 + }
1701 +
1702 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1703 + if ( ! empty( $management_type ) ) {
1704 + $vars['meta_query'][] = array(
1705 + 'key' => '_management_type',
1706 + 'value' => $management_type,
1707 + );
1708 + }
1709 + }
1710 + elseif ( 'key_date' === $typenow )
1711 + {
1712 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1713 + if ( ! empty( $key_date_status ) ) {
1714 +
1715 + $value = $key_date_status;
1716 +
1717 + switch ($value) {
1718 + case 'booked':
1719 + case 'complete':
1720 + case 'on_hold':
1721 + case 'cancelled':
1722 + $vars['meta_query'][] = array(
1723 + 'key' => '_key_date_status',
1724 + 'value' => $value,
1725 + );
1726 + break;
1727 + case 'pending':
1728 + $vars['meta_query'][] = array(
1729 + 'key' => '_key_date_status',
1730 + 'value' => 'pending',
1731 + );
1732 + break;
1733 + case 'overdue':
1734 + $vars['meta_query'][] = array(
1735 + 'key' => '_key_date_status',
1736 + 'value' => array('pending', 'booked'),
1737 + 'compare' => 'IN'
1738 + );
1739 + $vars['meta_query'][] = array(
1740 + 'key' => '_date_due',
1741 + 'value' => gmdate("Y-m-d"),
1742 + 'type' => 'date',
1743 + 'compare' => '<',
1744 + );
1745 + break;
1746 + case 'upcoming_and_overdue':
1747 + $vars['meta_query'][] = array(
1748 + 'key' => '_key_date_status',
1749 + 'value' => array('pending', 'booked'),
1750 + 'compare' => 'IN'
1751 + );
1752 + $upcoming_threshold = new DateTime('+ ' . apply_filters( 'propertyhive_key_date_upcoming_days', 7 ) . ' DAYS');
1753 + $vars['meta_query'][] = array(
1754 + 'key' => '_date_due',
1755 + 'value' => $upcoming_threshold->format('Y-m-d'),
1756 + 'type' => 'date',
1757 + 'compare' => '<=',
1758 + );
1759 + break;
1760 + }
1761 + }
1762 +
1763 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1764 + if ( !empty( $_GET['_key_date_type_id'] ) )
1765 + {
1766 + $vars['meta_query'][] = array(
1767 + 'key' => '_key_date_type_id',
1768 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1769 + 'value' => (int)$_GET['_key_date_type_id'],
1770 + );
1771 + }
1772 +
1773 + $vars = $this->filter_by_date_range($vars, '_date_due');
1774 + }
1775 +
1776 + $vars = apply_filters( 'propertyhive_property_filter_query', $vars, $typenow );
1777 +
618 1778 return $vars;
619 1779 }
620 -
621 - /**
622 - * search_property_reference function.
623 - *
624 - * @access public
625 - * @param string $where (default: '')
626 - * @return string (modified where clause)
627 - */
628 - public function search_property_reference( $where = '' ) {
629 - global $typenow;
630 -
631 - if ( $typenow == 'property' && isset( $_GET['s'] ) && ! empty( $_GET['s'] ) )
1780 +
1781 + private function filter_by_date_range($vars, $meta_key = '_start_date_time')
1782 + {
1783 + $date_range_label = $this->get_admin_query_value( '_date_range_label' );
1784 + $date_range_from = $this->get_admin_query_value( '_date_range_from' );
1785 + $date_range_to = $this->get_admin_query_value( '_date_range_to' );
1786 +
1787 + if (
1788 + ! empty( $date_range_label )
1789 + && ! empty( $date_range_from )
1790 + && ! empty( $date_range_to )
1791 + && $date_range_label !== 'Any Time'
1792 + && DateTime::createFromFormat('Y-m-d', $date_range_from) !== false
1793 + && DateTime::createFromFormat('Y-m-d', $date_range_to) !== false
1794 + )
1795 + {
1796 + if ( $meta_key == 'date_query' )
1797 + {
1798 + $vars['date_query'] = array(
1799 + 'after' => $date_range_from . ' 00:00:00',
1800 + 'before' => $date_range_to . ' 23:59:59',
1801 + );
1802 + }
1803 + else
1804 + {
1805 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Add validated date boundaries using the fixed date key selected for this paginated admin post-type list.
1806 + $vars['meta_query'] = array_merge($vars['meta_query'], array (
1807 + array(
1808 + 'key' => $meta_key,
1809 + 'value' => $date_range_from,
1810 + 'type' => 'date',
1811 + 'compare' => '>='
1812 + ),
1813 + array(
1814 + 'key' => $meta_key,
1815 + 'value' => $date_range_to,
1816 + 'type' => 'date',
1817 + 'compare' => '<='
1818 + ),
1819 + ));
1820 + }
1821 + }
1822 +
1823 + return $vars;
1824 + }
1825 +
1826 + public function posts_join( $join, $q ) {
1827 + global $typenow, $wp_query, $wpdb;
1828 +
1829 + if ( !$q->is_main_query() )
1830 + return $join;
1831 +
1832 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1833 + $search = isset( $_GET['s'] ) && is_string( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
1834 + if ( $search === '' ) {
1835 + return $join;
1836 + }
1837 +
1838 + if ( 'property' === $typenow )
632 1839 {
633 - //$where .= 'hel';
1840 + $join .= "
1841 +LEFT JOIN " . $wpdb->postmeta . " AS ph_property_filter_meta_address_concatenated ON " . $wpdb->posts . ".ID = ph_property_filter_meta_address_concatenated.post_id AND ph_property_filter_meta_address_concatenated.meta_key = '_address_concatenated'
1842 +LEFT JOIN " . $wpdb->postmeta . " AS ph_property_filter_meta_reference_number ON " . $wpdb->posts . ".ID = ph_property_filter_meta_reference_number.post_id AND ph_property_filter_meta_reference_number.meta_key = '_reference_number'
1843 +LEFT JOIN " . $wpdb->postmeta . " AS ph_property_filter_meta_owner_details ON " . $wpdb->posts . ".ID = ph_property_filter_meta_owner_details.post_id AND ph_property_filter_meta_owner_details.meta_key = '_owner_details'
1844 +";
634 1845 }
635 -
1846 + elseif ( 'contact' === $typenow )
1847 + {
1848 + $phone_number = '';
1849 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1850 + if ( is_numeric(substr($search, 0, 1)) )
1851 + {
1852 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1853 + $phone_number = preg_replace( "/[^0-9,]/", "", $search );
1854 + }
1855 +
1856 + $join .= "
1857 +LEFT JOIN " . $wpdb->postmeta . " AS ph_contact_filter_meta_address_concatenated ON " . $wpdb->posts . ".ID = ph_contact_filter_meta_address_concatenated.post_id AND ph_contact_filter_meta_address_concatenated.meta_key = '_address_concatenated'
1858 +LEFT JOIN " . $wpdb->postmeta . " AS ph_contact_filter_meta_email_address ON " . $wpdb->posts . ".ID = ph_contact_filter_meta_email_address.post_id AND ph_contact_filter_meta_email_address.meta_key = '_email_address' ";
1859 +
1860 + if ( $phone_number != '' )
1861 + {
1862 + $join .= " LEFT JOIN " . $wpdb->postmeta . " AS ph_contact_filter_meta_telephone_number ON " . $wpdb->posts . ".ID = ph_contact_filter_meta_telephone_number.post_id AND ph_contact_filter_meta_telephone_number.meta_key = '_telephone_number_clean'
1863 + ";
1864 + }
1865 + }
1866 + elseif ( 'appraisal' === $typenow )
1867 + {
1868 + $join .= "
1869 +LEFT JOIN " . $wpdb->postmeta . " AS ph_appraisal_filter_meta_name_number ON " . $wpdb->posts . ".ID = ph_appraisal_filter_meta_name_number.post_id AND ph_appraisal_filter_meta_name_number.meta_key = '_address_name_number'
1870 +LEFT JOIN " . $wpdb->postmeta . " AS ph_appraisal_filter_meta_street ON " . $wpdb->posts . ".ID = ph_appraisal_filter_meta_street.post_id AND ph_appraisal_filter_meta_street.meta_key = '_address_street'
1871 +LEFT JOIN " . $wpdb->postmeta . " AS ph_appraisal_filter_meta_2 ON " . $wpdb->posts . ".ID = ph_appraisal_filter_meta_2.post_id AND ph_appraisal_filter_meta_2.meta_key = '_address_two'
1872 +LEFT JOIN " . $wpdb->postmeta . " AS ph_appraisal_filter_meta_3 ON " . $wpdb->posts . ".ID = ph_appraisal_filter_meta_3.post_id AND ph_appraisal_filter_meta_3.meta_key = '_address_three'
1873 +LEFT JOIN " . $wpdb->postmeta . " AS ph_appraisal_filter_meta_4 ON " . $wpdb->posts . ".ID = ph_appraisal_filter_meta_4.post_id AND ph_appraisal_filter_meta_4.meta_key = '_address_four'
1874 +LEFT JOIN " . $wpdb->postmeta . " AS ph_appraisal_filter_meta_postcode ON " . $wpdb->posts . ".ID = ph_appraisal_filter_meta_postcode.post_id AND ph_appraisal_filter_meta_postcode.meta_key = '_address_postcode'
1875 +";
1876 + }
1877 + elseif ( 'viewing' === $typenow || 'offer' === $typenow || 'sale' === $typenow || 'tenancy' === $typenow )
1878 + {
1879 + $join .= "
1880 +LEFT JOIN " . $wpdb->postmeta . " AS ph_property_filter_meta ON " . $wpdb->posts . ".ID = ph_property_filter_meta.post_id AND ph_property_filter_meta.meta_key = '_property_id'
1881 +LEFT JOIN " . $wpdb->posts . " AS ph_property_filter_posts ON ph_property_filter_posts.ID = ph_property_filter_meta.meta_value
1882 +LEFT JOIN " . $wpdb->postmeta . " AS ph_property_filter_meta_address_concatenated ON ph_property_filter_posts.ID = ph_property_filter_meta_address_concatenated.post_id AND ph_property_filter_meta_address_concatenated.meta_key = '_address_concatenated'
1883 +LEFT JOIN " . $wpdb->postmeta . " AS ph_property_filter_meta_reference_number ON ph_property_filter_posts.ID = ph_property_filter_meta_reference_number.post_id AND ph_property_filter_meta_reference_number.meta_key = '_reference_number'
1884 +LEFT JOIN " . $wpdb->postmeta . " AS ph_applicant_filter_meta ON " . $wpdb->posts . ".ID = ph_applicant_filter_meta.post_id AND ph_applicant_filter_meta.meta_key = '_applicant_contact_id'
1885 +LEFT JOIN " . $wpdb->posts . " AS ph_applicant_filter_posts ON ph_applicant_filter_posts.ID = ph_applicant_filter_meta.meta_value
1886 +";
1887 + }
1888 +
1889 + return $join;
1890 + }
1891 +
1892 + public function posts_where( $where, $q ) {
1893 + global $typenow, $wp_query, $wpdb;
1894 +
1895 + if ( !$q->is_main_query() )
1896 + return $where;
1897 +
1898 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1899 + $search = isset( $_GET['s'] ) && is_string( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
1900 + if ( $search === '' ) {
1901 + return $where;
1902 + }
1903 + $reference_like = $wpdb->prepare( '%s', $wpdb->esc_like( $search ) . '%' );
1904 + $reference_exact = $wpdb->prepare( '%s', $search );
1905 + $phone_number = '';
1906 +
1907 + if ( 'property' === $typenow )
1908 + {
1909 + $where = preg_replace_callback(
1910 + "/\(\s*" . $wpdb->posts . ".post_title\s+LIKE\s*('(?:\\\\.|[^'\\\\])*')\s*\)/",
1911 + static function( $matches ) use ( $wpdb, $reference_like, $reference_exact, $phone_number ) {
1912 + return "(
1913 + (" . $wpdb->posts . ".post_title LIKE " . $matches[1] . ")
1914 + OR
1915 + (ph_property_filter_meta_address_concatenated.meta_value LIKE " . $matches[1] . ")
1916 + OR
1917 + (ph_property_filter_meta_reference_number.meta_value LIKE " . $reference_like . ")
1918 + OR
1919 + (ph_property_filter_meta_owner_details.meta_value LIKE " . $matches[1] . ")
1920 + )";
1921 + },
1922 + $where
1923 + );
1924 +
1925 + $where = preg_replace(
1926 + "/\s+OR\s+\(\s*" . $wpdb->posts . ".post_excerpt\s+LIKE\s*('(?:\\\\.|[^'\\\\])*')\s*\)/",
1927 + "",
1928 + $where
1929 + );
1930 +
1931 + $where = preg_replace(
1932 + "/\s+OR\s+\(\s*" . $wpdb->posts . ".post_content\s+LIKE\s*('(?:\\\\.|[^'\\\\])*')\s*\)/",
1933 + "",
1934 + $where
1935 + );
1936 + }
1937 + elseif ( 'contact' === $typenow )
1938 + {
1939 + $phone_number = '';
1940 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1941 + if ( is_numeric(substr($search, 0, 1)) )
1942 + {
1943 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1944 + $phone_number = preg_replace( "/[^0-9,]/", "", $search );
1945 + }
1946 +
1947 + $where = preg_replace_callback(
1948 + "/\(\s*" . $wpdb->posts . ".post_title\s+LIKE\s*('(?:\\\\.|[^'\\\\])*')\s*\)/",
1949 + static function( $matches ) use ( $wpdb, $reference_like, $reference_exact, $phone_number ) {
1950 + return "(
1951 + (" . $wpdb->posts . ".post_title LIKE " . $matches[1] . ")
1952 + OR
1953 + (ph_contact_filter_meta_address_concatenated.meta_value LIKE " . $matches[1] . ")
1954 + OR
1955 + (ph_contact_filter_meta_email_address.meta_value LIKE " . $matches[1] . ")
1956 + " . ( $phone_number != '' ? "OR (ph_contact_filter_meta_telephone_number.meta_value LIKE '%" . $phone_number . "%')" : '' ) . "
1957 + )";
1958 + },
1959 + $where
1960 + );
1961 +
1962 + $where = preg_replace(
1963 + "/\s+OR\s+\(\s*" . $wpdb->posts . ".post_excerpt\s+LIKE\s*('(?:\\\\.|[^'\\\\])*')\s*\)/",
1964 + "",
1965 + $where
1966 + );
1967 +
1968 + $where = preg_replace(
1969 + "/\s+OR\s+\(\s*" . $wpdb->posts . ".post_content\s+LIKE\s*('(?:\\\\.|[^'\\\\])*')\s*\)/",
1970 + "",
1971 + $where
1972 + );
1973 + }
1974 + elseif ( 'appraisal' === $typenow )
1975 + {
1976 + $where = preg_replace_callback(
1977 + "/\(\s*" . $wpdb->posts . ".post_title\s+LIKE\s*('(?:\\\\.|[^'\\\\])*')\s*\)/",
1978 + static function( $matches ) use ( $wpdb, $reference_like, $reference_exact, $phone_number ) {
1979 + return "(
1980 + (" . $wpdb->posts . ".post_title LIKE " . $matches[1] . ")
1981 + OR
1982 + (ph_appraisal_filter_meta_name_number.meta_value LIKE " . $matches[1] . ")
1983 + OR
1984 + (ph_appraisal_filter_meta_street.meta_value LIKE " . $matches[1] . ")
1985 + OR
1986 + (ph_appraisal_filter_meta_2.meta_value LIKE " . $matches[1] . ")
1987 + OR
1988 + (ph_appraisal_filter_meta_3.meta_value LIKE " . $matches[1] . ")
1989 + OR
1990 + (ph_appraisal_filter_meta_4.meta_value LIKE " . $matches[1] . ")
1991 + OR
1992 + (ph_appraisal_filter_meta_postcode.meta_value LIKE " . $matches[1] . ")
1993 + )";
1994 + },
1995 + $where
1996 + );
1997 + }
1998 + elseif ( 'viewing' === $typenow || 'offer' === $typenow || 'sale' === $typenow || 'tenancy' === $typenow )
1999 + {
2000 + $where = preg_replace_callback(
2001 + "/\(\s*" . $wpdb->posts . ".post_title\s+LIKE\s*('(?:\\\\.|[^'\\\\])*')\s*\)/",
2002 + static function( $matches ) use ( $wpdb, $reference_like, $reference_exact, $phone_number ) {
2003 + return "(
2004 + (" . $wpdb->posts . ".post_title LIKE " . $matches[1] . ")
2005 + OR
2006 + (ph_property_filter_posts.post_title LIKE " . $matches[1] . ")
2007 + OR
2008 + (ph_property_filter_meta_address_concatenated.meta_value LIKE " . $matches[1] . ")
2009 + OR
2010 + (ph_property_filter_meta_reference_number.meta_value = " . $reference_exact . ")
2011 + OR
2012 + (ph_applicant_filter_posts.post_title LIKE " . $matches[1] . ")
2013 + )";
2014 + },
2015 + $where
2016 + );
2017 + }
2018 +
636 2019 return $where;
637 2020 }
638 2021
639 2022 /**
@@ -700,5 +2083,5 @@
700 2083 }
701 2084
702 2085 endif;
703 2086
704 -return new PH_Admin_Post_Types();
2087 +return new PH_Admin_Post_Types();