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
← All changes | includes/class-ph-comments.php +360 -63 1.4.492.3.0 View file →
@@ -23,8 +23,10 @@
23 23 public static function init() {
24 24 // Secure propertyhive notes
25 25 add_filter( 'comments_clauses', array( __CLASS__, 'exclude_note_comments' ), 10, 1 );
26 26
27 + add_filter( 'comments_clauses', array( __CLASS__, 'related_to_or_post_id' ), 20, 1 );
28 +
27 29 // Count comments
28 30 add_filter( 'wp_count_comments', array( __CLASS__, 'wp_count_comments' ), 99, 2 );
29 31
30 32 // Delete comments count cache whenever there is a new comment or a comment status changes
@@ -34,10 +36,281 @@
34 36 add_action( 'add_post_meta', array( __CLASS__, 'check_on_market_add' ), 10, 3 );
35 37 add_action( 'update_post_meta', array( __CLASS__, 'check_on_market_update' ), 10, 4 );
36 38
37 39 add_action( 'update_post_meta', array( __CLASS__, 'check_price_change' ), 10, 4 );
40 +
41 + add_action( 'set_object_terms', array( __CLASS__, 'check_property_status_update' ), 10, 6 );
38 42 }
39 43
44 + public static function check_property_status_update( $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids )
45 + {
46 + if ( $taxonomy == 'availability' )
47 + {
48 + if (
49 + get_post_type($object_id) == 'property' &&
50 + get_post_status($object_id) == 'publish' &&
51 + $tt_ids != $old_tt_ids
52 + )
53 + {
54 + if ( apply_filters( 'propertyhive_add_property_availability_change_note', true ) === true )
55 + {
56 + $all_availability_terms = get_terms( array_merge( wp_parse_args( array( 'hide_empty' => 0 ) ), array( 'taxonomy' => 'availability' ) ) );
57 +
58 + $old_availability_id = '';
59 + $old_availability_name = '';
60 + if ( is_array($old_tt_ids) && !empty($old_tt_ids) )
61 + {
62 + $old_availability_id = (int)$old_tt_ids[0];
63 +
64 + foreach( $all_availability_terms as $term )
65 + {
66 + $tt_id = (int)$term->term_taxonomy_id;
67 + if( $tt_id == $old_availability_id )
68 + {
69 + $old_availability_name = $term->name;
70 + }
71 + }
72 + }
73 +
74 + $new_availability_id = '';
75 + $new_availability_name = '';
76 + if ( is_array($tt_ids) && !empty($tt_ids) )
77 + {
78 + $new_availability_id = (int)$tt_ids[0];
79 +
80 + foreach( $all_availability_terms as $term )
81 + {
82 + $tt_id = (int)$term->term_taxonomy_id;
83 + if( $tt_id == $new_availability_id )
84 + {
85 + $new_availability_name = $term->name;
86 + }
87 + }
88 + }
89 +
90 + $current_user = wp_get_current_user();
91 +
92 + // Add note/comment to property
93 + $comment = array(
94 + 'note_type' => 'action',
95 + 'action' => 'property_availability_change',
96 + 'original_value' => $old_availability_name,
97 + 'new_value' => $new_availability_name
98 + );
99 +
100 + $data = array(
101 + 'comment_post_ID' => (int)$object_id,
102 + 'comment_author' => $current_user->display_name,
103 + 'comment_author_email' => 'propertyhive@noreply.com',
104 + 'comment_author_url' => '',
105 + 'comment_date' => gmdate("Y-m-d H:i:s"),
106 + 'comment_content' => serialize($comment),
107 + 'comment_approved' => 1,
108 + 'comment_type' => 'propertyhive_note',
109 + );
110 + $comment_id = wp_insert_comment( $data );
111 + }
112 +
113 + update_post_meta( $object_id, '_availability_change_date', gmdate("Y-m-d H:i:s") );
114 + }
115 + }
116 + }
117 +
118 + public static function related_to_or_post_id( $clauses )
119 + {
120 + global $wpdb, $post;
121 +
122 + if ( strpos($clauses['where'], 'related_to') !== FALSE )
123 + {
124 + $clauses['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $clauses['join'] );
125 +
126 + // Remove main post ID constraint as it's handled by OR below
127 + $strpos_post_id = strpos($clauses['where'], 'comment_post_ID');
128 + if ( $strpos_post_id !== FALSE )
129 + {
130 + $strpos_next_and = strpos($clauses['where'], 'AND', $strpos_post_id);
131 + if ( $strpos_next_and !== FALSE )
132 + {
133 + $clauses['where'] = substr_replace($clauses['where'], ' 1=1 ', $strpos_post_id, $strpos_next_and - $strpos_post_id );
134 + }
135 + }
136 +
137 + // we're searching for related_to so should check where main post is comment_post_ID
138 + $clauses['where'] = str_replace(
139 + $wpdb->prefix . 'commentmeta.meta_key',
140 + '( ' . $wpdb->prefix . 'commentmeta.meta_key',
141 + $clauses['where']
142 + );
143 + $clauses['where'] .= ' OR comment_post_ID = "' . $post->ID . '" ) ';
144 + }
145 +
146 + return $clauses;
147 + }
148 +
149 + public static function insert_note( $post_id, $comment )
150 + {
151 + $current_user = wp_get_current_user();
152 +
153 + $post_type = get_post_type( $post_id );
154 +
155 + $related_to = array( $post_id );
156 +
157 + switch ( $post_type )
158 + {
159 + case "property": {
160 + // get property owner
161 + $owner_contact_ids = get_post_meta( $post_id, '_owner_contact_id', TRUE );
162 + if ( !empty($owner_contact_ids) )
163 + {
164 + if ( !is_array($owner_contact_ids) )
165 + {
166 + $owner_contact_ids = array( $owner_contact_ids );
167 + }
168 + foreach ( $owner_contact_ids as $owner_contact_id )
169 + {
170 + $related_to[] = $owner_contact_id;
171 + }
172 + }
173 + break;
174 + }
175 + case "contact": {
176 + // check contact type, then add to property if owner
177 + $contact_types = get_post_meta( $post_id, '_contact_types', TRUE );
178 + $contact_types = is_array( $contact_types ) ? $contact_types : ( is_string( $contact_types ) && '' !== $contact_types ? array( $contact_types ) : array() );
179 + if ( in_array('owner', $contact_types) )
180 + {
181 + // this contact is an owner
182 + // get properties
183 + $args = array(
184 + 'post_type' => 'property',
185 + 'nopaging' => true,
186 + 'fields' => 'ids',
187 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Notes must link every owned property; supports both scalar and serialized legacy owner IDs and fetches IDs only.
188 + 'meta_query' => array(
189 + 'relation' => 'OR',
190 + array(
191 + 'key' => '_owner_contact_id',
192 + 'value' => $post_id,
193 + 'compare' => '=',
194 + ),
195 + array(
196 + 'key' => '_owner_contact_id',
197 + 'value' => '"' . $post_id . '"',
198 + 'compare' => 'LIKE',
199 + ),
200 + )
201 + );
202 +
203 + $property_query = new WP_Query( $args );
204 +
205 + if ( $property_query->have_posts() )
206 + {
207 + while ( $property_query->have_posts() )
208 + {
209 + $property_query->the_post();
210 +
211 + $related_to[] = get_the_ID();
212 + }
213 + }
214 + wp_reset_postdata();
215 + }
216 + break;
217 + }
218 + case "appraisal": {
219 + // get potential owner
220 + $property_owner_contact_id = get_post_meta( $post_id, '_property_owner_contact_id', TRUE );
221 + if ( $property_owner_contact_id != '' )
222 + {
223 + $related_to[] = $property_owner_contact_id;
224 + }
225 + break;
226 + }
227 + case "viewing":
228 + case "offer":
229 + case "sale": {
230 + // get property
231 + $property_id = get_post_meta( $post_id, '_property_id', TRUE );
232 + if ( $property_id != '' )
233 + {
234 + $related_to[] = $property_id;
235 +
236 + // get property owner
237 + $owner_contact_ids = get_post_meta( $property_id, '_owner_contact_id', TRUE );
238 + if ( !empty($owner_contact_ids) )
239 + {
240 + if ( !is_array($owner_contact_ids) )
241 + {
242 + $owner_contact_ids = array( $owner_contact_ids );
243 + }
244 + foreach ( $owner_contact_ids as $owner_contact_id )
245 + {
246 + $related_to[] = $owner_contact_id;
247 + }
248 + }
249 + }
250 +
251 + // get applicant
252 + $applicant_ids = get_post_meta( $post_id, '_applicant_contact_id' );
253 + if ( !empty($applicant_ids) )
254 + {
255 + if ( !is_array($applicant_ids) )
256 + {
257 + $applicant_ids = array( $applicant_ids );
258 + }
259 + foreach ( $applicant_ids as $applicant_id )
260 + {
261 + $related_to[] = $applicant_id;
262 + }
263 + }
264 + break;
265 + }
266 + }
267 +
268 + if ( isset($comment['note_type']) && $comment['note_type'] == 'note' && isset($comment['note']) && !empty($comment['note']) )
269 + {
270 + // Regular expression pattern to match {{mention-ID|NAME}} or {{mention-ID}}
271 + $pattern = '/\{\{mention-(\d+)(?:\|([^}]*))?\}\}/';
272 +
273 + // Use preg_match_all to find all matches
274 + if ( preg_match_all($pattern, $comment['note'], $matches, PREG_SET_ORDER) )
275 + {
276 + foreach ($matches as $match)
277 + {
278 + $related_to[] = (int)$match[1];
279 + }
280 + }
281 + }
282 +
283 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Existing public Property Hive extension hook property_insert_note_related_to; changing the established name would detach installed callbacks.
284 + $related_to = apply_filters( 'property_insert_note_related_to', $related_to, $post_id );
285 +
286 + $related_to = array_filter( $related_to );
287 +
288 + // Ensure they all go in as strings to allow LIKE query to work when querying related_to
289 + $new_related_to = array();
290 + foreach ( $related_to as $related_to_value )
291 + {
292 + $new_related_to[] = (string)$related_to_value;
293 + }
294 +
295 + $data = array(
296 + 'comment_post_ID' => $post_id,
297 + 'comment_author' => $current_user->display_name,
298 + 'comment_author_email' => 'propertyhive@noreply.com',
299 + 'comment_author_url' => '',
300 + 'comment_date' => gmdate("Y-m-d H:i:s"),
301 + 'comment_content' => serialize($comment),
302 + 'comment_approved' => 1,
303 + 'comment_type' => 'propertyhive_note',
304 + 'comment_meta' => array(
305 + 'related_to' => $new_related_to,
306 + ),
307 + );
308 + $comment_id = wp_insert_comment( wp_slash( $data ) );
309 +
310 + return $comment_id;
311 + }
312 +
40 313 public static function check_price_change( $meta_id, $object_id, $meta_key, $meta_value )
41 314 {
42 315 if ( get_post_type($object_id) == 'property' && ( $meta_key == '_price' || $meta_key == '_rent' ) )
43 316 {
@@ -44,31 +317,34 @@
44 317 $original_value = get_post_meta( $object_id, $meta_key, TRUE );
45 318
46 319 if ( $original_value != $meta_value )
47 320 {
48 - $current_user = wp_get_current_user();
321 + if ( apply_filters( 'propertyhive_add_property_price_change_note', true ) === true )
322 + {
323 + $current_user = wp_get_current_user();
49 324
50 - // Add note/comment to property
51 - $comment = array(
52 - 'note_type' => 'action',
53 - 'action' => 'property_price_change',
54 - 'original_value' => $original_value,
55 - 'new_value' => $meta_value
56 - );
325 + // Add note/comment to property
326 + $comment = array(
327 + 'note_type' => 'action',
328 + 'action' => 'property_price_change',
329 + 'original_value' => $original_value,
330 + 'new_value' => $meta_value
331 + );
57 332
58 - $data = array(
59 - 'comment_post_ID' => (int)$object_id,
60 - 'comment_author' => $current_user->display_name,
61 - 'comment_author_email' => 'propertyhive@noreply.com',
62 - 'comment_author_url' => '',
63 - 'comment_date' => date("Y-m-d H:i:s"),
64 - 'comment_content' => serialize($comment),
65 - 'comment_approved' => 1,
66 - 'comment_type' => 'propertyhive_note',
67 - );
68 - $comment_id = wp_insert_comment( $data );
333 + $data = array(
334 + 'comment_post_ID' => (int)$object_id,
335 + 'comment_author' => $current_user->display_name,
336 + 'comment_author_email' => 'propertyhive@noreply.com',
337 + 'comment_author_url' => '',
338 + 'comment_date' => gmdate("Y-m-d H:i:s"),
339 + 'comment_content' => serialize($comment),
340 + 'comment_approved' => 1,
341 + 'comment_type' => 'propertyhive_note',
342 + );
343 + $comment_id = wp_insert_comment( $data );
344 + }
69 345
70 - update_post_meta( $object_id, '_price_change_date', date("Y-m-d H:i:s") );
346 + update_post_meta( $object_id, '_price_change_date', gmdate("Y-m-d H:i:s") );
71 347 }
72 348 }
73 349 }
74 350
@@ -77,31 +353,34 @@
77 353 if ( get_post_type($object_id) == 'property' && $meta_key == '_on_market' )
78 354 {
79 355 if ( $meta_value == 'yes' )
80 356 {
81 - $note_action = 'property_on_market';
357 + if ( apply_filters( 'propertyhive_add_property_on_market_change_note', true ) === true )
358 + {
359 + $note_action = 'property_on_market';
82 360
83 - $current_user = wp_get_current_user();
361 + $current_user = wp_get_current_user();
84 362
85 - // Add note/comment to property
86 - $comment = array(
87 - 'note_type' => 'action',
88 - 'action' => $note_action,
89 - );
363 + // Add note/comment to property
364 + $comment = array(
365 + 'note_type' => 'action',
366 + 'action' => $note_action,
367 + );
90 368
91 - $data = array(
92 - 'comment_post_ID' => (int)$object_id,
93 - 'comment_author' => $current_user->display_name,
94 - 'comment_author_email' => 'propertyhive@noreply.com',
95 - 'comment_author_url' => '',
96 - 'comment_date' => date("Y-m-d H:i:s"),
97 - 'comment_content' => serialize($comment),
98 - 'comment_approved' => 1,
99 - 'comment_type' => 'propertyhive_note',
100 - );
101 - $comment_id = wp_insert_comment( $data );
369 + $data = array(
370 + 'comment_post_ID' => (int)$object_id,
371 + 'comment_author' => $current_user->display_name,
372 + 'comment_author_email' => 'propertyhive@noreply.com',
373 + 'comment_author_url' => '',
374 + 'comment_date' => gmdate("Y-m-d H:i:s"),
375 + 'comment_content' => serialize($comment),
376 + 'comment_approved' => 1,
377 + 'comment_type' => 'propertyhive_note',
378 + );
379 + $comment_id = wp_insert_comment( $data );
380 + }
102 381
103 - update_post_meta( $object_id, '_on_market_change_date', date("Y-m-d H:i:s") );
382 + update_post_meta( $object_id, '_on_market_change_date', gmdate("Y-m-d H:i:s") );
104 383 }
105 384 }
106 385 }
107 386
@@ -112,35 +391,38 @@
112 391 $original_value = get_post_meta( $object_id, $meta_key, TRUE );
113 392
114 393 if ( $original_value != $meta_value )
115 394 {
116 - $note_action = 'property_off_market';
117 - if ($meta_value == 'yes')
395 + if ( apply_filters( 'propertyhive_add_property_on_market_change_note', true ) === true )
118 396 {
119 - $note_action = 'property_on_market';
120 - }
397 + $note_action = 'property_off_market';
398 + if ($meta_value == 'yes')
399 + {
400 + $note_action = 'property_on_market';
401 + }
121 402
122 - $current_user = wp_get_current_user();
403 + $current_user = wp_get_current_user();
123 404
124 - // Add note/comment to property
125 - $comment = array(
126 - 'note_type' => 'action',
127 - 'action' => $note_action,
128 - );
405 + // Add note/comment to property
406 + $comment = array(
407 + 'note_type' => 'action',
408 + 'action' => $note_action,
409 + );
129 410
130 - $data = array(
131 - 'comment_post_ID' => (int)$object_id,
132 - 'comment_author' => $current_user->display_name,
133 - 'comment_author_email' => 'propertyhive@noreply.com',
134 - 'comment_author_url' => '',
135 - 'comment_date' => date("Y-m-d H:i:s"),
136 - 'comment_content' => serialize($comment),
137 - 'comment_approved' => 1,
138 - 'comment_type' => 'propertyhive_note',
139 - );
140 - $comment_id = wp_insert_comment( $data );
411 + $data = array(
412 + 'comment_post_ID' => (int)$object_id,
413 + 'comment_author' => $current_user->display_name,
414 + 'comment_author_email' => 'propertyhive@noreply.com',
415 + 'comment_author_url' => '',
416 + 'comment_date' => gmdate("Y-m-d H:i:s"),
417 + 'comment_content' => serialize($comment),
418 + 'comment_approved' => 1,
419 + 'comment_type' => 'propertyhive_note',
420 + );
421 + $comment_id = wp_insert_comment( $data );
422 + }
141 423
142 - update_post_meta( $object_id, '_on_market_change_date', date("Y-m-d H:i:s") );
424 + update_post_meta( $object_id, '_on_market_change_date', gmdate("Y-m-d H:i:s") );
143 425 }
144 426 }
145 427 }
146 428
@@ -153,13 +435,27 @@
153 435 */
154 436 public static function exclude_note_comments( $clauses ) {
155 437 //global $wpdb, $typenow;
156 438
157 - if ( is_admin() && function_exists( 'get_current_screen' ) )
439 + if ( is_admin() && current_user_can( 'manage_propertyhive' ) && function_exists( 'get_current_screen' ) )
158 440 {
159 441 $screen = get_current_screen();
442 + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only query visibility filter; the authorized CRM action verifies its own nonce before requesting notes.
443 + $post_action = isset( $_POST['action'] ) && is_string( $_POST['action'] ) ? sanitize_text_field( wp_unslash( $_POST['action'] ) ) : '';
444 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only CRM screen context; manage_propertyhive is checked above.
445 + $get_action = isset( $_GET['action'] ) && is_string( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : '';
446 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only CRM screen context; manage_propertyhive is checked above.
447 + $page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
160 448
161 - if ( isset($screen->id) && in_array( $screen->id, apply_filters( 'propertyhive_post_types_with_notes', array( 'property', 'contact', 'enquiry', 'appraisal', 'viewing', 'offer', 'sale' ) ) ) )
449 + if (
450 + ( isset($screen->id) && in_array( $screen->id, apply_filters( 'propertyhive_post_types_with_notes', array( 'property', 'contact', 'enquiry', 'appraisal', 'viewing', 'offer', 'sale', 'tenancy' ) ) ) )
451 + ||
452 + ( wp_doing_ajax() && in_array( $post_action, array( 'propertyhive_get_notes_grid', 'propertyhive_get_pinned_notes_grid', 'propertyhive_merge_contact_records' ), true ) )
453 + ||
454 + ( wp_doing_ajax() && strpos( $get_action, 'propertyhive_' ) !== FALSE && strpos( $get_action, '_lightbox' ) !== FALSE )
455 + ||
456 + ( substr( $page, 0, 3 ) == 'ph-' )
457 + )
162 458 {
163 459 return $clauses; // Don't hide when viewing Property Hive record
164 460 }
165 461 }
@@ -192,8 +488,9 @@
192 488 if ( 0 === $post_id ) {
193 489 $stats = get_transient( 'ph_count_comments' );
194 490 if ( ! $stats ) {
195 491 $stats = array();
492 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Aggregate excludes private CRM notes; ph_count_comments transient above caches this result and comment mutations invalidate it.
196 493 $count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} WHERE comment_type != 'propertyhive_note' GROUP BY comment_approved", ARRAY_A );
197 494 $total = 0;
198 495 $approved = array( '0' => 'moderated', '1' => 'approved', 'spam' => 'spam', 'trash' => 'trash', 'post-trashed' => 'post-trashed' );
199 496 foreach ( (array) $count as $row ) {