PluginProbe
Property Hive / trunk
Property Hive vtrunk
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 +1073 -134 1.4.53trunk 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,176 +320,245 @@
38 320
39 321 //$permalinks = get_option( 'propertyhive_permalinks' );
40 322
41 323 register_taxonomy( 'availability',
42 - 'property',
43 - array(
44 - 'label' => __( 'Availabilities', 'propertyhive' ),
45 - 'hierarchical' => true,
46 - 'show_ui' => false,
47 - 'show_in_nav_menus' => false,
48 - 'query_var' => is_admin(),
49 - 'rewrite' => false,
50 - '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 + )
51 337 )
52 338 );
53 339
54 340 register_taxonomy( 'property_type',
55 - array( 'property', 'appraisal' ),
56 - array(
57 - 'label' => __( 'Property Types', 'propertyhive' ),
58 - 'hierarchical' => true,
59 - 'show_ui' => false,
60 - 'show_in_nav_menus' => false,
61 - 'query_var' => is_admin(),
62 - 'rewrite' => false,
63 - 'public' => true
64 - )
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 + )
65 355 );
66 356
67 357 register_taxonomy( 'commercial_property_type',
68 - array( 'property', 'appraisal' ),
69 - array(
70 - 'label' => __( 'Commercial Property Types', 'propertyhive' ),
71 - 'hierarchical' => true,
72 - 'show_ui' => false,
73 - 'show_in_nav_menus' => false,
74 - 'query_var' => is_admin(),
75 - 'rewrite' => false,
76 - '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 + )
77 371 )
78 372 );
79 373
80 374 register_taxonomy( 'location',
81 - 'property',
82 - array(
83 - 'label' => __( 'Locations', 'propertyhive' ),
84 - 'hierarchical' => true,
85 - 'show_ui' => false,
86 - 'show_in_nav_menus' => false,
87 - 'query_var' => is_admin(),
88 - 'rewrite' => false,
89 - '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 + )
90 388 )
91 389 );
92 390
93 391 register_taxonomy( 'parking',
94 - array( 'property', 'appraisal' ),
95 - array(
96 - 'label' => __( 'Parking', 'propertyhive' ),
97 - 'hierarchical' => true,
98 - 'show_ui' => false,
99 - 'show_in_nav_menus' => false,
100 - 'query_var' => is_admin(),
101 - 'rewrite' => false,
102 - '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 + )
103 405 )
104 406 );
105 407
106 408 register_taxonomy( 'outside_space',
107 - array( 'property', 'appraisal' ),
108 - array(
109 - 'label' => __( 'Outside Spaces', 'propertyhive' ),
110 - 'hierarchical' => true,
111 - 'show_ui' => false,
112 - 'show_in_nav_menus' => false,
113 - 'query_var' => is_admin(),
114 - 'rewrite' => false,
115 - '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 + )
116 422 )
117 423 );
118 424
119 425 register_taxonomy( 'price_qualifier',
120 - 'property',
121 - array(
122 - 'label' => __( 'Price Qualifiers', 'propertyhive' ),
123 - 'hierarchical' => true,
124 - 'show_ui' => false,
125 - 'show_in_nav_menus' => false,
126 - 'query_var' => is_admin(),
127 - 'rewrite' => false,
128 - '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 + )
129 439 )
130 440 );
131 441
132 442 register_taxonomy( 'tenure',
133 - 'property',
134 - array(
135 - 'label' => __( 'Tenures', 'propertyhive' ),
136 - 'hierarchical' => true,
137 - 'show_ui' => false,
138 - 'show_in_nav_menus' => false,
139 - 'query_var' => is_admin(),
140 - 'rewrite' => false,
141 - '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 + )
142 456 )
143 457 );
144 458
145 459 register_taxonomy( 'commercial_tenure',
146 - 'property',
147 - array(
148 - 'label' => __( 'Commercial Tenures', 'propertyhive' ),
149 - 'hierarchical' => true,
150 - 'show_ui' => false,
151 - 'show_in_nav_menus' => false,
152 - 'query_var' => is_admin(),
153 - 'rewrite' => false,
154 - '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 + )
155 473 )
156 474 );
157 475
158 476 register_taxonomy( 'sale_by',
159 - 'property',
160 - array(
161 - 'label' => __( 'Sale By', 'propertyhive' ),
162 - 'hierarchical' => true,
163 - 'show_ui' => false,
164 - 'show_in_nav_menus' => false,
165 - 'query_var' => is_admin(),
166 - 'rewrite' => false,
167 - '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 + )
168 490 )
169 491 );
170 492
171 493 register_taxonomy( 'furnished',
172 - array( 'property', 'appraisal' ),
173 - array(
174 - 'label' => __( 'Furnished', 'propertyhive' ),
175 - 'hierarchical' => true,
176 - 'show_ui' => false,
177 - 'show_in_nav_menus' => false,
178 - 'query_var' => is_admin(),
179 - 'rewrite' => false,
180 - '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 + )
181 507 )
182 508 );
183 509
184 510 register_taxonomy( 'marketing_flag',
185 - 'property',
186 - array(
187 - 'label' => __( 'Marketing Flags', 'propertyhive' ),
188 - 'hierarchical' => true,
189 - 'show_ui' => false,
190 - 'show_in_nav_menus' => false,
191 - 'query_var' => is_admin(),
192 - 'rewrite' => false,
193 - '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 + )
194 524 )
195 525 );
196 526
197 527 register_taxonomy( 'property_feature',
198 - 'property',
199 - array(
200 - 'label' => __( 'Property Features', 'propertyhive' ),
201 - 'hierarchical' => true,
202 - 'show_ui' => false,
203 - 'show_in_nav_menus' => false,
204 - 'query_var' => is_admin(),
205 - 'rewrite' => false,
206 - '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 + )
207 541 )
208 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 + );
209 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.
210 561 do_action( 'do_action_after_register_taxonomies' );
211 562 }
212 563
213 564 /**
@@ -245,8 +596,9 @@
245 596 'description' => __( 'This is where you can add new properties to your site.', 'propertyhive' ),
246 597 'public' => true,
247 598 'show_ui' => true,
248 599 'capability_type' => 'post',
600 + 'capabilities' => self::record_capabilities(),
249 601 'map_meta_cap' => true,
250 602 'publicly_queryable' => true,
251 603 'exclude_from_search' => false,
252 604 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
@@ -253,9 +605,9 @@
253 605 //'rewrite' => $product_permalink ? array( 'slug' => untrailingslashit( $product_permalink ), 'with_front' => false, 'feeds' => true ) : false,
254 606 'query_var' => true,
255 607 'supports' => array( 'title', 'excerpt' ),
256 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',
257 - 'show_in_nav_menus' => false,
609 + 'show_in_nav_menus' => true,
258 610 'show_in_menu' => false,
259 611 'show_in_admin_bar' => true,
260 612 'show_in_rest' => true,
261 613 )
@@ -284,8 +636,9 @@
284 636 'description' => __( 'This is where you can add new contacts to your site.', 'propertyhive' ),
285 637 'public' => false,
286 638 'show_ui' => true,
287 639 'capability_type' => 'post',
640 + 'capabilities' => self::record_capabilities(),
288 641 'map_meta_cap' => true,
289 642 'publicly_queryable' => false,
290 643 'exclude_from_search' => true,
291 644 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
@@ -318,16 +671,18 @@
318 671 ),
319 672 'public' => true,
320 673 'show_ui' => false,
321 674 'capability_type' => 'post',
675 + 'capabilities' => self::record_capabilities(),
322 676 'map_meta_cap' => true,
323 - 'publicly_queryable' => true,
677 + 'publicly_queryable' => false,
324 678 'exclude_from_search' => true,
325 679 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
326 680 'query_var' => true,
327 681 'supports' => array( 'title' ),
328 682 'show_in_nav_menus' => false,
329 - 'show_in_menu' => false
683 + 'show_in_menu' => false,
684 + 'show_in_rest' => true,
330 685 )
331 686 )
332 687 );
333 688
@@ -352,8 +707,9 @@
352 707 ),
353 708 'public' => false,
354 709 'show_ui' => true,
355 710 'capability_type' => 'post',
711 + 'capabilities' => self::record_capabilities(),
356 712 'map_meta_cap' => true,
357 713 'publicly_queryable' => false,
358 714 'exclude_from_search' => true,
359 715 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
@@ -359,9 +715,10 @@
359 715 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
360 716 'query_var' => true,
361 717 'supports' => array( 'title' ),
362 718 'show_in_nav_menus' => false,
363 - 'show_in_menu' => false
719 + 'show_in_menu' => false,
720 + 'show_in_rest' => true,
364 721 )
365 722 )
366 723 );
367 724
@@ -386,8 +743,9 @@
386 743 ),
387 744 'public' => false,
388 745 'show_ui' => true,
389 746 'capability_type' => 'post',
747 + 'capabilities' => self::record_capabilities(),
390 748 'map_meta_cap' => true,
391 749 'publicly_queryable' => false,
392 750 'exclude_from_search' => true,
393 751 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
@@ -420,8 +778,9 @@
420 778 ),
421 779 'public' => false,
422 780 'show_ui' => true,
423 781 'capability_type' => 'post',
782 + 'capabilities' => self::record_capabilities(),
424 783 'map_meta_cap' => true,
425 784 'publicly_queryable' => false,
426 785 'exclude_from_search' => true,
427 786 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
@@ -454,8 +813,9 @@
454 813 ),
455 814 'public' => false,
456 815 'show_ui' => true,
457 816 'capability_type' => 'post',
817 + 'capabilities' => self::record_capabilities(),
458 818 'map_meta_cap' => true,
459 819 'publicly_queryable' => false,
460 820 'exclude_from_search' => true,
461 821 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
@@ -488,11 +848,46 @@
488 848 ),
489 849 'public' => false,
490 850 'show_ui' => true,
491 851 'capability_type' => 'post',
492 - 'capabilities' => array(
852 + 'capabilities' => array_merge( self::record_capabilities(), array(
493 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' )
494 885 ),
886 + 'public' => false,
887 + 'show_ui' => true,
888 + 'capability_type' => 'post',
889 + 'capabilities' => self::record_capabilities(),
495 890 'map_meta_cap' => true,
496 891 'publicly_queryable' => false,
497 892 'exclude_from_search' => true,
498 893 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records!
@@ -502,29 +897,133 @@
502 897 'show_in_menu' => false
503 898 )
504 899 )
505 900 );
506 -
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 + );
507 938 do_action( 'propertyhive_after_register_post_types' );
508 939 }
509 940
510 - 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 + );
511 953
512 - $property = new PH_Property( (int)$post_id );
954 + $properties_query = new WP_Query($args);
513 955
514 - $media_ids = $property->get_gallery_attachment_ids();
515 - 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();
516 961
517 - $media_ids = $property->get_floorplan_attachment_ids();
518 - self::do_delete_media_attachments( $post_id, $media_ids );
962 + wp_trash_post( get_the_ID() );
963 + }
964 + }
965 + wp_reset_postdata();
966 + }
967 + }
519 968
520 - $media_ids = $property->get_brochure_attachment_ids();
521 - 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 + );
522 990
523 - $media_ids = $property->get_epc_attachment_ids();
524 - 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 + }
525 1004 }
526 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 +
527 1026 private static function do_delete_media_attachments( $post_id, $media_ids = array() )
528 1027 {
529 1028 if ( is_array($media_ids) && !empty($media_ids) )
530 1029 {
@@ -533,10 +1032,13 @@
533 1032 // Make sure this media isn't used anywhere else
534 1033 $args = array(
535 1034 'post_type' => 'property',
536 1035 'fields' => 'ids',
1036 + 'suppress_filters' => TRUE,
537 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.
538 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.
539 1041 'meta_query' => array(
540 1042 'relation' => 'OR',
541 1043 array(
542 1044 'key' => '_photos',
@@ -586,8 +1088,445 @@
586 1088 if ( !$property_query->have_posts() )
587 1089 {
588 1090 // We're ok to delete
589 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 );
590 1529 }
591 1530 }
592 1531 }
593 1532 }