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-post-types.php +1123 -121 1.4.62.3.0 View file →
@@ -13,20 +13,302 @@
13 13 * @package PropertyHive/Classes
14 14 * @category Class
15 15 * @author PropertyHive
16 16 */
17 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Legacy public PH_Post_types class; preserve its existing name for plugin and extension compatibility.
17 18 class PH_Post_types {
18 19
20 +
21 + /**
22 + * Keep native CRM screens separate from ordinary WordPress post access.
23 + * Existing post permissions remain required, alongside Property Hive access.
24 + *
25 + * @return array Dedicated primitive capabilities.
26 + */
27 + public static function record_capabilities() {
28 + $capabilities = array();
29 + foreach ( array( 'edit_posts', 'create_posts', 'edit_others_posts', 'edit_private_posts', 'edit_published_posts', 'publish_posts', 'delete_posts', 'delete_others_posts', 'delete_private_posts', 'delete_published_posts', 'read_private_posts' ) as $capability ) {
30 + $capabilities[$capability] = 'propertyhive_' . $capability;
31 + }
32 + return $capabilities;
33 + }
34 +
35 + /**
36 + * Resolve both direct checks and capabilities produced by core's post mapper.
37 + * This preserves custom roles' existing edit/publish/delete restrictions.
38 + *
39 + * @param array $caps Required primitive capabilities.
40 + * @return array Required capabilities.
41 + */
42 + public static function map_record_capabilities( $caps ) {
43 + $mapped = array();
44 + $record_caps = array_flip( self::record_capabilities() );
45 + foreach ( $caps as $capability ) {
46 + if ( isset( $record_caps[$capability] ) ) {
47 + $mapped[] = 'manage_propertyhive';
48 + $mapped[] = 'create_posts' === $record_caps[$capability] ? 'edit_posts' : $record_caps[$capability];
49 + } else {
50 + $mapped[] = $capability;
51 + }
52 + }
53 + return array_values( array_unique( $mapped ) );
54 + }
55 +
19 56 /**
20 57 * Constructor
21 58 */
22 59 public function __construct() {
60 + add_filter( 'map_meta_cap', array( __CLASS__, 'map_record_capabilities' ) );
23 61 add_action( 'init', array( __CLASS__, 'register_taxonomies' ), 5 );
24 62 add_action( 'init', array( __CLASS__, 'register_post_types' ), 5 );
63 + add_action( 'init', array( __CLASS__, 'register_post_statuses' ), 5 );
25 64
65 + add_action( 'wp_trash_post', array( __CLASS__, 'trash_property_children' ), 5 );
66 + add_action( 'wp_trash_post', array( __CLASS__, 'trash_property_enquiries' ), 5 );
67 +
26 68 add_action( 'before_delete_post', array( __CLASS__, 'delete_property_media' ), 5 );
69 + add_action( 'before_delete_post', array( __CLASS__, 'delete_property_children' ), 5 );
70 + add_action( 'before_delete_post', array( __CLASS__, 'delete_property_enquiries' ), 5 );
71 + add_action( 'before_delete_post', array( __CLASS__, 'delete_contact_user' ), 5 );
72 +
73 + add_action( 'delete_user', array( $this, 'delete_contact_user_link' ) );
74 +
75 + add_action( 'save_post', array( __CLASS__, 'create_concatenated_indexable_meta' ), 99, 3 );
76 +
77 + add_action( 'save_post', array( __CLASS__, 'ensure_property_floor_area_to_set' ), 99, 3 );
78 +
79 + add_action( 'save_post', array( __CLASS__, 'update_property_indexed_owner_names' ), 99, 3 );
80 +
81 + add_action( 'save_post', array( __CLASS__, 'store_related_viewings' ), 99, 3 );
82 + add_action( 'updated_post_meta', array( __CLASS__, 'store_related_viewings_meta_change' ), 10, 4 );
83 +
84 + add_action( 'propertyhive_update_address_concatenated', array( __CLASS__, 'update_address_concatenated' ) );
85 +
86 + add_filter( 'get_terms', array( $this, 'put_terms_in_order' ), 10, 4 );
87 +
88 + add_action( 'propertyhive_after_register_post_types', array( __CLASS__, 'maybe_flush_rewrite_rules' ) );
27 89 }
28 90
91 + public static function maybe_flush_rewrite_rules()
92 + {
93 + if ( 'yes' === get_option( 'propertyhive_queue_flush_rewrite_rules' ) ) {
94 + update_option( 'propertyhive_queue_flush_rewrite_rules', 'no' );
95 + flush_rewrite_rules();
96 + }
97 + }
98 +
99 + public static function register_post_statuses()
100 + {
101 + register_post_status(
102 + 'archive',
103 + array(
104 + 'label' => _x('Archived', 'post status label', 'propertyhive'),
105 + 'public' => false,
106 + 'exclude_from_search' => true,
107 + 'show_in_admin_all_list' => false,
108 + 'show_in_admin_status_list' => true,
109 + /* translators: %s: Number of archived posts. */
110 + 'label_count' => _n_noop(
111 + 'Archived <span class="count">(%s)</span>',
112 + 'Archived <span class="count">(%s)</span>',
113 + 'propertyhive'
114 + ),
115 + )
116 + );
117 + }
118 +
119 + public static function update_property_indexed_owner_names( $post_id, $post, $update )
120 + {
121 + // $post_id and $post are required
122 + if ( empty( $post_id ) || empty( $post ) ) {
123 + return;
124 + }
125 +
126 + // Dont' save meta boxes for revisions or autosaves
127 + if ( defined( 'DOING_AUTOSAVE' ) || is_int( wp_is_post_revision( $post ) ) || is_int( wp_is_post_autosave( $post ) ) ) {
128 + return;
129 + }
130 +
131 + if ( $post->post_type !== 'property' && $post->post_type !== 'contact' ) {
132 + return;
133 + }
134 +
135 + if ( $post->post_type == 'property' )
136 + {
137 + // Get the owner contact IDs
138 + $contact_ids = get_post_meta($post_id, '_owner_contact_id', true);
139 + if ( !is_array($contact_ids) )
140 + {
141 + $contact_ids = array($contact_ids);
142 + }
143 +
144 + // Get the names of the contacts
145 + $owner_details = array();
146 + if ( !empty($contact_ids) )
147 + {
148 + foreach ( $contact_ids as $contact_id )
149 + {
150 + $contact_post = get_post($contact_id);
151 + if ( $contact_post && $contact_post->post_type == 'contact' )
152 + {
153 + $owner_details[] = $contact_post->post_title;
154 + if ( get_post_meta( $contact_id, '_telephone_number', TRUE ) != '' )
155 + {
156 + $owner_details[] = get_post_meta( $contact_id, '_telephone_number', TRUE );
157 + }
158 + if ( get_post_meta( $contact_id, '_email_address', TRUE ) != '' )
159 + {
160 + $owner_details[] = get_post_meta( $contact_id, '_email_address', TRUE );
161 + }
162 + }
163 + }
164 +
165 + $owner_details = array_filter($owner_details);
166 + $owner_details = array_unique($owner_details);
167 + }
168 +
169 + // Update the owner names meta field
170 + update_post_meta($post_id, '_owner_details', implode(', ', $owner_details));
171 + }
172 +
173 + if ( $post->post_type == 'contact' )
174 + {
175 + // Find all properties linked to this contact
176 + $args = array(
177 + 'post_type' => 'property',
178 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Owner search metadata must be refreshed on properties linked through the existing serialized owner IDs; retain the relationship match.
179 + 'meta_query' => array(
180 + array(
181 + 'key' => '_owner_contact_id',
182 + 'value' => '"' . $post_id . '"',
183 + 'compare' => 'LIKE'
184 + )
185 + ),
186 + 'posts_per_page' => -1
187 + );
188 +
189 + $properties = get_posts($args);
190 + foreach ( $properties as $property )
191 + {
192 + // Get the owner contact IDs
193 + $contact_ids = get_post_meta($property->ID, '_owner_contact_id', true);
194 + if ( !is_array($contact_ids) )
195 + {
196 + $contact_ids = array($contact_ids);
197 + }
198 +
199 + // Get the names of the contacts
200 + $owner_details = array();
201 + if ( !empty($contact_ids) )
202 + {
203 + foreach ( $contact_ids as $contact_id )
204 + {
205 + $contact_post = get_post($contact_id);
206 + if ( $contact_post && $contact_post->post_type == 'contact' )
207 + {
208 + $owner_details[] = $contact_post->post_title;
209 + if ( get_post_meta( $contact_id, '_telephone_number', TRUE ) != '' )
210 + {
211 + $owner_details[] = get_post_meta( $contact_id, '_telephone_number', TRUE );
212 + }
213 + if ( get_post_meta( $contact_id, '_email_address', TRUE ) != '' )
214 + {
215 + $owner_details[] = get_post_meta( $contact_id, '_email_address', TRUE );
216 + }
217 + }
218 + }
219 +
220 + $owner_details = array_filter($owner_details);
221 + $owner_details = array_unique($owner_details);
222 + }
223 +
224 + // Update the owner names meta field
225 + update_post_meta($property->ID, '_owner_details', implode(', ', $owner_details));
226 + }
227 + }
228 + }
229 +
230 + public static function put_terms_in_order( $terms, $taxonomies, $query_vars, $term_query ) {
231 +
232 + if ( empty($taxonomies) )
233 + {
234 + return $terms;
235 + }
236 +
237 + if ( !isset($query_vars['fields']) || ( isset($query_vars['fields']) && $query_vars['fields'] != 'all' ) )
238 + {
239 + return $terms;
240 + }
241 +
242 + if ( !is_array($taxonomies) ) { $taxonomies = array($taxonomies); }
243 +
244 + foreach ( $taxonomies as $taxonomy_name )
245 + {
246 + $taxonomy = get_taxonomy( $taxonomy_name );
247 +
248 + if ( is_array($taxonomy->object_type) && in_array('property', $taxonomy->object_type) )
249 + {
250 +
251 + }
252 + else
253 + {
254 + return $terms;
255 + }
256 +
257 + $order = get_option( 'propertyhive_taxonomy_terms_order_' . $taxonomy_name, '' );
258 +
259 + if ( empty($order) )
260 + {
261 + return $terms;
262 + }
263 +
264 + // Convert $order string to an array of IDs
265 + $order_array = explode('|', $order);
266 +
267 + // Initialize a new array to store the sorted objects
268 + $sorted_array = array();
269 +
270 + // Loop through each order ID and find matching WP_Term objects
271 + foreach ( $order_array as $order_id )
272 + {
273 + foreach ( $terms as $item )
274 + {
275 + if ( $item->term_id == $order_id )
276 + {
277 + $sorted_array[] = $item;
278 + break; // Stop the inner loop once a match is found
279 + }
280 + }
281 + }
282 +
283 + // Append WP_Term objects not listed in $order at the end of $sorted_array
284 + foreach ( $terms as $item )
285 + {
286 + // Check if the item is not in $sorted_array
287 + $found = false;
288 + foreach ( $sorted_array as $sorted_item )
289 + {
290 + if ( $sorted_item->term_id == $item->term_id )
291 + {
292 + $found = true;
293 + break;
294 + }
295 + }
296 + // If not found, append to $sorted_array
297 + if ( !$found )
298 + {
299 + $sorted_array[] = $item;
300 + }
301 + }
302 +
303 + // Replace the original array with the sorted array
304 + $terms = $sorted_array;
305 + }
306 +
307 + return $terms;
308 +
309 + }
310 +
29 311 /**
30 312 * Register PropertyHive taxonomies.
31 313 */
32 314 public static function register_taxonomies() {
@@ -38,163 +320,245 @@
38 320
39 321 //$permalinks = get_option( 'propertyhive_permalinks' );
40 322
41 323 register_taxonomy( 'availability',
42 - 'property',
43 - array(
44 - 'hierarchical' => true,
45 - 'show_ui' => false,
46 - 'show_in_nav_menus' => false,
47 - 'query_var' => is_admin(),
48 - 'rewrite' => false,
49 - 'public' => true
324 + apply_filters( 'propertyhive_taxonomy_objects_availability', array( 'property' ) ),
325 + apply_filters(
326 + 'propertyhive_taxonomy_args_availability',
327 + array(
328 + 'label' => __( 'Availabilities', 'propertyhive' ),
329 + 'hierarchical' => true,
330 + 'show_ui' => false,
331 + 'show_in_nav_menus' => false,
332 + 'query_var' => is_admin(),
333 + 'rewrite' => false,
334 + 'public' => true,
335 + 'show_in_rest' => true,
336 + )
50 337 )
51 338 );
52 339
53 340 register_taxonomy( 'property_type',
54 - 'property',
55 - array(
56 - 'hierarchical' => true,
57 - 'show_ui' => false,
58 - 'show_in_nav_menus' => false,
59 - 'query_var' => is_admin(),
60 - 'rewrite' => false,
61 - 'public' => true
62 - )
341 + apply_filters( 'propertyhive_taxonomy_objects_property_type', array( 'property', 'appraisal' ) ),
342 + apply_filters(
343 + 'propertyhive_taxonomy_args_property_type',
344 + array(
345 + 'label' => __( 'Property Types', 'propertyhive' ),
346 + 'hierarchical' => true,
347 + 'show_ui' => false,
348 + 'show_in_nav_menus' => false,
349 + 'query_var' => is_admin(),
350 + 'rewrite' => false,
351 + 'public' => true,
352 + 'show_in_rest' => true,
353 + )
354 + )
63 355 );
64 356
65 357 register_taxonomy( 'commercial_property_type',
66 - 'property',
67 - array(
68 - 'hierarchical' => true,
69 - 'show_ui' => false,
70 - 'show_in_nav_menus' => false,
71 - 'query_var' => is_admin(),
72 - 'rewrite' => false,
73 - 'public' => true
358 + apply_filters( 'propertyhive_taxonomy_objects_commercial_property_type', array( 'property', 'appraisal' ) ),
359 + apply_filters(
360 + 'propertyhive_taxonomy_args_commercial_property_type',
361 + array(
362 + 'label' => __( 'Commercial Property Types', 'propertyhive' ),
363 + 'hierarchical' => true,
364 + 'show_ui' => false,
365 + 'show_in_nav_menus' => false,
366 + 'query_var' => is_admin(),
367 + 'rewrite' => false,
368 + 'public' => true,
369 + 'show_in_rest' => true,
370 + )
74 371 )
75 372 );
76 373
77 374 register_taxonomy( 'location',
78 - 'property',
79 - array(
80 - 'hierarchical' => true,
81 - 'show_ui' => false,
82 - 'show_in_nav_menus' => false,
83 - 'query_var' => is_admin(),
84 - 'rewrite' => false,
85 - 'public' => true
375 + apply_filters( 'propertyhive_taxonomy_objects_location', array( 'property' ) ),
376 + apply_filters(
377 + 'propertyhive_taxonomy_args_location',
378 + array(
379 + 'label' => __( 'Locations', 'propertyhive' ),
380 + 'hierarchical' => true,
381 + 'show_ui' => false,
382 + 'show_in_nav_menus' => false,
383 + 'query_var' => is_admin(),
384 + 'rewrite' => false,
385 + 'public' => true,
386 + 'show_in_rest' => true,
387 + )
86 388 )
87 389 );
88 390
89 391 register_taxonomy( 'parking',
90 - 'property',
91 - array(
92 - 'hierarchical' => true,
93 - 'show_ui' => false,
94 - 'show_in_nav_menus' => false,
95 - 'query_var' => is_admin(),
96 - 'rewrite' => false,
97 - 'public' => true
392 + apply_filters( 'propertyhive_taxonomy_objects_parking', array( 'property', 'appraisal' ) ),
393 + apply_filters(
394 + 'propertyhive_taxonomy_args_parking',
395 + array(
396 + 'label' => __( 'Parking', 'propertyhive' ),
397 + 'hierarchical' => true,
398 + 'show_ui' => false,
399 + 'show_in_nav_menus' => false,
400 + 'query_var' => is_admin(),
401 + 'rewrite' => false,
402 + 'public' => true,
403 + 'show_in_rest' => true,
404 + )
98 405 )
99 406 );
100 407
101 408 register_taxonomy( 'outside_space',
102 - 'property',
103 - array(
104 - 'hierarchical' => true,
105 - 'show_ui' => false,
106 - 'show_in_nav_menus' => false,
107 - 'query_var' => is_admin(),
108 - 'rewrite' => false,
109 - 'public' => true
409 + apply_filters( 'propertyhive_taxonomy_objects_outside_space', array( 'property', 'appraisal' ) ),
410 + apply_filters(
411 + 'propertyhive_taxonomy_args_outside_space',
412 + array(
413 + 'label' => __( 'Outside Spaces', 'propertyhive' ),
414 + 'hierarchical' => true,
415 + 'show_ui' => false,
416 + 'show_in_nav_menus' => false,
417 + 'query_var' => is_admin(),
418 + 'rewrite' => false,
419 + 'public' => true,
420 + 'show_in_rest' => true,
421 + )
110 422 )
111 423 );
112 424
113 425 register_taxonomy( 'price_qualifier',
114 - 'property',
115 - array(
116 - 'hierarchical' => true,
117 - 'show_ui' => false,
118 - 'show_in_nav_menus' => false,
119 - 'query_var' => is_admin(),
120 - 'rewrite' => false,
121 - 'public' => true
426 + apply_filters( 'propertyhive_taxonomy_objects_price_qualifier', array( 'property' ) ),
427 + apply_filters(
428 + 'propertyhive_taxonomy_args_price_qualifier',
429 + array(
430 + 'label' => __( 'Price Qualifiers', 'propertyhive' ),
431 + 'hierarchical' => true,
432 + 'show_ui' => false,
433 + 'show_in_nav_menus' => false,
434 + 'query_var' => is_admin(),
435 + 'rewrite' => false,
436 + 'public' => true,
437 + 'show_in_rest' => true,
438 + )
122 439 )
123 440 );
124 441
125 442 register_taxonomy( 'tenure',
126 - 'property',
127 - array(
128 - 'hierarchical' => true,
129 - 'show_ui' => false,
130 - 'show_in_nav_menus' => false,
131 - 'query_var' => is_admin(),
132 - 'rewrite' => false,
133 - 'public' => true
443 + apply_filters( 'propertyhive_taxonomy_objects_tenure', array( 'property' ) ),
444 + apply_filters(
445 + 'propertyhive_taxonomy_args_tenure',
446 + array(
447 + 'label' => __( 'Tenures', 'propertyhive' ),
448 + 'hierarchical' => true,
449 + 'show_ui' => false,
450 + 'show_in_nav_menus' => false,
451 + 'query_var' => is_admin(),
452 + 'rewrite' => false,
453 + 'public' => true,
454 + 'show_in_rest' => true,
455 + )
134 456 )
135 457 );
136 458
137 459 register_taxonomy( 'commercial_tenure',
138 - 'property',
139 - array(
140 - 'hierarchical' => true,
141 - 'show_ui' => false,
142 - 'show_in_nav_menus' => false,
143 - 'query_var' => is_admin(),
144 - 'rewrite' => false,
145 - 'public' => true
460 + apply_filters( 'propertyhive_taxonomy_objects_commercial_tenure', array( 'property' ) ),
461 + apply_filters(
462 + 'propertyhive_taxonomy_args_commercial_tenure',
463 + array(
464 + 'label' => __( 'Commercial Tenures', 'propertyhive' ),
465 + 'hierarchical' => true,
466 + 'show_ui' => false,
467 + 'show_in_nav_menus' => false,
468 + 'query_var' => is_admin(),
469 + 'rewrite' => false,
470 + 'public' => true,
471 + 'show_in_rest' => true,
472 + )
146 473 )
147 474 );
148 475
149 476 register_taxonomy( 'sale_by',
150 - 'property',
151 - array(
152 - 'hierarchical' => true,
153 - 'show_ui' => false,
154 - 'show_in_nav_menus' => false,
155 - 'query_var' => is_admin(),
156 - 'rewrite' => false,
157 - 'public' => true
477 + apply_filters( 'propertyhive_taxonomy_objects_sale_by', array( 'property' ) ),
478 + apply_filters(
479 + 'propertyhive_taxonomy_args_sale_by',
480 + array(
481 + 'label' => __( 'Sale By', 'propertyhive' ),
482 + 'hierarchical' => true,
483 + 'show_ui' => false,
484 + 'show_in_nav_menus' => false,
485 + 'query_var' => is_admin(),
486 + 'rewrite' => false,
487 + 'public' => true,
488 + 'show_in_rest' => true,
489 + )
158 490 )
159 491 );
160 492
161 493 register_taxonomy( 'furnished',
162 - 'property',
163 - array(
164 - 'hierarchical' => true,
165 - 'show_ui' => false,
166 - 'show_in_nav_menus' => false,
167 - 'query_var' => is_admin(),
168 - 'rewrite' => false,
169 - 'public' => true
494 + apply_filters( 'propertyhive_taxonomy_objects_furnished', array( 'property', 'appraisal' ) ),
495 + apply_filters(
496 + 'propertyhive_taxonomy_args_furnished',
497 + array(
498 + 'label' => __( 'Furnished', 'propertyhive' ),
499 + 'hierarchical' => true,
500 + 'show_ui' => false,
501 + 'show_in_nav_menus' => false,
502 + 'query_var' => is_admin(),
503 + 'rewrite' => false,
504 + 'public' => true,
505 + 'show_in_rest' => true,
506 + )
170 507 )
171 508 );
172 509
173 510 register_taxonomy( 'marketing_flag',
174 - 'property',
175 - array(
176 - 'hierarchical' => true,
177 - 'show_ui' => false,
178 - 'show_in_nav_menus' => false,
179 - 'query_var' => is_admin(),
180 - 'rewrite' => false,
181 - 'public' => true
511 + apply_filters( 'propertyhive_taxonomy_objects_marketing_flag', array( 'property' ) ),
512 + apply_filters(
513 + 'propertyhive_taxonomy_args_marketing_flag',
514 + array(
515 + 'label' => __( 'Marketing Flags', 'propertyhive' ),
516 + 'hierarchical' => true,
517 + 'show_ui' => false,
518 + 'show_in_nav_menus' => false,
519 + 'query_var' => is_admin(),
520 + 'rewrite' => false,
521 + 'public' => true,
522 + 'show_in_rest' => true,
523 + )
182 524 )
183 525 );
184 526
185 527 register_taxonomy( 'property_feature',
186 - 'property',
187 - array(
188 - 'hierarchical' => true,
189 - 'show_ui' => false,
190 - 'show_in_nav_menus' => false,
191 - 'query_var' => is_admin(),
192 - 'rewrite' => false,
193 - 'public' => true
528 + apply_filters( 'propertyhive_taxonomy_objects_property_feature', array( 'property' ) ),
529 + apply_filters(
530 + 'propertyhive_taxonomy_args_property_feature',
531 + array(
532 + 'label' => __( 'Property Features', 'propertyhive' ),
533 + 'hierarchical' => true,
534 + 'show_ui' => false,
535 + 'show_in_nav_menus' => false,
536 + 'query_var' => is_admin(),
537 + 'rewrite' => false,
538 + 'public' => true,
539 + 'show_in_rest' => true,
540 + )
194 541 )
195 542 );
543 +
544 + register_taxonomy( 'management_key_date_type',
545 + apply_filters( 'propertyhive_taxonomy_objects_management_key_date_type', array( 'key_date' ) ),
546 + apply_filters(
547 + 'propertyhive_taxonomy_args_management_key_date_type',
548 + array(
549 + 'label' => __( 'Management Dates', 'propertyhive' ),
550 + 'hierarchical' => true,
551 + 'show_ui' => false,
552 + 'show_in_nav_menus' => false,
553 + 'query_var' => is_admin(),
554 + 'rewrite' => false,
555 + 'public' => true
556 + )
557 + )
558 + );
196 559
560 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Existing public Property Hive extension hook do_action_after_register_taxonomies; changing the established name would detach installed callbacks.
197 561 do_action( 'do_action_after_register_taxonomies' );
198 562 }
199 563
200 564 /**
@@ -232,8 +596,9 @@
232 596 'description' => __( 'This is where you can add new properties to your site.', 'propertyhive' ),
233 597 'public' => true,
234 598 'show_ui' => true,
235 599 'capability_type' => 'post',
600 + 'capabilities' => self::record_capabilities(),
236 601 'map_meta_cap' => true,
237 602 'publicly_queryable' => true,
238 603 'exclude_from_search' => false,
239 604 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
@@ -240,9 +605,9 @@
240 605 //'rewrite' => $product_permalink ? array( 'slug' => untrailingslashit( $product_permalink ), 'with_front' => false, 'feeds' => true ) : false,
241 606 'query_var' => true,
242 607 'supports' => array( 'title', 'excerpt' ),
243 608 'has_archive' => ( $search_results_page_id = ph_get_page_id( 'search_results' ) ) && get_page( $search_results_page_id ) ? get_page_uri( $search_results_page_id ) : 'search_results',
244 - 'show_in_nav_menus' => false,
609 + 'show_in_nav_menus' => true,
245 610 'show_in_menu' => false,
246 611 'show_in_admin_bar' => true,
247 612 'show_in_rest' => true,
248 613 )
@@ -271,10 +636,11 @@
271 636 'description' => __( 'This is where you can add new contacts to your site.', 'propertyhive' ),
272 637 'public' => false,
273 638 'show_ui' => true,
274 639 'capability_type' => 'post',
640 + 'capabilities' => self::record_capabilities(),
275 641 'map_meta_cap' => true,
276 - 'publicly_queryable' => true,
642 + 'publicly_queryable' => false,
277 643 'exclude_from_search' => true,
278 644 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
279 645 'query_var' => true,
280 646 'supports' => array( 'title' ),
@@ -286,19 +652,37 @@
286 652
287 653 register_post_type( "office",
288 654 apply_filters( 'propertyhive_register_post_type_office',
289 655 array(
656 + 'labels' => array(
657 + 'name' => __( 'Offices', 'propertyhive' ),
658 + 'singular_name' => __( 'Office', 'propertyhive' ),
659 + 'menu_name' => _x( 'Offices', 'Admin menu name', 'propertyhive' ),
660 + 'add_new' => __( 'Add Office', 'propertyhive' ),
661 + 'add_new_item' => __( 'Add New Office', 'propertyhive' ),
662 + 'edit' => __( 'Edit', 'propertyhive' ),
663 + 'edit_item' => __( 'Edit Office', 'propertyhive' ),
664 + 'new_item' => __( 'New Office', 'propertyhive' ),
665 + 'view' => __( 'View Office', 'propertyhive' ),
666 + 'view_item' => __( 'View Office', 'propertyhive' ),
667 + 'search_items' => __( 'Search Offices', 'propertyhive' ),
668 + 'not_found' => __( 'No offices found', 'propertyhive' ),
669 + 'not_found_in_trash' => __( 'No offices found in trash', 'propertyhive' ),
670 + 'parent' => __( 'Parent Office', 'propertyhive' )
671 + ),
290 672 'public' => true,
291 673 'show_ui' => false,
292 674 'capability_type' => 'post',
675 + 'capabilities' => self::record_capabilities(),
293 676 'map_meta_cap' => true,
294 - 'publicly_queryable' => true,
677 + 'publicly_queryable' => false,
295 678 'exclude_from_search' => true,
296 679 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
297 680 'query_var' => true,
298 681 'supports' => array( 'title' ),
299 682 'show_in_nav_menus' => false,
300 - 'show_in_menu' => false
683 + 'show_in_menu' => false,
684 + 'show_in_rest' => true,
301 685 )
302 686 )
303 687 );
304 688
@@ -323,8 +707,9 @@
323 707 ),
324 708 'public' => false,
325 709 'show_ui' => true,
326 710 'capability_type' => 'post',
711 + 'capabilities' => self::record_capabilities(),
327 712 'map_meta_cap' => true,
328 713 'publicly_queryable' => false,
329 714 'exclude_from_search' => true,
330 715 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
@@ -330,8 +715,44 @@
330 715 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
331 716 'query_var' => true,
332 717 'supports' => array( 'title' ),
333 718 'show_in_nav_menus' => false,
719 + 'show_in_menu' => false,
720 + 'show_in_rest' => true,
721 + )
722 + )
723 + );
724 +
725 + register_post_type( "appraisal",
726 + apply_filters( 'propertyhive_register_post_type_appraisal',
727 + array(
728 + 'labels' => array(
729 + 'name' => __( 'Appraisals', 'propertyhive' ),
730 + 'singular_name' => __( 'Appraisal', 'propertyhive' ),
731 + 'menu_name' => _x( 'Appraisals', 'Admin menu name', 'propertyhive' ),
732 + 'add_new' => __( 'Add Appraisal', 'propertyhive' ),
733 + 'add_new_item' => __( 'Add New Appraisal', 'propertyhive' ),
734 + 'edit' => __( 'Edit', 'propertyhive' ),
735 + 'edit_item' => __( 'Edit Appraisal', 'propertyhive' ),
736 + 'new_item' => __( 'New Appraisal', 'propertyhive' ),
737 + 'view' => __( 'View Appraisal', 'propertyhive' ),
738 + 'view_item' => __( 'View Appraisal', 'propertyhive' ),
739 + 'search_items' => __( 'Search Appraisals', 'propertyhive' ),
740 + 'not_found' => __( 'No appraisals found', 'propertyhive' ),
741 + 'not_found_in_trash' => __( 'No appraisals found in trash', 'propertyhive' ),
742 + 'parent' => __( 'Parent Appraisal', 'propertyhive' )
743 + ),
744 + 'public' => false,
745 + 'show_ui' => true,
746 + 'capability_type' => 'post',
747 + 'capabilities' => self::record_capabilities(),
748 + 'map_meta_cap' => true,
749 + 'publicly_queryable' => false,
750 + 'exclude_from_search' => true,
751 + 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
752 + 'query_var' => true,
753 + 'supports' => false,
754 + 'show_in_nav_menus' => false,
334 755 'show_in_menu' => false
335 756 )
336 757 )
337 758 );
@@ -357,8 +778,9 @@
357 778 ),
358 779 'public' => false,
359 780 'show_ui' => true,
360 781 'capability_type' => 'post',
782 + 'capabilities' => self::record_capabilities(),
361 783 'map_meta_cap' => true,
362 784 'publicly_queryable' => false,
363 785 'exclude_from_search' => true,
364 786 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
@@ -391,8 +813,9 @@
391 813 ),
392 814 'public' => false,
393 815 'show_ui' => true,
394 816 'capability_type' => 'post',
817 + 'capabilities' => self::record_capabilities(),
395 818 'map_meta_cap' => true,
396 819 'publicly_queryable' => false,
397 820 'exclude_from_search' => true,
398 821 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
@@ -425,11 +848,46 @@
425 848 ),
426 849 'public' => false,
427 850 'show_ui' => true,
428 851 'capability_type' => 'post',
429 - 'capabilities' => array(
852 + 'capabilities' => array_merge( self::record_capabilities(), array(
430 853 'create_posts' => false
854 + ) ),
855 + 'map_meta_cap' => true,
856 + 'publicly_queryable' => false,
857 + 'exclude_from_search' => true,
858 + 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
859 + 'query_var' => true,
860 + 'supports' => false,
861 + 'show_in_nav_menus' => false,
862 + 'show_in_menu' => false
863 + )
864 + )
865 + );
866 +
867 + register_post_type( "tenancy",
868 + apply_filters( 'propertyhive_register_post_type_tenancy',
869 + array(
870 + 'labels' => array(
871 + 'name' => __( 'Tenancies', 'propertyhive' ),
872 + 'singular_name' => __( 'Tenancy', 'propertyhive' ),
873 + 'menu_name' => _x( 'Tenancies', 'Admin menu name', 'propertyhive' ),
874 + 'add_new' => __( 'Add Tenancy', 'propertyhive' ),
875 + 'add_new_item' => __( 'Add New Tenancy', 'propertyhive' ),
876 + 'edit' => __( 'Edit', 'propertyhive' ),
877 + 'edit_item' => __( 'Edit Tenancy', 'propertyhive' ),
878 + 'new_item' => __( 'New Tenancy', 'propertyhive' ),
879 + 'view' => __( 'View Tenancy', 'propertyhive' ),
880 + 'view_item' => __( 'View Tenancy', 'propertyhive' ),
881 + 'search_items' => __( 'Search Tenancies', 'propertyhive' ),
882 + 'not_found' => __( 'No tenancies found', 'propertyhive' ),
883 + 'not_found_in_trash' => __( 'No tenancies found in trash', 'propertyhive' ),
884 + 'parent' => __( 'Parent Tenancy', 'propertyhive' )
431 885 ),
886 + 'public' => false,
887 + 'show_ui' => true,
888 + 'capability_type' => 'post',
889 + 'capabilities' => self::record_capabilities(),
432 890 'map_meta_cap' => true,
433 891 'publicly_queryable' => false,
434 892 'exclude_from_search' => true,
435 893 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
@@ -439,29 +897,133 @@
439 897 'show_in_menu' => false
440 898 )
441 899 )
442 900 );
443 -
901 +
902 + register_post_type( "key_date",
903 + apply_filters( 'propertyhive_register_post_type_key_date',
904 + array(
905 + 'labels' => array(
906 + 'name' => __( 'Key Dates', 'propertyhive' ),
907 + 'singular_name' => __( 'Key Date', 'propertyhive' ),
908 + 'menu_name' => _x( 'Management', 'Admin menu name', 'propertyhive' ),
909 + 'add_new' => __( 'Add Key Date', 'propertyhive' ),
910 + 'add_new_item' => __( 'Add New Key Date', 'propertyhive' ),
911 + 'edit' => __( 'Edit', 'propertyhive' ),
912 + 'edit_item' => __( 'Edit Key Date', 'propertyhive' ),
913 + 'new_item' => __( 'New Key Date', 'propertyhive' ),
914 + 'view' => __( 'View Key Date', 'propertyhive' ),
915 + 'view_item' => __( 'View Key Date', 'propertyhive' ),
916 + 'search_items' => __( 'Search Key Dates', 'propertyhive' ),
917 + 'not_found' => __( 'No key dates found', 'propertyhive' ),
918 + 'not_found_in_trash' => __( 'No key dates found in trash', 'propertyhive' ),
919 + 'parent' => __( 'Parent Key Date', 'propertyhive' )
920 + ),
921 + 'public' => false,
922 + 'show_ui' => true,
923 + 'capability_type' => 'post',
924 + 'map_meta_cap' => true,
925 + 'publicly_queryable' => false,
926 + 'exclude_from_search' => true,
927 + 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
928 + 'query_var' => true,
929 + 'supports' => false,
930 + 'show_in_nav_menus' => false,
931 + 'show_in_menu' => false,
932 + 'capabilities' => array_merge( self::record_capabilities(), array(
933 + 'create_posts' => 'do_not_allow'
934 + ) ),
935 + )
936 + )
937 + );
444 938 do_action( 'propertyhive_after_register_post_types' );
445 939 }
446 940
447 - public static function delete_property_media( $post_id ) {
941 + public static function trash_property_children( $post_id )
942 + {
943 + if ( get_post_type($post_id) == 'property' )
944 + {
945 + // get all children
946 + $args = array(
947 + 'post_type' => 'property',
948 + 'nopaging' => true,
949 + 'post_parent' => (int)$post_id,
950 + 'suppress_filters' => TRUE,
951 + 'post_status' => array('publish', 'pending', 'private', 'draft', 'auto-draft', 'future'),
952 + );
448 953
449 - $property = new PH_Property( (int)$post_id );
954 + $properties_query = new WP_Query($args);
450 955
451 - $media_ids = $property->get_gallery_attachment_ids();
452 - self::do_delete_media_attachments( $post_id, $media_ids );
956 + if ( $properties_query->have_posts() )
957 + {
958 + while( $properties_query->have_posts() )
959 + {
960 + $properties_query->the_post();
453 961
454 - $media_ids = $property->get_floorplan_attachment_ids();
455 - self::do_delete_media_attachments( $post_id, $media_ids );
962 + wp_trash_post( get_the_ID() );
963 + }
964 + }
965 + wp_reset_postdata();
966 + }
967 + }
456 968
457 - $media_ids = $property->get_brochure_attachment_ids();
458 - self::do_delete_media_attachments( $post_id, $media_ids );
969 + public static function trash_property_enquiries( $post_id )
970 + {
971 + if ( apply_filters( 'propertyhive_delete_enquiries_on_property_delete', false ) === true && get_post_type($post_id) == 'property' )
972 + {
973 + $args = array(
974 + 'post_type' => 'enquiry',
975 + 'nopaging' => true,
976 + 'post_status' => array('publish', 'pending', 'private', 'draft', 'auto-draft', 'future', 'trash'),
977 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Both legacy and current property relationship metadata must match when the explicit enquiry-trash option is enabled.
978 + 'meta_query' => array(
979 + 'relation' => 'OR',
980 + array(
981 + 'key' => '_property_id',
982 + 'value' => $post_id
983 + ),
984 + array(
985 + 'key' => 'property_id',
986 + 'value' => $post_id
987 + )
988 + )
989 + );
459 990
460 - $media_ids = $property->get_epc_attachment_ids();
461 - self::do_delete_media_attachments( $post_id, $media_ids );
991 + $enquiries_query = new WP_Query($args);
992 +
993 + if ( $enquiries_query->have_posts() )
994 + {
995 + while( $enquiries_query->have_posts() )
996 + {
997 + $enquiries_query->the_post();
998 +
999 + wp_trash_post( get_the_ID() );
1000 + }
1001 + }
1002 + wp_reset_postdata();
1003 + }
462 1004 }
463 1005
1006 + public static function delete_property_media( $post_id )
1007 + {
1008 + if ( get_post_type($post_id) == 'property' && apply_filters( 'propertyhive_remove_media_on_property_delete', TRUE ) === TRUE )
1009 + {
1010 + $property = new PH_Property( (int)$post_id );
1011 +
1012 + $media_ids = $property->get_gallery_attachment_ids();
1013 + self::do_delete_media_attachments( $post_id, $media_ids );
1014 +
1015 + $media_ids = $property->get_floorplan_attachment_ids();
1016 + self::do_delete_media_attachments( $post_id, $media_ids );
1017 +
1018 + $media_ids = $property->get_brochure_attachment_ids();
1019 + self::do_delete_media_attachments( $post_id, $media_ids );
1020 +
1021 + $media_ids = $property->get_epc_attachment_ids();
1022 + self::do_delete_media_attachments( $post_id, $media_ids );
1023 + }
1024 + }
1025 +
464 1026 private static function do_delete_media_attachments( $post_id, $media_ids = array() )
465 1027 {
466 1028 if ( is_array($media_ids) && !empty($media_ids) )
467 1029 {
@@ -470,10 +1032,13 @@
470 1032 // Make sure this media isn't used anywhere else
471 1033 $args = array(
472 1034 'post_type' => 'property',
473 1035 'fields' => 'ids',
1036 + 'suppress_filters' => TRUE,
474 1037 'posts_per_page' => 1,
1038 + // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Excludes only the property being deleted so its own media reference cannot prevent attachment cleanup.
475 1039 'post__not_in' => array( $post_id ),
1040 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Before deleting an attachment, check all four serialized property media relationships; the query stops at one matching property to preserve shared media.
476 1041 'meta_query' => array(
477 1042 'relation' => 'OR',
478 1043 array(
479 1044 'key' => '_photos',
@@ -523,8 +1088,445 @@
523 1088 if ( !$property_query->have_posts() )
524 1089 {
525 1090 // We're ok to delete
526 1091 wp_delete_attachment( $media_id, true );
1092 + }
1093 + }
1094 + }
1095 + }
1096 +
1097 + public static function delete_property_children( $post_id )
1098 + {
1099 + if ( get_post_type($post_id) == 'property' )
1100 + {
1101 + // get all children
1102 + $args = array(
1103 + 'post_type' => 'property',
1104 + 'nopaging' => true,
1105 + 'suppress_filters' => TRUE,
1106 + 'post_parent' => (int)$post_id,
1107 + 'post_status' => array('publish', 'pending', 'private', 'draft', 'auto-draft', 'future', 'trash'),
1108 + );
1109 +
1110 + $properties_query = new WP_Query($args);
1111 +
1112 + if ( $properties_query->have_posts() )
1113 + {
1114 + while( $properties_query->have_posts() )
1115 + {
1116 + $properties_query->the_post();
1117 +
1118 + wp_delete_post( get_the_ID(), true );
1119 + }
1120 + }
1121 + wp_reset_postdata();
1122 + }
1123 + }
1124 +
1125 + public static function delete_property_enquiries( $post_id )
1126 + {
1127 + if ( apply_filters( 'propertyhive_delete_enquiries_on_property_delete', false ) === true && get_post_type($post_id) == 'property' )
1128 + {
1129 + $args = array(
1130 + 'post_type' => 'enquiry',
1131 + 'nopaging' => true,
1132 + 'post_status' => array('publish', 'pending', 'private', 'draft', 'auto-draft', 'future', 'trash'),
1133 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Both legacy and current property relationship metadata must match when the explicit enquiry-delete option is enabled.
1134 + 'meta_query' => array(
1135 + 'relation' => 'OR',
1136 + array(
1137 + 'key' => '_property_id',
1138 + 'value' => $post_id
1139 + ),
1140 + array(
1141 + 'key' => 'property_id',
1142 + 'value' => $post_id
1143 + )
1144 + )
1145 + );
1146 +
1147 + $enquiries_query = new WP_Query($args);
1148 +
1149 + if ( $enquiries_query->have_posts() )
1150 + {
1151 + while( $enquiries_query->have_posts() )
1152 + {
1153 + $enquiries_query->the_post();
1154 +
1155 + wp_delete_post( get_the_ID(), true );
1156 + }
1157 + }
1158 + wp_reset_postdata();
1159 + }
1160 + }
1161 +
1162 + public static function delete_contact_user( $post_id )
1163 + {
1164 + if ( get_post_type($post_id) == 'contact' )
1165 + {
1166 + // If the contact being deleted has a user_id meta_key, delete the user with that ID if they're a contact or agent
1167 + $contact_user_id = get_post_meta ($post_id, '_user_id', TRUE );
1168 + if ( !empty($contact_user_id) )
1169 + {
1170 + $user_meta = get_userdata($contact_user_id);
1171 + $user_roles = $user_meta->roles;
1172 + $user_role = array_shift($user_roles);
1173 +
1174 + if ( in_array($user_role, array( 'property_hive_contact' )) )
1175 + {
1176 + // Include user admin functions to get access to wp_delete_user().
1177 + require_once ABSPATH . 'wp-admin/includes/user.php';
1178 + wp_delete_user( $contact_user_id );
1179 + }
1180 + }
1181 + }
1182 + }
1183 +
1184 + public static function delete_contact_user_link( $user_id )
1185 + {
1186 + global $post;
1187 +
1188 + $args = array(
1189 + 'post_type' => 'contact',
1190 + 'nopaging' => true,
1191 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Remove only contacts linked to the deleted user through their stored user ID.
1192 + 'meta_query' => array(
1193 + array(
1194 + 'key' => '_user_id',
1195 + 'value' => (int)$user_id,
1196 + )
1197 + )
1198 + );
1199 + $agent_query = new WP_Query( $args );
1200 + if ( $agent_query->have_posts() )
1201 + {
1202 + while ( $agent_query->have_posts() )
1203 + {
1204 + $agent_query->the_post();
1205 +
1206 + delete_post_meta( $post->ID, '_user_id' );
1207 +
1208 + wp_reset_postdata();
1209 + }
1210 + }
1211 + }
1212 +
1213 + public static function ensure_property_floor_area_to_set( $post_id, $post, $update )
1214 + {
1215 + // $post_id and $post are required
1216 + if ( empty( $post_id ) || empty( $post ) ) {
1217 + return;
1218 + }
1219 +
1220 + // Dont' save meta boxes for revisions or autosaves
1221 + if ( defined( 'DOING_AUTOSAVE' ) || is_int( wp_is_post_revision( $post ) ) || is_int( wp_is_post_autosave( $post ) ) ) {
1222 + return;
1223 + }
1224 +
1225 + if ( $post->post_type !== 'property' ) {
1226 + return;
1227 + }
1228 +
1229 + $property = new PH_Property( $post_id );
1230 +
1231 +
1232 + if ( $property->department == 'commercial' || ph_get_custom_department_based_on( $property->department ) == 'commercial' )
1233 + {
1234 + if ( ($property->floor_area_to_sqft == '' || $property->floor_area_to_sqft == '0') && ( $property->floor_area_from_sqft != '' && $property->floor_area_from_sqft != '0' ) )
1235 + {
1236 + update_post_meta( $post_id, '_floor_area_to', $property->floor_area_from );
1237 + update_post_meta( $post_id, '_floor_area_to_sqft', $property->floor_area_from_sqft);
1238 + }
1239 + }
1240 + }
1241 +
1242 + /**
1243 + * Check if we're saving, then trigger an action based on the post type
1244 + *
1245 + * @param int $post_id
1246 + * @param object $post
1247 + */
1248 + public static function create_concatenated_indexable_meta( $post_id, $post, $update )
1249 + {
1250 + // $post_id and $post are required
1251 + if ( empty( $post_id ) || empty( $post ) ) {
1252 + return;
1253 + }
1254 +
1255 + // Dont' save meta boxes for revisions or autosaves
1256 + if ( defined( 'DOING_AUTOSAVE' ) || is_int( wp_is_post_revision( $post ) ) || is_int( wp_is_post_autosave( $post ) ) ) {
1257 + return;
1258 + }
1259 +
1260 + if ( $post->post_type !== 'property' && $post->post_type !== 'contact' ) {
1261 + return;
1262 + }
1263 +
1264 + if ( $post->post_type == 'property' )
1265 + {
1266 + $property = new PH_Property( $post_id );
1267 +
1268 + // Set field of concatenated address
1269 + self::remove_all_duplicated_post_meta( $post_id, '_address_concatenated' );
1270 +
1271 + update_post_meta( $post_id, '_address_concatenated', $property->get_formatted_full_address() );
1272 +
1273 + // Set field of concatenated features
1274 + self::remove_all_duplicated_post_meta( $post_id, '_features_concatenated' );
1275 +
1276 + $features_concat_array = $property->get_features();
1277 +
1278 + $features_concat = implode('|', array_filter($features_concat_array));
1279 + update_post_meta($post_id, '_features_concatenated', $features_concat);
1280 +
1281 + // Set field of concatenated descriptions information
1282 + self::remove_all_duplicated_post_meta( $post_id, '_descriptions_concatenated' );
1283 +
1284 + $descs_concat = $property->get_formatted_description();
1285 + update_post_meta($post_id, '_descriptions_concatenated', $descs_concat);
1286 + }
1287 +
1288 + if ( $post->post_type == 'contact' )
1289 + {
1290 + $contact = new PH_Contact( $post_id );
1291 +
1292 + // Set field of concatenated address
1293 + self::remove_all_duplicated_post_meta( $post_id, '_address_concatenated' );
1294 +
1295 + update_post_meta( $post_id, '_address_concatenated', $contact->get_formatted_full_address() );
1296 + }
1297 + }
1298 +
1299 + private static function remove_all_duplicated_post_meta( $post_id, $meta_key )
1300 + {
1301 + $current_meta_array = get_post_meta( $post_id, $meta_key );
1302 + if ( is_array($current_meta_array) && count($current_meta_array) > 1 )
1303 + {
1304 + delete_post_meta( $post_id, $meta_key );
1305 + }
1306 + }
1307 +
1308 + public static function update_address_concatenated()
1309 + {
1310 + $args = array(
1311 + 'post_type' => 'property',
1312 + 'fields' => 'ids',
1313 + 'post_status' => array( 'publish', 'draft'),
1314 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- This maintenance backfill must select records missing the destination metadata; removing the predicate would overwrite existing values.
1315 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Owner search metadata must be refreshed on properties linked through the existing serialized owner IDs; retain the relationship match.
1316 + 'meta_query' => array(
1317 + array(
1318 + 'key' => '_address_concatenated',
1319 + 'compare' => 'NOT EXISTS'
1320 + )
1321 + ),
1322 + 'nopaging' => true,
1323 + 'orderby' => 'rand',
1324 + // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- Maintenance must cover every language; front-end language filters would omit records from this backfill.
1325 + 'suppress_filters' => true,
1326 + );
1327 + $property_query = new WP_Query($args);
1328 +
1329 + if ( $property_query->have_posts() )
1330 + {
1331 + while ( $property_query->have_posts() )
1332 + {
1333 + $property_query->the_post();
1334 +
1335 + $property = new PH_Property( get_the_ID() );
1336 +
1337 + // Set field of concatenated address
1338 + update_post_meta( get_the_ID(), '_address_concatenated', $property->get_formatted_full_address() );
1339 + }
1340 + }
1341 +
1342 + wp_reset_postdata();
1343 +
1344 + $args = array(
1345 + 'post_type' => 'contact',
1346 + 'fields' => 'ids',
1347 + 'post_status' => 'publish',
1348 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- This maintenance backfill must select records missing the destination metadata; removing the predicate would overwrite existing values.
1349 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Owner search metadata must be refreshed on properties linked through the existing serialized owner IDs; retain the relationship match.
1350 + 'meta_query' => array(
1351 + array(
1352 + 'key' => '_address_concatenated',
1353 + 'compare' => 'NOT EXISTS'
1354 + )
1355 + ),
1356 + 'nopaging' => true,
1357 + 'orderby' => 'rand',
1358 + // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- Maintenance must cover every language; front-end language filters would omit records from this backfill.
1359 + 'suppress_filters' => true,
1360 + );
1361 + $contact_query = new WP_Query($args);
1362 +
1363 + if ( $contact_query->have_posts() )
1364 + {
1365 + while ( $contact_query->have_posts() )
1366 + {
1367 + $contact_query->the_post();
1368 +
1369 + $contact = new PH_Contact( get_the_ID() );
1370 +
1371 + // Set field of concatenated address
1372 + update_post_meta( get_the_ID(), '_address_concatenated', $contact->get_formatted_full_address() );
1373 + }
1374 + }
1375 +
1376 + wp_reset_postdata();
1377 + }
1378 +
1379 + /**
1380 + * @param int $post_id
1381 + * @param object $post
1382 + */
1383 + public static function store_related_viewings( $post_id, $post, $update )
1384 + {
1385 + // $post_id and $post are required
1386 + if ( empty( $post_id ) || empty( $post ) ) {
1387 + return;
1388 + }
1389 +
1390 + // Dont' save meta boxes for revisions or autosaves
1391 + if ( defined( 'DOING_AUTOSAVE' ) || is_int( wp_is_post_revision( $post ) ) || is_int( wp_is_post_autosave( $post ) ) ) {
1392 + return;
1393 + }
1394 +
1395 + if ( $post->post_type !== 'property' && $post->post_type !== 'contact' && $post->post_type !== 'viewing' ) {
1396 + return;
1397 + }
1398 +
1399 + if ( get_option('propertyhive_module_disabled_viewings', '') == 'yes' ) {
1400 + return;
1401 + }
1402 +
1403 + $viewing_ids = array();
1404 +
1405 + switch ( $post->post_type )
1406 + {
1407 + case "property":
1408 + {
1409 + // get all viewings for this property
1410 + $meta_query = array(
1411 + array(
1412 + 'key' => '_property_id',
1413 + 'value' => $post_id,
1414 + )
1415 + );
1416 +
1417 + $args = array(
1418 + 'fields' => 'ids',
1419 + 'post_type' => 'viewing',
1420 + 'nopaging' => true,
1421 + 'post_status' => 'publish',
1422 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Recompute related-viewing metadata only for viewings linked to the changed property or applicant; retain existing relationship and status semantics.
1423 + 'meta_query' => $meta_query,
1424 + 'orderby' => 'none'
1425 + );
1426 +
1427 + $viewings_query = new WP_Query( $args );
1428 +
1429 + if ( $viewings_query->have_posts() )
1430 + {
1431 + while ( $viewings_query->have_posts() )
1432 + {
1433 + $viewings_query->the_post();
1434 +
1435 + $viewing_ids[] = get_the_ID();
1436 + }
1437 + }
1438 + wp_reset_postdata();
1439 +
1440 + break;
1441 + }
1442 + case "contact":
1443 + {
1444 + // get all viewings for this contact
1445 + $meta_query = array(
1446 + array(
1447 + 'key' => '_applicant_contact_id',
1448 + 'value' => $post_id,
1449 + )
1450 + );
1451 +
1452 + $args = array(
1453 + 'fields' => 'ids',
1454 + 'post_type' => 'viewing',
1455 + 'nopaging' => true,
1456 + 'post_status' => 'publish',
1457 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Recompute related-viewing metadata only for viewings linked to the changed property or applicant; retain existing relationship and status semantics.
1458 + 'meta_query' => $meta_query,
1459 + 'orderby' => 'none'
1460 + );
1461 +
1462 + $viewings_query = new WP_Query( $args );
1463 +
1464 + if ( $viewings_query->have_posts() )
1465 + {
1466 + while ( $viewings_query->have_posts() )
1467 + {
1468 + $viewings_query->the_post();
1469 +
1470 + $viewing_ids[] = get_the_ID();
1471 + }
1472 + }
1473 + wp_reset_postdata();
1474 +
1475 + break;
1476 + }
1477 + case "viewing":
1478 + {
1479 + $viewing_ids[] = $post_id;
1480 +
1481 + break;
1482 + }
1483 + }
1484 +
1485 + if ( !empty($viewing_ids) )
1486 + {
1487 + foreach ( $viewing_ids as $viewing_id )
1488 + {
1489 + $viewing = new PH_Viewing( $viewing_id );
1490 +
1491 + $related_viewings = $viewing->get_related_viewings();
1492 +
1493 + update_post_meta( $post_id, '_related_viewings', $related_viewings );
1494 +
1495 + if ( !empty($related_viewings['all']) )
1496 + {
1497 + foreach ( $related_viewings['all'] as $related_viewing_id )
1498 + {
1499 + $other_viewing = new PH_Viewing( $related_viewing_id );
1500 +
1501 + $other_related_viewings = $other_viewing->get_related_viewings();
1502 +
1503 + update_post_meta( $related_viewing_id, '_related_viewings', $other_related_viewings );
1504 + }
1505 + }
1506 + }
1507 + }
1508 + }
1509 +
1510 + public static function store_related_viewings_meta_change( $meta_id, $object_id, $meta_key, $meta_value )
1511 + {
1512 + if ( get_post_type($object_id) == 'viewing' && ( $meta_key == '_status' || $meta_key == '_start_date_time' ) )
1513 + {
1514 + $viewing = new PH_Viewing( $object_id );
1515 +
1516 + $related_viewings = $viewing->get_related_viewings();
1517 +
1518 + update_post_meta( $object_id, '_related_viewings', $related_viewings );
1519 +
1520 + if ( !empty($related_viewings['all']) )
1521 + {
1522 + foreach ( $related_viewings['all'] as $related_viewing_id )
1523 + {
1524 + $other_viewing = new PH_Viewing( $related_viewing_id );
1525 +
1526 + $other_related_viewings = $other_viewing->get_related_viewings();
1527 +
1528 + update_post_meta( $related_viewing_id, '_related_viewings', $other_related_viewings );
527 1529 }
528 1530 }
529 1531 }
530 1532 }