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 +412 -3 1.4.62.3.0 View file →
@@ -6,9 +6,9 @@
6 6
7 7 /**
8 8 * Comments
9 9 *
10 - * Handle comments (reviews and order notes).
10 + * Handle comments.
11 11 *
12 12 * @class PH_Comments
13 13 * @version 1.0.0
14 14 * @package PropertyHive/Classes/Products
@@ -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
@@ -29,10 +31,402 @@
29 31
30 32 // Delete comments count cache whenever there is a new comment or a comment status changes
31 33 add_action( 'wp_insert_comment', array( __CLASS__, 'delete_comments_count_cache' ) );
32 34 add_action( 'wp_set_comment_status', array( __CLASS__, 'delete_comments_count_cache' ) );
35 +
36 + add_action( 'add_post_meta', array( __CLASS__, 'check_on_market_add' ), 10, 3 );
37 + add_action( 'update_post_meta', array( __CLASS__, 'check_on_market_update' ), 10, 4 );
38 +
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 );
33 42 }
34 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 +
313 + public static function check_price_change( $meta_id, $object_id, $meta_key, $meta_value )
314 + {
315 + if ( get_post_type($object_id) == 'property' && ( $meta_key == '_price' || $meta_key == '_rent' ) )
316 + {
317 + $original_value = get_post_meta( $object_id, $meta_key, TRUE );
318 +
319 + if ( $original_value != $meta_value )
320 + {
321 + if ( apply_filters( 'propertyhive_add_property_price_change_note', true ) === true )
322 + {
323 + $current_user = wp_get_current_user();
324 +
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 + );
332 +
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 + }
345 +
346 + update_post_meta( $object_id, '_price_change_date', gmdate("Y-m-d H:i:s") );
347 + }
348 + }
349 + }
350 +
351 + public static function check_on_market_add( $object_id, $meta_key, $meta_value )
352 + {
353 + if ( get_post_type($object_id) == 'property' && $meta_key == '_on_market' )
354 + {
355 + if ( $meta_value == 'yes' )
356 + {
357 + if ( apply_filters( 'propertyhive_add_property_on_market_change_note', true ) === true )
358 + {
359 + $note_action = 'property_on_market';
360 +
361 + $current_user = wp_get_current_user();
362 +
363 + // Add note/comment to property
364 + $comment = array(
365 + 'note_type' => 'action',
366 + 'action' => $note_action,
367 + );
368 +
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 + }
381 +
382 + update_post_meta( $object_id, '_on_market_change_date', gmdate("Y-m-d H:i:s") );
383 + }
384 + }
385 + }
386 +
387 + public static function check_on_market_update( $meta_id, $object_id, $meta_key, $meta_value )
388 + {
389 + if ( get_post_type($object_id) == 'property' && $meta_key == '_on_market' )
390 + {
391 + $original_value = get_post_meta( $object_id, $meta_key, TRUE );
392 +
393 + if ( $original_value != $meta_value )
394 + {
395 + if ( apply_filters( 'propertyhive_add_property_on_market_change_note', true ) === true )
396 + {
397 + $note_action = 'property_off_market';
398 + if ($meta_value == 'yes')
399 + {
400 + $note_action = 'property_on_market';
401 + }
402 +
403 + $current_user = wp_get_current_user();
404 +
405 + // Add note/comment to property
406 + $comment = array(
407 + 'note_type' => 'action',
408 + 'action' => $note_action,
409 + );
410 +
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 + }
423 +
424 + update_post_meta( $object_id, '_on_market_change_date', gmdate("Y-m-d H:i:s") );
425 + }
426 + }
427 + }
428 +
35 429 /**
36 430 * Exclude propertyhive notes from queries and RSS.
37 431 *
38 432 * This code should exclude propertyhive_note comments from queries. Some queries (like the recent comments widget on the dashboard) are hardcoded.
@@ -41,13 +435,27 @@
41 435 */
42 436 public static function exclude_note_comments( $clauses ) {
43 437 //global $wpdb, $typenow;
44 438
45 - if ( is_admin() && function_exists( 'get_current_screen' ) )
439 + if ( is_admin() && current_user_can( 'manage_propertyhive' ) && function_exists( 'get_current_screen' ) )
46 440 {
47 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'] ) ) : '';
48 448
49 - if ( isset($screen->id) && in_array( $screen->id, array( 'property', 'contact', 'enquiry', '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 + )
50 458 {
51 459 return $clauses; // Don't hide when viewing Property Hive record
52 460 }
53 461 }
@@ -80,8 +488,9 @@
80 488 if ( 0 === $post_id ) {
81 489 $stats = get_transient( 'ph_count_comments' );
82 490 if ( ! $stats ) {
83 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.
84 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 );
85 494 $total = 0;
86 495 $approved = array( '0' => 'moderated', '1' => 'approved', 'spam' => 'spam', 'trash' => 'trash', 'post-trashed' => 'post-trashed' );
87 496 foreach ( (array) $count as $row ) {