PluginProbe
Property Hive / 2.3.0
Property Hive v2.3.0
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
propertyhive / includes / admin / class-ph-admin-matching-properties.php

class-ph-admin-matching-properties.php in Property Hive 2.3.0, at includes/admin/class-ph-admin-matching-properties.php

954 lines 43.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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
5 /**
6 * PropertyHive Admin Matching Properties Class.
7 *
8 * @author PropertyHive
9 * @category Admin
10 * @package PropertyHive/Admin
11 * @version 1.0.0
12 */
13
14 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
15
16 if ( ! class_exists( 'PH_Admin_Matching_Properties' ) ) :
17
18 /**
19 * PH_Admin_Matching_Properties
20 */
21 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Legacy public global class PH_Admin_Matching_Properties; preserving the existing PH_* class name is required for plugin and extension compatibility.
22 class PH_Admin_Matching_Properties {
23
24 public function output()
25 {
26 // The initial matching screen is read-only. The POST branch below verifies the
27 // matching nonce before it performs any state-changing action.
28 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This request is used to render the read-only matching screen; POST mutations verify the matching nonce below.
29 $request_get = wp_unslash( $_GET );
30 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Only the presence of the action selector is checked here; request values are normalized after the nonce check below.
31 $has_step = isset( $_POST['step'] );
32
33 $contact_id = ( isset( $request_get['contact_id'] ) && is_scalar( $request_get['contact_id'] ) ) ? absint( $request_get['contact_id'] ) : 0;
34 $applicant_profile_id = ( isset( $request_get['applicant_profile'] ) && is_scalar( $request_get['applicant_profile'] ) ) ? absint( $request_get['applicant_profile'] ) : 0;
35
36 if ( ! $contact_id || get_post_type( $contact_id ) !== 'contact' )
37 {
38 die('Invalid contact_id passed');
39 }
40 if ( ! isset( $request_get['applicant_profile'] ) || ! is_scalar( $request_get['applicant_profile'] ) )
41 {
42 die('Invalid applicant_profile passed');
43 }
44
45 $email_address = get_post_meta( $contact_id, '_email_address', TRUE );
46
47 $applicant_profile = get_post_meta( $contact_id, '_applicant_profile_' . $applicant_profile_id, TRUE );
48
49 if ( $has_step )
50 {
51 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Only the nonce value is read before verification; all other POST values are normalized after the check below.
52 $request_request = wp_unslash( $_REQUEST );
53 if ( empty( $request_request['_wpnonce'] ) || ! wp_verify_nonce( ( isset( $request_request['_wpnonce'] ) && is_string( $request_request['_wpnonce'] ) ) ? sanitize_text_field( $request_request['_wpnonce'] ) : '', 'propertyhive-matching-properties' ) )
54 die( esc_html(__( 'Action failed. Please refresh the page and retry.', 'propertyhive' )) );
55
56 $request_post = wp_unslash( $_POST );
57 $step = is_string( $request_post['step'] ) ? sanitize_key( $request_post['step'] ) : '';
58
59 switch ( $step )
60 {
61 case "one":
62 {
63 // Properties have been selected to email or dismiss
64
65 // Handle dismissed properties
66 $this->dismiss_properties();
67
68 $nothing_to_send = true;
69
70 // Handle properties to email
71 if ( isset( $request_post['email_property_id'] ) && ! empty( $request_post['email_property_id'] ) )
72 {
73 $nothing_to_send = false;
74
75 $subject = get_option( 'propertyhive_property_match_default_email_subject', '' );
76 $body = get_option( 'propertyhive_property_match_default_email_body', '' );
77
78 $from_email_option = get_option( 'propertyhive_property_match_default_from', '' );
79 if( $from_email_option == 'default_from_email' )
80 {
81 $from_email_address = get_option('propertyhive_email_from_address', '');
82 }
83 else
84 {
85 $current_user = wp_get_current_user();
86 $from_email_address = $current_user->user_email;
87 }
88 }
89
90 $nothing_to_send = apply_filters( 'propertyhive_property_match_nothing_to_send', $nothing_to_send );
91
92 if ( $nothing_to_send != true )
93 {
94 ?>
95 <div class="wrap propertyhive">
96
97 <div id="poststuff">
98
99 <form method="post" id="mainform" action="" enctype="multipart/form-data">
100 <?php
101 if ( isset( $request_post['email_property_id'] ) && ! empty( $request_post['email_property_id'] ) )
102 {
103 // We've got emails to send
104 include 'views/html-admin-matching-properties-email.php';
105 }
106
107 do_action( 'propertyhive_property_match_step_two', $contact_id, $applicant_profile_id );
108 ?>
109 <p class="submit">
110
111 <input name="save" class="button-primary" type="submit" value="<?php echo esc_attr(__( 'Send Matches', 'propertyhive' )); ?>" />
112 <?php if ( isset( $request_post['email_property_id'] ) && ! empty( $request_post['email_property_id'] ) ) { ?>
113 <input name="preview" id="preview_email" class="button" type="button" value="<?php echo esc_attr(__( 'Preview Email', 'propertyhive' )); ?>" />
114 <?php } ?>
115
116 <input type="hidden" name="step" value="two" />
117 <input type="hidden" name="email_property_id" value="<?php
118 $selected_property_ids = array();
119 if ( isset( $request_post['email_property_id'] ) && is_array( $request_post['email_property_id'] ) ) {
120 foreach ( $request_post['email_property_id'] as $selected_property_id ) {
121 if ( is_scalar( $selected_property_id ) ) {
122 $selected_property_ids[] = absint( $selected_property_id );
123 }
124 }
125 }
126 echo esc_attr( implode( ',', $selected_property_ids ) );
127 ?>" />
128 <?php do_action( 'propertyhive_property_match_step_two_hidden_fields' ); ?>
129 <?php wp_nonce_field( 'propertyhive-matching-properties' ); ?>
130
131 </p>
132
133 <p>
134 <?php echo wp_kses_post( __( 'When sending out lots of emails we recommend using <a href="https://en-gb.wordpress.org/plugins/tags/smtp" target="_blank">a plugin</a> to send them out using SMTP. Your web developer or hosting company should be able to advise on this.', 'propertyhive' ) );
135 ?>
136 </p>
137
138 </form>
139
140 </div>
141
142 </div>
143
144 <script>
145
146 jQuery(document).ready(function()
147 {
148 jQuery('#preview_email').click(function(e)
149 {
150 e.preventDefault();
151
152 showPreview();
153 });
154 });
155
156 function showPreview()
157 {
158 jQuery('#mainform').attr('target', '_blank');
159 jQuery('#mainform').attr('action', <?php echo wp_json_encode( admin_url( '?preview_propertyhive_email=true&contact_id=' . $contact_id . '&applicant_profile=' . $applicant_profile_id ), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); ?>);
160
161 jQuery('#mainform').submit();
162 jQuery('#mainform').attr('target', '_self');
163 jQuery('#mainform').attr('action', '');
164 }
165
166 </script>
167 <?php
168 }
169
170 if ( $nothing_to_send == true )
171 {
172 echo '<script>window.location.href = ' . wp_json_encode( get_edit_post_link( $contact_id, 'url' ) . '&ph_message=2', JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ) . ';</script>';
173
174 //header("Location: " . get_edit_post_link( $contact_id, 'url' ) . '&ph_message=2' ); // properties marked as not interested
175 //die();
176 }
177
178 break;
179 }
180 case "two":
181 {
182 if ( isset( $request_post['email_property_id'] ) && ! empty( $request_post['email_property_id'] ) )
183 {
184 $to_email_address_input = ( isset( $request_post['to_email_address'] ) && is_string( $request_post['to_email_address'] ) ) ? $request_post['to_email_address'] : '';
185 $to_email_addresses = explode( ',', $to_email_address_input );
186 $new_to_email_addresses = array();
187 foreach ( $to_email_addresses as $to_email_address )
188 {
189 $new_to_email_addresses[] = sanitize_email($to_email_address);
190 }
191
192 $cc_email_address_input = ( isset( $request_post['cc_email_address'] ) && is_string( $request_post['cc_email_address'] ) ) ? $request_post['cc_email_address'] : '';
193 $cc_email_addresses = explode( ',', $cc_email_address_input );
194 $new_cc_email_addresses = array();
195 foreach ( $cc_email_addresses as $cc_email_address )
196 {
197 $new_cc_email_addresses[] = sanitize_email($cc_email_address);
198 }
199
200 $bcc_email_address_input = ( isset( $request_post['bcc_email_address'] ) && is_string( $request_post['bcc_email_address'] ) ) ? $request_post['bcc_email_address'] : '';
201 $bcc_email_addresses = explode( ',', $bcc_email_address_input );
202 $new_bcc_email_addresses = array();
203 foreach ( $bcc_email_addresses as $bcc_email_address )
204 {
205 $new_bcc_email_addresses[] = sanitize_email($bcc_email_address);
206 }
207
208 $allowed_tags = array(
209 'strong' => array(),
210 'span' => array(),
211 'em' => array(),
212 'h1' => array(),
213 'h2' => array(),
214 'h3' => array(),
215 'h4' => array(),
216 'h5' => array(),
217 'h6' => array(),
218 'i' => array(),
219 'u' => array(),
220 'b' => array(),
221 'a' => array(
222 'href' => array(),
223 'target' => array(),
224 ),
225 );
226 $allowed_tags = apply_filters( 'propertyhive_match_email_allowed_tags', $allowed_tags );
227
228 $body_input = ( isset( $request_post['body'] ) && is_string( $request_post['body'] ) ) ? $request_post['body'] : '';
229 $body = wp_kses( $body_input, $allowed_tags );
230 $email_property_id_input = ( isset( $request_post['email_property_id'] ) && is_scalar( $request_post['email_property_id'] ) ) ? $request_post['email_property_id'] : '';
231 $from_name_input = ( isset( $request_post['from_name'] ) && is_string( $request_post['from_name'] ) ) ? $request_post['from_name'] : '';
232 $from_email_address_input = ( isset( $request_post['from_email_address'] ) && is_string( $request_post['from_email_address'] ) ) ? $request_post['from_email_address'] : '';
233 $subject_input = ( isset( $request_post['subject'] ) && is_string( $request_post['subject'] ) ) ? $request_post['subject'] : '';
234
235 // Email info entered. Time to send emails
236 $this->send_emails(
237 $contact_id,
238 $applicant_profile_id,
239 array_values( array_filter( array_map( 'absint', explode( ',', sanitize_text_field( $email_property_id_input ) ) ) ) ),
240 ph_clean( $from_name_input ),
241 sanitize_email( $from_email_address_input ),
242 ph_clean( $subject_input ),
243 $body,
244 implode(",", $new_to_email_addresses),
245 implode(",", $new_cc_email_addresses),
246 implode(",", $new_bcc_email_addresses)
247 );
248
249 //header("Location: " . get_edit_post_link( $contact_id, 'url' ) . '&ph_message=1' ); // email sent
250 //die();
251 }
252
253 do_action( 'propertyhive_property_match_step_send', $contact_id, $applicant_profile_id );
254
255 echo '<script>window.location.href = ' . wp_json_encode( get_edit_post_link( $contact_id, 'url' ) . '&ph_message=1', JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ) . ';</script>';
256 }
257 }
258 }
259 else
260 {
261 $applicant_profile_match_history = get_post_meta( $contact_id, '_applicant_profile_' . $applicant_profile_id . '_match_history', TRUE );
262
263 $properties = $this->get_matching_properties( $contact_id, $applicant_profile_id );
264
265 $do_not_email = false;
266 $forbidden_contact_methods = get_post_meta( $contact_id, '_forbidden_contact_methods', TRUE );
267 if ( is_array($forbidden_contact_methods) && in_array('email', $forbidden_contact_methods) )
268 {
269 $do_not_email = true;
270 }
271
272 include 'views/html-admin-matching-properties.php';
273 }
274 }
275
276 private function dismiss_properties()
277 {
278 // output() verifies the matching nonce before calling this private mutator.
279 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- This private helper is only called from output() after the matching nonce has been verified.
280 $request_post = wp_unslash( $_POST );
281 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This private helper receives the read-only contact identifier from the already-authorized matching screen.
282 $request_get = wp_unslash( $_GET );
283 $contact_id = ( isset( $request_get['contact_id'] ) && is_scalar( $request_get['contact_id'] ) ) ? absint( $request_get['contact_id'] ) : 0;
284
285 // Get currently dismissed properties for this contact to decide if we need to add or remove it
286 $dismissed_properties = get_post_meta( $contact_id, '_dismissed_properties', TRUE );
287
288 if ( !is_array($dismissed_properties) )
289 {
290 $dismissed_properties = array();
291 }
292
293 if ( isset( $request_post['not_interested_property_id'] ) && is_array( $request_post['not_interested_property_id'] ) && ! empty( $request_post['not_interested_property_id'] ) )
294 {
295 foreach ( $request_post['not_interested_property_id'] as $property_id )
296 {
297 if ( ! is_scalar( $property_id ) ) {
298 continue;
299 }
300 if ( in_array((int)$property_id, $dismissed_properties) )
301 {
302 // Already dismissed. Need to remove from array
303 if( ($key = array_search((int)$property_id, $dismissed_properties)) !== false )
304 {
305 unset($dismissed_properties[$key]);
306 }
307 }
308 else
309 {
310 // Not dismissed. Add to array
311 $dismissed_properties[] = (int)$property_id;
312 }
313 }
314
315 $dismissed_properties = array_unique($dismissed_properties);
316
317 update_post_meta( $contact_id, '_dismissed_properties', $dismissed_properties );
318 }
319 }
320
321 public function get_matching_properties( $contact_id, $applicant_profile_id, $date_added_from = '' )
322 {
323 global $post;
324
325 $properties = array();
326
327 $contact = get_post($contact_id);
328
329 if ( !is_null( $contact ) )
330 {
331 $percentage_lower = get_option( 'propertyhive_applicant_match_price_range_percentage_lower', '' );
332 $percentage_higher = get_option( 'propertyhive_applicant_match_price_range_percentage_higher', '' );
333
334 $dismissed_properties = get_post_meta( $contact_id, '_dismissed_properties', TRUE );
335 if ( !is_array($dismissed_properties) )
336 {
337 $dismissed_properties = array();
338 }
339
340 $applicant_profile = get_post_meta( $contact_id, '_applicant_profile_' . $applicant_profile_id, TRUE );
341
342 $args = array(
343 'post_type' => 'property',
344 'nopaging' => true,
345 // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Exclude this contact's explicitly dismissed properties before applying existing matching and extension query conditions.
346 'post__not_in' => $dismissed_properties
347 );
348
349 // Meta query
350 $meta_query = array('relation' => 'AND');
351
352 if ( $date_added_from != '' )
353 {
354 $datetime = new DateTimeImmutable(
355 $date_added_from,
356 new DateTimeZone( 'UTC' )
357 );
358
359 if ( apply_filters( 'propertyhive_matching_properties_use_on_market_change_date', false ) === true )
360 {
361 // _on_market_change_date is currently stored using date(),
362 // which will normally be UTC in WordPress.
363 $meta_query[] = array(
364 'key' => '_on_market_change_date',
365 'value' => $datetime->format( 'Y-m-d H:i:s' ),
366 'compare' => '>=',
367 'type' => 'DATETIME',
368 );
369 }
370 else
371 {
372 // post_date is stored in the site's local timezone.
373 $local_datetime = $datetime->setTimezone( wp_timezone() );
374
375 $args['date_query'] = array(
376 array(
377 'after' => $local_datetime->format( 'Y-m-d H:i:s' ),
378 'inclusive' => true,
379 ),
380 );
381 }
382 }
383
384 $meta_query[] = array(
385 'key' => '_on_market',
386 'value' => 'yes'
387 );
388 if (
389 isset($applicant_profile['department']) &&
390 (
391 $applicant_profile['department'] == 'residential-sales' ||
392 ph_get_custom_department_based_on($applicant_profile['department']) == 'residential-sales'
393 )
394 )
395 {
396 $meta_query[] = array(
397 'key' => '_department',
398 'value' => $applicant_profile['department']
399 );
400
401 if (
402 get_option( 'propertyhive_applicant_match_price_range_percentage_lower', '' ) != '' &&
403 get_option( 'propertyhive_applicant_match_price_range_percentage_higher', '' ) != ''
404 )
405 {
406 $match_price_range_lower = '';
407 if ( !isset($applicant_profile['match_price_range_lower_actual']) || ( isset($applicant_profile['match_price_range_lower_actual']) && $applicant_profile['match_price_range_lower_actual'] == '' ) )
408 {
409 if ( isset($applicant_profile['max_price_actual']) && $applicant_profile['max_price_actual'] != '' )
410 {
411 if ( $percentage_lower != '' )
412 {
413 $match_price_range_lower = $applicant_profile['max_price_actual'] - ( $applicant_profile['max_price_actual'] * ( $percentage_lower / 100 ) );
414 }
415 }
416 }
417 else
418 {
419 $match_price_range_lower = $applicant_profile['match_price_range_lower_actual'];
420 }
421
422 $match_price_range_higher = '';
423 if ( !isset($applicant_profile['match_price_range_higher_actual']) || ( isset($applicant_profile['match_price_range_higher_actual']) && $applicant_profile['match_price_range_higher_actual'] == '' ) )
424 {
425 if ( isset($applicant_profile['max_price_actual']) && $applicant_profile['max_price_actual'] != '' )
426 {
427 if ( $percentage_higher != '' )
428 {
429 $match_price_range_higher = $applicant_profile['max_price_actual'] + ( $applicant_profile['max_price_actual'] * ( $percentage_higher / 100 ) );
430 }
431 }
432 }
433 else
434 {
435 $match_price_range_higher = $applicant_profile['match_price_range_higher_actual'];
436 }
437
438 if ( $match_price_range_lower != '' && $match_price_range_higher != '' )
439 {
440 $meta_query[] = array(
441 'key' => '_price_actual',
442 'value' => array($match_price_range_lower, $match_price_range_higher),
443 'compare' => 'BETWEEN',
444 'type' => 'NUMERIC'
445 );
446 }
447 }
448 elseif ( isset($applicant_profile['max_price_actual']) && !empty($applicant_profile['max_price_actual']) )
449 {
450 $meta_query[] = array(
451 'key' => '_price_actual',
452 'value' => $applicant_profile['max_price_actual'],
453 'compare' => '<=',
454 'type' => 'NUMERIC'
455 );
456 }
457 }
458 elseif (
459 isset($applicant_profile['department']) &&
460 (
461 $applicant_profile['department'] == 'residential-lettings' ||
462 ph_get_custom_department_based_on($applicant_profile['department']) == 'residential-lettings'
463 )
464 )
465 {
466 $meta_query[] = array(
467 'key' => '_department',
468 'value' => $applicant_profile['department']
469 );
470 if ( isset($applicant_profile['max_price_actual']) && $applicant_profile['max_price_actual'] != '' && $applicant_profile['max_price_actual'] != 0 )
471 {
472 $meta_query[] = array(
473 'key' => '_price_actual',
474 'value' => $applicant_profile['max_price_actual'],
475 'compare' => '<=',
476 'type' => 'NUMERIC'
477 );
478 }
479 }
480
481 if (
482 isset($applicant_profile['department']) &&
483 (
484 $applicant_profile['department'] == 'residential-sales' ||
485 $applicant_profile['department'] == 'residential-lettings' ||
486 ph_get_custom_department_based_on($applicant_profile['department']) == 'residential-sales' ||
487 ph_get_custom_department_based_on($applicant_profile['department']) == 'residential-lettings'
488 )
489 )
490 {
491 if ( isset($applicant_profile['min_beds']) && $applicant_profile['min_beds'] != '' && $applicant_profile['min_beds'] != 0 )
492 {
493 $meta_query[] = array(
494 'key' => '_bedrooms',
495 'value' => $applicant_profile['min_beds'],
496 'compare' => '>=',
497 'type' => 'NUMERIC'
498 );
499 }
500 }
501 if (
502 isset($applicant_profile['department']) &&
503 (
504 $applicant_profile['department'] == 'commercial' ||
505 ph_get_custom_department_based_on($applicant_profile['department']) == 'commercial'
506 )
507 )
508 {
509 if ( isset($applicant_profile['available_as']) && is_array($applicant_profile['available_as']) && !empty($applicant_profile['available_as']) )
510 {
511 if ( in_array('sale', $applicant_profile['available_as']) && !in_array('rent', $applicant_profile['available_as']) )
512 {
513 $meta_query[] = array(
514 'key' => '_for_sale',
515 'value' => 'yes',
516 );
517 }
518 if ( !in_array('sale', $applicant_profile['available_as']) && in_array('rent', $applicant_profile['available_as']) )
519 {
520 $meta_query[] = array(
521 'key' => '_to_rent',
522 'value' => 'yes',
523 );
524 }
525 if ( in_array('sale', $applicant_profile['available_as']) && in_array('rent', $applicant_profile['available_as']) )
526 {
527 // Do nothing as both are ticked
528 }
529 }
530
531 if (
532 !isset($applicant_profile['min_floor_area_actual']) ||
533 ( isset($applicant_profile['min_floor_area_actual']) && $applicant_profile['min_floor_area_actual'] === '')
534 )
535 {
536 $applicant_profile['min_floor_area_actual'] = 0;
537 }
538 if (
539 !isset($applicant_profile['max_floor_area_actual']) ||
540 ( isset($applicant_profile['max_floor_area_actual']) && $applicant_profile['max_floor_area_actual'] === '')
541 )
542 {
543 $applicant_profile['max_floor_area_actual'] = 99999999999;
544 }
545 $meta_query[] = array(
546 'key' => '_floor_area_from_sqft',
547 'value' => $applicant_profile['max_floor_area_actual'],
548 'compare' => '<=',
549 'type' => 'NUMERIC'
550 );
551 $meta_query[] = array(
552 'key' => '_floor_area_to_sqft',
553 'value' => $applicant_profile['min_floor_area_actual'],
554 'compare' => '>=',
555 'type' => 'NUMERIC'
556 );
557 }
558
559 if ( get_option('propertyhive_applicant_locations_type') == 'text' )
560 {
561 if ( isset($applicant_profile['location_text']) && $applicant_profile['location_text'] != '' )
562 {
563 $address_keywords = array( $applicant_profile['location_text'] );
564 if ( strpos( $applicant_profile['location_text'], ' ' ) !== FALSE )
565 {
566 $address_keywords[] = str_replace(" ", "-", ph_clean($applicant_profile['location_text']));
567 }
568 if ( strpos( $applicant_profile['location_text'], '-' ) !== FALSE )
569 {
570 $address_keywords[] = str_replace("-", " ", ph_clean($applicant_profile['location_text']));
571 }
572
573 if ( strpos( $applicant_profile['location_text'], '.' ) !== FALSE )
574 {
575 $address_keywords[] = str_replace(".", "", ph_clean($applicant_profile['location_text']));
576 }
577 if ( stripos( $applicant_profile['location_text'], 'st ' ) !== FALSE )
578 {
579 $address_keywords[] = str_ireplace("st ", "st. ", ph_clean($applicant_profile['location_text']));
580 }
581
582 $location_query = array('relation' => 'OR');
583
584 $address_fields_to_query = array(
585 '_address_street',
586 '_address_two',
587 '_address_three',
588 '_address_four',
589 '_address_postcode'
590 );
591
592 $address_fields_to_query = apply_filters( 'propertyhive_address_fields_to_query', $address_fields_to_query );
593
594 $address_keyword_compare = get_option( 'propertyhive_address_keyword_compare', '=' );
595 if ( $address_keyword_compare == 'polygon' )
596 {
597 $address_keyword_compare = apply_filters('propertyhive_property_match_address_keyword_compare', '=');
598 }
599
600 foreach ( $address_keywords as $address_keyword )
601 {
602 foreach ( $address_fields_to_query as $address_field )
603 {
604 if ( $address_field == '_address_postcode' ) { continue; } // ignore postcode as that is handled differently afterwards
605
606 $location_query[] = array(
607 'key' => $address_field,
608 'value' => $address_keyword,
609 'compare' => $address_keyword_compare
610 );
611 }
612 }
613 if ( in_array('_address_postcode', $address_fields_to_query) )
614 {
615 if ( strlen($applicant_profile['location_text']) <= 4 )
616 {
617 $location_query[] = array(
618 'key' => '_address_postcode',
619 'value' => ph_clean( $applicant_profile['location_text'] ),
620 'compare' => '='
621 );
622 // Run regex match where given keyword is at the start of the postcode ^
623 // followed by one or zero letters (for WC2E-style postcodes) [a-zA-Z]?
624 // then a single space [ ]
625 $location_query[] = array(
626 'key' => '_address_postcode',
627 'value' => '^' . ph_clean( $applicant_profile['location_text'] ) . '[a-zA-Z]?[ ]',
628 'compare' => 'RLIKE'
629 );
630 }
631 else
632 {
633 $postcode = ph_clean( $applicant_profile['location_text'] );
634
635 if ( preg_match('#^(GIR ?0AA|[A-PR-UWYZ]([0-9]{1,2}|([A-HK-Y][0-9]([0-9ABEHMNPRV-Y])?)|[0-9][A-HJKPS-UW])[0-9][ABD-HJLNP-UW-Z]{2})$#i', $postcode) )
636 {
637 // UK postcode found with no space
638
639 if ( strlen($postcode) == 5 )
640 {
641 $first_part = substr($postcode, 0, 2);
642 $last_part = substr($postcode, 2, 3);
643
644 $postcode = $first_part . ' ' . $last_part;
645 }
646 elseif ( strlen($postcode) == 6 )
647 {
648 $first_part = substr($postcode, 0, 3);
649 $last_part = substr($postcode, 3, 3);
650
651 $postcode = $first_part . ' ' . $last_part;
652 }
653 elseif ( strlen($postcode) == 7 )
654 {
655 $first_part = substr($postcode, 0, 4);
656 $last_part = substr($postcode, 4, 3);
657
658 $postcode = $first_part . ' ' . $last_part;
659 }
660 }
661
662 $location_query[] = array(
663 'key' => '_address_postcode',
664 'value' => ph_clean( $postcode ),
665 'compare' => 'LIKE'
666 );
667 }
668 }
669 $meta_query[] = $location_query;
670 }
671 }
672 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Required property matching criteria (department, price, market state and area) use the plugin's existing metadata schema.
673 $args['meta_query'] = $meta_query;
674
675 // Term query
676 $tax_query = array('relation' => 'AND');
677 if (
678 isset($applicant_profile['department']) &&
679 (
680 $applicant_profile['department'] == 'residential-sales' ||
681 $applicant_profile['department'] == 'residential-lettings' ||
682 ph_get_custom_department_based_on($applicant_profile['department']) == 'residential-sales' ||
683 ph_get_custom_department_based_on($applicant_profile['department']) == 'residential-lettings'
684 )
685 )
686 {
687 if ( isset($applicant_profile['property_types']) && is_array($applicant_profile['property_types']) && !empty($applicant_profile['property_types']) )
688 {
689 $tax_query[] = array(
690 'taxonomy' => 'property_type',
691 'field' => 'term_id',
692 'terms' => $applicant_profile['property_types'],
693 'operator' => 'IN',
694 );
695 }
696 }
697 if (
698 isset($applicant_profile['department']) &&
699 (
700 $applicant_profile['department'] == 'commercial' ||
701 ph_get_custom_department_based_on($applicant_profile['department']) == 'commercial'
702 )
703 )
704 {
705 if ( isset($applicant_profile['commercial_property_types']) && is_array($applicant_profile['commercial_property_types']) && !empty($applicant_profile['commercial_property_types']) )
706 {
707 $tax_query[] = array(
708 'taxonomy' => 'commercial_property_type',
709 'field' => 'term_id',
710 'terms' => $applicant_profile['commercial_property_types'],
711 'operator' => 'IN',
712 );
713 }
714 }
715 if ( get_option('propertyhive_applicant_locations_type') != 'text' )
716 {
717 if ( isset($applicant_profile['locations']) && is_array($applicant_profile['locations']) && !empty($applicant_profile['locations']) )
718 {
719 $tax_query[] = array(
720 'taxonomy' => 'location',
721 'field' => 'term_id',
722 'terms' => $applicant_profile['locations'],
723 'operator' => 'IN',
724 );
725 }
726 }
727 $property_match_statuses = get_option( 'propertyhive_property_match_statuses', '' );
728 if ( $property_match_statuses != '' && is_array($property_match_statuses) && !empty($property_match_statuses) )
729 {
730 $tax_query[] = array(
731 'taxonomy' => 'availability',
732 'field' => 'term_id',
733 'terms' => $property_match_statuses,
734 'operator' => 'IN',
735 );
736 }
737 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Applicant property-type/location and availability constraints require the existing taxonomies; preserve extension query semantics.
738 $args['tax_query'] = $tax_query;
739
740 $args = apply_filters( 'propertyhive_matching_properties_args', $args, $contact_id, $applicant_profile );
741
742 $properties_query = new WP_Query( $args );
743
744 if ( $properties_query->have_posts() )
745 {
746 while ( $properties_query->have_posts() )
747 {
748 $properties_query->the_post();
749
750 $property = new PH_Property($post->ID);
751
752 $properties[] = $property;
753 }
754 }
755 wp_reset_postdata();
756 }
757
758 return $properties;
759 }
760
761 public function send_emails( $contact_id, $applicant_profile, $email_property_ids, $from_name, $from_email_address, $subject, $body, $to_email_address = '', $cc_email_address = '', $bcc_email_address = '' )
762 {
763 global $wpdb;
764
765 $current_user = wp_get_current_user();
766
767 $applicant_profile_details = get_post_meta( $contact_id, '_applicant_profile_' . $applicant_profile, TRUE );
768
769 $contact = new PH_Contact($contact_id);
770 if ( $to_email_address == '' )
771 {
772 $to_email_address = $contact->email_address;
773 }
774
775 $subject = str_replace("[property_count]", count($email_property_ids) . ' propert' . ( ( count($email_property_ids) != 1 ) ? 'ies' : 'y' ), $subject);
776
777 $body = str_replace( '[contact_name]', esc_html( $contact->post_title ), $body );
778 $body = str_replace( '[contact_dear]', esc_html( $contact->dear() ), $body );
779 $body = str_replace("[property_count]", count($email_property_ids) . ' propert' . ( ( count($email_property_ids) != 1 ) ? 'ies' : 'y' ), $body);
780
781 $office_counts = array();
782
783 if ( strpos($body, '[properties]') !== FALSE )
784 {
785 ob_start();
786 if ( !empty($email_property_ids) )
787 {
788 foreach ( $email_property_ids as $email_property_id )
789 {
790
791 $property = new PH_Property((int)$email_property_id);
792
793 if ( $property->office_id != '' && $property->office_id != 0 )
794 {
795 if ( !isset($office_counts[$property->office_id]) ) { $office_counts[$property->office_id] = 0; }
796 ++$office_counts[$property->office_id];
797 }
798
799 ph_get_template( 'emails/applicant-match-property.php', array( 'property' => $property ) );
800 }
801 }
802 $body = str_replace("[properties]", ob_get_clean(), $body);
803 }
804
805 // Get email address of office with most properties
806 $office_name = '';
807 $office_email_address = '';
808
809 $office_id = get_user_meta($current_user->ID, 'office_id', TRUE);
810 if ($office_id == '')
811 {
812 // No office against user. Use email address of office with most properties
813 if ( !empty($office_counts) )
814 {
815 arsort($office_counts);
816 reset($office_counts);
817 $office_id = key($office_counts);
818 }
819 }
820
821 if ( !empty($office_id) )
822 {
823 $office_name = get_the_title($office_id);
824 $office_email_address = get_post_meta( $office_id, '_office_email_address_' . str_replace("residential-", "", $applicant_profile_details['department']), TRUE );
825 }
826
827 $body = str_replace( '[office_name]', esc_html( $office_name ), $body );
828 $body = str_replace( '[office_email_address]', esc_html( $office_email_address ), $body );
829
830 $body = str_replace( '[negotiator_name]', esc_html( $current_user->display_name ), $body );
831 $body = str_replace( '[negotiator_email_address]', esc_html( $current_user->user_email ), $body );
832
833 $body = stripslashes($body);
834
835 if (extension_loaded('zlib'))
836 {
837 $compressed_body = @gzcompress($body);
838 if ( $compressed_body !== false )
839 {
840 $body = $compressed_body;
841 }
842 }
843
844 // Insert into email log
845 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Typed insert into the plugin-owned email queue table; no WordPress object API represents these queued messages.
846 $insert = $wpdb->insert(
847 $wpdb->prefix . 'ph_email_log',
848 array(
849 'contact_id' => $contact_id,
850 'property_ids' => serialize($email_property_ids),
851 'applicant_profile_id' => $applicant_profile,
852 'to_email_address' => $to_email_address,
853 'cc_email_address' => $cc_email_address,
854 'bcc_email_address' => $bcc_email_address,
855 'from_name' => $from_name,
856 'from_email_address' => $from_email_address,
857 'subject' => stripslashes($subject),
858 'body' => $body,
859 'status' => '',
860 'send_at' => gmdate("Y-m-d H:i:s"),
861 'sent_by' => $current_user->ID,
862 ),
863 array(
864 '%d',
865 '%s',
866 '%d',
867 '%s',
868 '%s',
869 '%s',
870 '%s',
871 '%s',
872 '%s',
873 '%s',
874 '%s',
875 '%s',
876 '%d',
877 )
878 );
879
880 if ( $insert !== FALSE )
881 {
882 $email_log_id = $wpdb->insert_id;
883
884 // Insert properties into applicant match history
885 $applicant_profile_match_history = get_post_meta( $contact_id, '_applicant_profile_' . $applicant_profile . '_match_history', TRUE );
886 if ( !is_array($applicant_profile_match_history) )
887 {
888 $applicant_profile_match_history = array();
889 }
890
891 if ( is_array($email_property_ids) && !empty($email_property_ids) )
892 {
893 foreach ( $email_property_ids as $email_property_id )
894 {
895 if ( !isset($applicant_profile_match_history[$email_property_id]) )
896 {
897 $applicant_profile_match_history[$email_property_id] = array();
898 }
899
900 $applicant_profile_match_history[$email_property_id][] = array(
901 'date' => gmdate("Y-m-d H:i:s"),
902 'method' => 'email',
903 'email_log_id' => $email_log_id,
904 );
905
906 // Add note/comment to property
907 $comment = array(
908 'note_type' => 'mailout',
909 'method' => 'email',
910 'email_log_id' => $email_log_id
911 );
912
913 $data = array(
914 'comment_post_ID' => $email_property_id,
915 'comment_author' => $current_user->display_name,
916 'comment_author_email' => 'propertyhive@noreply.com',
917 'comment_author_url' => '',
918 'comment_date' => gmdate("Y-m-d H:i:s"),
919 'comment_content' => serialize($comment),
920 'comment_approved' => 1,
921 'comment_type' => 'propertyhive_note',
922 );
923 wp_insert_comment( $data );
924
925 }
926
927 update_post_meta( $contact_id, '_applicant_profile_' . $applicant_profile . '_match_history', $applicant_profile_match_history );
928
929 // Add note/comment to contact
930 $comment = array(
931 'note_type' => 'mailout',
932 'method' => 'email',
933 'email_log_id' => $email_log_id
934 );
935
936 $data = array(
937 'comment_post_ID' => $contact_id,
938 'comment_author' => $current_user->display_name,
939 'comment_author_email' => 'propertyhive@noreply.com',
940 'comment_author_url' => '',
941 'comment_date' => gmdate("Y-m-d H:i:s"),
942 'comment_content' => serialize($comment),
943 'comment_approved' => 1,
944 'comment_type' => 'propertyhive_note',
945 );
946 wp_insert_comment( $data );
947 }
948 }
949 }
950
951 }
952
953 endif;
954