PluginProbe
Property Hive / 2.3.0
Property Hive v2.3.0
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 1.4.62 All 260 releases
propertyhive / includes / class-ph-comments.php

class-ph-comments.php in Property Hive 2.3.0, at includes/class-ph-comments.php

520 lines 18.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit; // Exit if accessed directly
5 }
6
7 /**
8 * Comments
9 *
10 * Handle comments.
11 *
12 * @class PH_Comments
13 * @version 1.0.0
14 * @package PropertyHive/Classes/Products
15 * @category Class
16 * @author PropertyHive
17 */
18 class PH_Comments {
19
20 /**
21 * Hook in methods.
22 */
23 public static function init() {
24 // Secure propertyhive notes
25 add_filter( 'comments_clauses', array( __CLASS__, 'exclude_note_comments' ), 10, 1 );
26
27 add_filter( 'comments_clauses', array( __CLASS__, 'related_to_or_post_id' ), 20, 1 );
28
29 // Count comments
30 add_filter( 'wp_count_comments', array( __CLASS__, 'wp_count_comments' ), 99, 2 );
31
32 // Delete comments count cache whenever there is a new comment or a comment status changes
33 add_action( 'wp_insert_comment', array( __CLASS__, 'delete_comments_count_cache' ) );
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 );
42 }
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
429 /**
430 * Exclude propertyhive notes from queries and RSS.
431 *
432 * This code should exclude propertyhive_note comments from queries. Some queries (like the recent comments widget on the dashboard) are hardcoded.
433 * @param array $clauses
434 * @return array
435 */
436 public static function exclude_note_comments( $clauses ) {
437 //global $wpdb, $typenow;
438
439 if ( is_admin() && current_user_can( 'manage_propertyhive' ) && function_exists( 'get_current_screen' ) )
440 {
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'] ) ) : '';
448
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 )
458 {
459 return $clauses; // Don't hide when viewing Property Hive record
460 }
461 }
462
463 $clauses['where'] .= ' AND comment_type != "propertyhive_note"';
464
465 return $clauses;
466 }
467
468 /**
469 * Delete comments count cache whenever there is
470 * new comment or the status of a comment changes. Cache
471 * will be regenerated next time PH_Comments::wp_count_comments()
472 * is called.
473 *
474 * @return void
475 */
476 public static function delete_comments_count_cache() {
477 delete_transient( 'ph_count_comments' );
478 }
479 /**
480 * Remove propertyhive notes from wp_count_comments().
481 * @since 1.0.0
482 * @param object $stats
483 * @param int $post_id
484 * @return object
485 */
486 public static function wp_count_comments( $stats, $post_id ) {
487 global $wpdb;
488 if ( 0 === $post_id ) {
489 $stats = get_transient( 'ph_count_comments' );
490 if ( ! $stats ) {
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.
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 );
494 $total = 0;
495 $approved = array( '0' => 'moderated', '1' => 'approved', 'spam' => 'spam', 'trash' => 'trash', 'post-trashed' => 'post-trashed' );
496 foreach ( (array) $count as $row ) {
497 // Don't count post-trashed toward totals
498 if ( 'post-trashed' != $row['comment_approved'] && 'trash' != $row['comment_approved'] ) {
499 $total += $row['num_comments'];
500 }
501 if ( isset( $approved[ $row['comment_approved'] ] ) ) {
502 $stats[ $approved[ $row['comment_approved'] ] ] = $row['num_comments'];
503 }
504 }
505 $stats['total_comments'] = $total;
506 $stats['all'] = $total;
507 foreach ( $approved as $key ) {
508 if ( empty( $stats[ $key ] ) ) {
509 $stats[ $key ] = 0;
510 }
511 }
512 $stats = (object) $stats;
513 set_transient( 'ph_count_comments', $stats );
514 }
515 }
516 return $stats;
517 }
518 }
519
520 PH_Comments::init();