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-rest-api.php +1154 -15 1.4.592.3.0 View file →
@@ -12,8 +12,9 @@
12 12 * @package PropertyHive/Classes/
13 13 * @category Class
14 14 * @author PropertyHive
15 15 */
16 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Legacy public global class PH_Rest_Api; preserving the existing PH_* class name is required for plugin and extension compatibility.
16 17 class PH_Rest_Api {
17 18
18 19 /** @var PH_Rest_Api The single instance of the class */
19 20 protected static $_instance = null;
@@ -18,8 +19,37 @@
18 19 /** @var PH_Rest_Api The single instance of the class */
19 20 protected static $_instance = null;
20 21
21 22 /**
23 + * Download REST media within the site's upload size limit.
24 + *
25 + * @param string $url Remote media URL.
26 + * @return string|WP_Error Temporary filename or an error.
27 + */
28 + private static function download_media( $url ) {
29 + $temporary_file = wp_tempnam( $url );
30 + if ( ! $temporary_file ) {
31 + return new WP_Error( 'propertyhive_media_temp_file', __( 'Unable to create a temporary file.', 'propertyhive' ) );
32 + }
33 + $maximum_size = wp_max_upload_size();
34 + $response = wp_safe_remote_get( $url, array(
35 + 'timeout' => 30,
36 + 'stream' => true,
37 + 'filename' => $temporary_file,
38 + 'limit_response_size' => $maximum_size + 1,
39 + ) );
40 + if ( is_wp_error( $response ) ) {
41 + wp_delete_file( $temporary_file );
42 + return $response;
43 + }
44 + if ( 200 !== wp_remote_retrieve_response_code( $response ) || filesize( $temporary_file ) > $maximum_size ) {
45 + wp_delete_file( $temporary_file );
46 + return new WP_Error( 'propertyhive_media_download', __( 'The media could not be downloaded or exceeds the upload size limit.', 'propertyhive' ) );
47 + }
48 + return $temporary_file;
49 + }
50 +
51 + /**
22 52 * Main PH_Rest_Api Instance.
23 53 *
24 54 * Ensures only one instance of PH_Rest_Api is loaded or can be loaded.
25 55 *
@@ -39,9 +69,9 @@
39 69 *
40 70 * @since 1.0.0
41 71 */
42 72 public function __clone() {
43 - _doing_it_wrong( __FUNCTION__, __( 'Cheatin’ huh?', 'propertyhive' ), '1.0.0' );
73 + _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin’ huh?', 'propertyhive' ), '1.0.0' );
44 74 }
45 75
46 76 /**
47 77 * Unserializing instances of this class is forbidden.
@@ -48,9 +78,9 @@
48 78 *
49 79 * @since 1.0.0
50 80 */
51 81 public function __wakeup() {
52 - _doing_it_wrong( __FUNCTION__, __( 'Cheatin’ huh?', 'propertyhive' ), '1.0.0' );
82 + _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin’ huh?', 'propertyhive' ), '1.0.0' );
53 83 }
54 84
55 85 /**
56 86 * Constructor for the licenses class
@@ -56,12 +86,468 @@
56 86 * Constructor for the licenses class
57 87 *
58 88 */
59 89 public function __construct() {
90 + // Property
60 91 add_filter( 'rest_property_query', array( $this, 'modify_rest_property_query' ), 10, 2 );
92 + add_filter( 'rest_prepare_property', array( $this, 'rest_prepare_property' ), 10, 3 );
61 93 add_action( 'rest_api_init', array( $this, 'register_rest_api_property_fields' ), 99 );
94 + add_filter( 'rest_property_collection_params', array( $this, 'modify_rest_order_by' ), 10, 1 );
95 + add_action( "rest_after_insert_property", array( $this, 'ensure_key_property_fields_set' ), 10, 3 );
96 +
97 + // Enquiry - Create
98 + add_action( 'rest_api_init', array( $this, 'register_enquiry_rest_route' ), 11 );
99 + add_action( "rest_after_insert_enquiry", array( $this, 'ensure_key_enquiry_fields_set' ), 10, 3 );
100 +
101 + // Enquiry - Read
102 + add_filter( 'pre_get_posts', array( $this, 'restrict_enquiry_rest_listing' ));
103 + add_filter( 'rest_pre_dispatch', array( $this, 'block_enquiry_rest_listing' ), 10, 3);
104 + add_action( 'rest_api_init', array( $this, 'register_rest_api_enquiry_fields' ), 99 );
105 +
106 + // Office
107 + add_action( 'rest_api_init', array( $this, 'register_rest_api_office_fields' ), 99 );
62 108 }
63 109
110 + // Restrict listing of enquiries via REST API
111 + public function restrict_enquiry_rest_listing(WP_Query $query)
112 + {
113 + if ( !is_admin() && defined('REST_REQUEST') && REST_REQUEST )
114 + {
115 + $post_type = $query->get('post_type');
116 +
117 + // Check if it's the 'enquiry' CPT
118 + if ( $post_type === 'enquiry' )
119 + {
120 + if ( !current_user_can('manage_propertyhive') )
121 + {
122 + // Force an empty result
123 + $query->set('post__in', [0]);
124 + }
125 + }
126 + }
127 + }
128 +
129 + public function block_enquiry_rest_listing($response, $server, $request)
130 + {
131 + if ( preg_match( '#^/wp/v2/enquiry(?:/|$)#', $request->get_route() ) )
132 + {
133 + $current_user = wp_get_current_user();
134 +
135 + if ( !current_user_can('manage_propertyhive') )
136 + {
137 + return new WP_Error(
138 + 'rest_forbidden',
139 + __( 'You are not allowed to access enquiries.', 'propertyhive' ),
140 + ['status' => 403]
141 + );
142 + }
143 +
144 + if ( get_option( 'propertyhive_module_disabled_enquiries', '' ) == 'yes' )
145 + {
146 + return new WP_Error(
147 + 'rest_forbidden',
148 + __('The enquiries module is currently disabled.', 'propertyhive'),
149 + ['status' => 403]
150 + );
151 + }
152 +
153 + if ( get_option( 'propertyhive_store_property_enquiries', 'yes' ) != 'yes' )
154 + {
155 + return new WP_Error(
156 + 'rest_forbidden',
157 + __('Storing of property enquiries has been disabled in the GDPR settings.', 'propertyhive'),
158 + ['status' => 403]
159 + );
160 + }
161 +
162 + }
163 + return $response;
164 + }
165 +
166 + public function register_rest_api_enquiry_fields()
167 + {
168 + $field_array = array(
169 + 'office',
170 + 'negotiator',
171 + 'status',
172 + 'source',
173 + 'properties',
174 + 'details',
175 + );
176 +
177 + $field_array = apply_filters( 'propertyhive_rest_api_enquiry_fields', $field_array );
178 +
179 + foreach ( $field_array as $field )
180 + {
181 + register_rest_field( 'enquiry',
182 + $field,
183 + array(
184 + 'get_callback' => function( $object, $field_name, $request )
185 + {
186 + $enquiry = new PH_Enquiry($object['id']);
187 +
188 + $return = '';
189 +
190 + switch ($field_name)
191 + {
192 + case "office":
193 + {
194 + $return = array(
195 + 'name' => get_the_title($enquiry->_office_id),
196 + );
197 + break;
198 + }
199 + case "negotiator":
200 + {
201 + $user = get_userdata( $enquiry->_negotiator_id );
202 +
203 + $return = array(
204 + 'name' => ( $user !== false ) ? $user->display_name : '',
205 + );
206 + break;
207 + }
208 + case "properties":
209 + {
210 + $return = array();
211 +
212 + $property_ids = $enquiry->get_properties();
213 +
214 + if ( !empty($property_ids) )
215 + {
216 + foreach ( $property_ids as $property_id )
217 + {
218 + $property = new PH_Property((int)$property_id);
219 +
220 + $return[] = array(
221 + 'id' => (int)$property_id,
222 + 'address' => $property->get_formatted_full_address()
223 + );
224 + }
225 + }
226 + break;
227 + }
228 + case "details":
229 + {
230 + $ignore_keys = array(
231 + '_status',
232 + '_source',
233 + '_negotiator_id',
234 + '_office_id',
235 + '_action',
236 + '_contact_id',
237 + '_property_id',
238 + 'property_id',
239 + );
240 +
241 + $enquiry_meta = get_metadata( 'post', $object['id'] );
242 +
243 + $return = array();
244 +
245 + foreach ($enquiry_meta as $key => $value)
246 + {
247 + if ( !in_array( $key, $ignore_keys ) && substr( $key, 0, 1 ) != '_' && strpos($key, 'captcha') === FALSE )
248 + {
249 + $return[trim($key, "_")] = $value;
250 + }
251 + }
252 + break;
253 + }
254 + default:
255 + {
256 + $return = $enquiry->{$field_name};
257 + }
258 + }
259 +
260 + $return = apply_filters( 'propertyhive_rest_api_enquiry_field_callback', $return, $field_name, $enquiry );
261 + return $return;
262 + },
263 + 'schema' => null,
264 + )
265 + );
266 + }
267 + }
268 +
269 + public function register_enquiry_rest_route()
270 + {
271 + register_rest_route('wp/v2', '/enquiry', array(
272 + array(
273 + 'methods' => WP_REST_Server::CREATABLE,
274 + 'callback' => array( $this, 'handle_enquiry_post' ),
275 + 'permission_callback' => array( $this, 'enquiry_permission_check' )
276 + ),
277 + ));
278 + }
279 +
280 + public function enquiry_permission_check() {
281 + if ( current_user_can( 'manage_propertyhive' ) ) {
282 + return true;
283 + }
284 + return new WP_Error( 'rest_forbidden', __( 'You do not have permission to create enquiries.', 'propertyhive' ), array( 'status' => 403 ) );
285 + }
286 +
287 + public function handle_enquiry_post(WP_REST_Request $request)
288 + {
289 + // Handle the creation of the enquiry post
290 + $params = $request->get_params();
291 +
292 + // validate office ID
293 + if ( isset($params['office_id']) && !empty($params['office_id']) )
294 + {
295 + if ( get_post_type((int)$params['office_id']) != 'office' )
296 + {
297 + return new WP_Error('rest_enquiry_error', __( 'Invalid office ID passed', 'propertyhive' ) . ' ('. (int)$params['office_id'] . ')', array('status' => 400));
298 + }
299 + }
300 +
301 + // validate negotiator ID
302 + if ( isset($params['negotiator_id']) && !empty($params['negotiator_id']) )
303 + {
304 + if ( get_userdata((int)$params['negotiator_id']) === false )
305 + {
306 + return new WP_Error('rest_enquiry_error', __( 'Invalid negotiator ID passed', 'propertyhive' ) . ' ('. (int)$params['negotiator_id'] . ')', array('status' => 400));
307 + }
308 + }
309 +
310 + // validate source
311 + $valid_sources = array(
312 + 'office' => __( 'Office', 'propertyhive' ),
313 + 'website' => __( 'Website', 'propertyhive' )
314 + );
315 +
316 + $valid_sources = apply_filters( 'propertyhive_enquiry_sources', $valid_sources );
317 +
318 + if ( isset($params['source']) && !empty($params['source']) )
319 + {
320 + if ( !array_key_exists($params['source'], $valid_sources) )
321 + {
322 + return new WP_Error('rest_enquiry_error', __( 'Invalid source passed. Should be one of', 'propertyhive' ) . ': ' . implode(", ", array_keys($valid_sources)), array('status' => 400));
323 + }
324 + }
325 +
326 + if ( isset($params['property_id']) && !empty($params['property_id']) )
327 + {
328 + if ( !is_array($params['property_id']) )
329 + {
330 + $property_ids = array($params['property_id']);
331 + }
332 + else
333 + {
334 + $property_ids = $params['property_id'];
335 + }
336 +
337 + foreach ( $property_ids as $property_id )
338 + {
339 + if ( get_post_type((int)$property_id) != 'property' )
340 + {
341 + return new WP_Error('rest_enquiry_error', __( 'Invalid property ID passed', 'propertyhive' ) . ' ('. (int)$property_id . ')', array('status' => 400));
342 + }
343 + }
344 + }
345 +
346 + $title = ( isset($params['title']) && !empty($params['title']) ? $params['title'] : '' );
347 + if (empty($title))
348 + {
349 + $title = 'Enquiry';
350 + if ( isset($params['name']) && !empty($params['name']) )
351 + {
352 + $title .= ' from ' . $params['name'];
353 + }
354 + if ( isset($params['property_id']) && !empty($params['property_id']) )
355 + {
356 + if ( is_array($params['property_id']) )
357 + {
358 + $title .= ' about ' . count($params['property_id']) . ' properties';
359 + }
360 + else
361 + {
362 + $title .= ' about ' . get_the_title($params['property_id']);
363 + }
364 + }
365 + }
366 + $data = array(
367 + 'post_type' => 'enquiry',
368 + 'post_status' => 'publish',
369 + 'post_title' => sanitize_text_field($title),
370 + 'post_content' => '',
371 + 'comment_status' => 'closed',
372 + 'ping_status' => 'closed',
373 + );
374 +
375 + $post_id = wp_insert_post($data, true);
376 +
377 + if ( is_wp_error($post_id) )
378 + {
379 + return new WP_Error('rest_enquiry_error', $post_id->get_error_message(), array('status' => 500));
380 + }
381 +
382 + // Status
383 + if ( isset($params['status']) && !empty($params['status']) && in_array($params['status'], array('open', 'closed')) )
384 + {
385 + update_post_meta( $post_id, '_status', sanitize_text_field($params['status']) );
386 + }
387 + else
388 + {
389 + update_post_meta( $post_id, '_status', 'open' );
390 + }
391 +
392 + if ( isset($params['office_id']) && !empty($params['office_id']) && is_numeric($params['office_id']) )
393 + {
394 + // Should be a valid office ID as this has already been validated by this point
395 + update_post_meta( $post_id, '_office_id', (int)$params['office_id'] );
396 + }
397 + else
398 + {
399 + $primary_office_id = '';
400 + $args = array(
401 + 'post_type' => 'office',
402 + 'nopaging' => true
403 + );
404 + $office_query = new WP_Query($args);
405 +
406 + if ($office_query->have_posts())
407 + {
408 + while ($office_query->have_posts())
409 + {
410 + $office_query->the_post();
411 +
412 + if (get_post_meta(get_the_ID(), 'primary', TRUE) == '1')
413 + {
414 + $primary_office_id = get_the_ID();
415 + }
416 + }
417 + }
418 + $office_query->reset_postdata();
419 +
420 + update_post_meta( $post_id, '_office_id', $primary_office_id );
421 + }
422 +
423 + if ( isset($params['negotiator_id']) && !empty($params['negotiator_id']) && is_numeric($params['negotiator_id']) )
424 + {
425 + // Should be a valid neg ID as this has already been validated by this point
426 + update_post_meta( $post_id, '_negotiator_id', (int)$params['negotiator_id'] );
427 + }
428 +
429 + // Source
430 + if ( isset($params['source']) && !empty($params['source']) && array_key_exists($params['source'], $valid_sources) )
431 + {
432 + update_post_meta( $post_id, '_source', sanitize_text_field($params['source']) );
433 + }
434 + else
435 + {
436 + update_post_meta( $post_id, '_source', 'website' );
437 + }
438 +
439 + if ( isset($params['name']) && !empty($params['name']) )
440 + {
441 + update_post_meta( $post_id, 'name', sanitize_text_field($params['name']) );
442 + }
443 +
444 + if ( isset($params['email_address']) && !empty($params['email_address']) )
445 + {
446 + update_post_meta( $post_id, 'email_address', sanitize_email($params['email_address']) );
447 + }
448 +
449 + if ( isset($params['telephone_number']) && !empty($params['telephone_number']) )
450 + {
451 + update_post_meta( $post_id, 'telephone_number', sanitize_text_field($params['telephone_number']) );
452 + }
453 +
454 + if ( isset($params['message']) && !empty($params['message']) )
455 + {
456 + update_post_meta( $post_id, 'message', sanitize_textarea_field($params['message']) );
457 + }
458 +
459 + if ( isset($params['property_id']) && !empty($params['property_id']) && ( is_numeric($params['property_id']) || is_array($params['property_id']) ) )
460 + {
461 + // Should be a valid property ID as this has already been validated by this point
462 + $property_ids = $params['property_id'];
463 + if ( !is_array($property_ids) ) { $property_ids = array($property_ids); }
464 +
465 + foreach ( $property_ids as $property_id )
466 + {
467 + add_post_meta( $post_id, 'property_id', (int)$property_id );
468 + }
469 + }
470 +
471 + return new WP_REST_Response(array('ID' => $post_id), 201);
472 + }
473 +
474 + public function ensure_key_property_fields_set( $post, $request, $creating )
475 + {
476 + // Country / price actual
477 + $country = get_post_meta( $post->ID, '_address_country', true );
478 + if ( $country == '' )
479 + {
480 + $default_country = get_option( 'propertyhive_default_country', 'GB' );
481 + update_post_meta( $post->ID, '_address_country', $default_country );
482 + }
483 +
484 + $ph_countries = new PH_Countries();
485 + $ph_countries->update_property_price_actual( $post->ID );
486 +
487 + // Department
488 + $department = get_post_meta( $post->ID, '_department', true );
489 + if ( $department == '' )
490 + {
491 + $primary_department = get_option( 'propertyhive_primary_department', 'residential-sales' );
492 + update_post_meta( $post->ID, '_department', $primary_department );
493 + }
494 +
495 + // On Market
496 + $on_market = get_post_meta( $post->ID, '_on_market', true );
497 + if ( $on_market != 'yes' )
498 + {
499 + update_post_meta( $post->ID, '_on_market', '' );
500 + }
501 +
502 + // Featured
503 + $featured = get_post_meta( $post->ID, '_featured', true );
504 + if ( $featured != 'yes' )
505 + {
506 + update_post_meta( $post->ID, '_featured', '' );
507 + }
508 +
509 + // Office
510 + $office_id = get_post_meta( $post->ID, '_office_id', true );
511 + if ( $office_id == '' )
512 + {
513 + $primary_office_id = '';
514 + $args = array(
515 + 'post_type' => 'office',
516 + 'nopaging' => true
517 + );
518 + $office_query = new WP_Query($args);
519 +
520 + if ($office_query->have_posts())
521 + {
522 + while ($office_query->have_posts())
523 + {
524 + $office_query->the_post();
525 +
526 + if (get_post_meta(get_the_ID(), 'primary', TRUE) == '1')
527 + {
528 + $primary_office_id = get_the_ID();
529 + }
530 + }
531 + }
532 + $office_query->reset_postdata();
533 +
534 + update_post_meta( $post->ID, '_office_id', $primary_office_id );
535 + }
536 + }
537 +
538 + public function modify_rest_order_by($params)
539 + {
540 + $fields = ["price-asc", "price-desc"];
541 +
542 + foreach ( $fields as $key => $value )
543 + {
544 + $params['orderby']['enum'][] = $value;
545 + }
546 +
547 + return $params;
548 + }
549 +
64 550 public function modify_rest_property_query($args, $request)
65 551 {
66 552 /*if ( !isset( $args['meta_query'] ) )
67 553 {
@@ -70,16 +556,25 @@
70 556
71 557 $PH_Query = new PH_Query();
72 558
73 559 // Meta query
560 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- REST property collection arguments are built from PH_Query filters and are consumed by WordPress REST collection pagination. Meta/tax/date filters and fixed ordering are feature behavior; the REST controller bounds per_page and the source preserves request filters.
74 561 $args['meta_query'] = $PH_Query->get_meta_query();
75 562
76 563 // Tax query
564 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- REST property collection arguments are built from PH_Query filters and are consumed by WordPress REST collection pagination. Meta/tax/date filters and fixed ordering are feature behavior; the REST controller bounds per_page and the source preserves request filters.
77 565 $args['tax_query'] = $PH_Query->get_tax_query();
78 566
79 567 // Date query
80 568 $args['date_query'] = $PH_Query->get_date_query();
81 569
570 + $ordering = $PH_Query->get_search_results_ordering_args();
571 + $args['orderby'] = $ordering['orderby'] . ' post_title';
572 + $args['order'] = $ordering['order'];
573 + if ( isset( $ordering['meta_key'] ) )
574 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- REST property collection arguments are built from PH_Query filters and are consumed by WordPress REST collection pagination. Meta/tax/date filters and fixed ordering are feature behavior; the REST controller bounds per_page and the source preserves request filters.
575 + $args['meta_key'] = $ordering['meta_key'];
576 +
82 577 $args = apply_filters( 'propertyhive_rest_api_query_args', $args );
83 578
84 579 return $args;
85 580 }
@@ -87,16 +582,32 @@
87 582 public function register_rest_api_property_fields()
88 583 {
89 584 $field_array = array(
90 585 'department',
586 + 'reference_number',
587 + 'address_street',
588 + 'address_two',
589 + 'address_three',
590 + 'address_four',
591 + 'address_postcode',
592 + 'address_country',
91 593 'latitude',
92 594 'longitude',
595 + 'price_actual',
93 596 'price',
597 + 'price_from',
598 + 'price_to',
599 + 'price_units',
600 + 'rent_from',
601 + 'rent_to',
602 + 'rent_units',
94 603 'price_formatted',
604 + 'rent_frequency',
95 605 'currency',
96 606 'price_qualifier',
97 607 'sale_by',
98 608 'tenure',
609 + 'council_tax_band',
99 610 'deposit',
100 611 'furnished',
101 612 'available_date',
102 613 'bedrooms',
@@ -104,16 +615,36 @@
104 615 'reception_rooms',
105 616 'property_type',
106 617 'parking',
107 618 'outside_space',
619 + 'for_sale',
620 + 'to_rent',
621 + 'floor_area_from',
622 + 'floor_area_to',
623 + 'floor_area_units',
624 + 'site_area_from',
625 + 'site_area_to',
626 + 'site_area_units',
108 627 'on_market',
109 628 'featured',
629 + 'location',
110 630 'availability',
111 - 'marketing_flags',
631 + 'marketing_flag',
112 632 'features',
113 633 'description',
114 634 'office',
635 + 'negotiator',
636 + 'images',
637 + 'floorplans',
638 + 'brochures',
639 + 'epcs',
115 640 'virtual_tours',
641 + 'views_total',
642 + 'views_today',
643 + 'views_yesterday',
644 + 'views_last_7_days',
645 + 'views_last_14_days',
646 + 'views_last_30_days',
116 647 );
117 648
118 649 $field_array = apply_filters( 'propertyhive_rest_api_property_fields', $field_array );
119 650
@@ -125,25 +656,49 @@
125 656 'get_callback' => function( $object, $field_name, $request )
126 657 {
127 658 $property = new PH_Property($object[ 'id' ]);
128 659
660 + $return = '';
661 +
129 662 switch ($field_name)
130 663 {
131 664 case "price":
132 - {
665 + {
133 666 if ( $property->_poa != 'yes' )
134 667 {
135 - if ( $property->_department == 'residential-lettings' ) { return $property->_rent; }else{ return $property->_price; }
668 + $department = $property->_department;
669 + if ( ph_get_custom_department_based_on( $department ) !== false )
670 + {
671 + $department = ph_get_custom_department_based_on( $department );
672 + }
673 + if ( $department == 'residential-lettings' ) { $return = $property->_rent; }else{ $return = $property->_price; }
136 674 }
137 -
138 - return '';
675 + break;
139 676 }
140 - case "price_formatted": { return $property->get_formatted_price(); break; }
141 - case "features": { return $property->get_features(); break; }
142 - case "description": { return $property->get_formatted_description(); break; }
677 + case "price_from":
678 + case "price_to":
679 + {
680 + if ( $property->_price_poa != 'yes' )
681 + {
682 + $return = $property->{'_' . $field_name};
683 + }
684 + break;
685 + }
686 + case "rent_from":
687 + case "rent_to":
688 + {
689 + if ( $property->_rent_poa != 'yes' )
690 + {
691 + $return = $property->{'_' . $field_name};
692 + }
693 + break;
694 + }
695 + case "price_formatted": { $return = $property->get_formatted_price(); break; }
696 + case "features": { $return = $property->get_features(); break; }
697 + case "description": { $return = $property->get_formatted_description(); break; }
143 698 case "office":
144 699 {
145 - return array(
700 + $return = array(
146 701 'name' => $property->office_name,
147 702 'address' => $property->office_address,
148 703 'telephone_number' => $property->office_telephone_number,
149 704 'email_address' => $property->office_email_address,
@@ -148,20 +703,605 @@
148 703 'telephone_number' => $property->office_telephone_number,
149 704 'email_address' => $property->office_email_address,
150 705 );
151 706 break;
707 + }
708 + case "negotiator":
709 + {
710 + $return = array(
711 + 'name' => $property->negotiator_name,
712 + 'telephone_number' => $property->negotiator_telephone_number,
713 + 'email_address' => $property->negotiator_email_address,
714 + );
715 + break;
716 + }
717 + case "images":
718 + {
719 + $images_array = array();
720 + if ( get_option('propertyhive_images_stored_as', '') == 'urls' )
721 + {
722 + $image_urls = $property->_photo_urls;
723 + if ( !is_array($image_urls) ) { $image_urls = array(); }
724 +
725 + foreach ( $image_urls as $image_url )
726 + {
727 + if ( isset($image_url['url']) )
728 + {
729 + $images_array[] = array(
730 + 'url' => $image_url['url'],
731 + 'large' => $image_url['url'],
732 + 'medium' => $image_url['url'],
733 + 'thumbnail' => $image_url['url'],
734 + );
735 + }
736 + }
737 + }
738 + else
739 + {
740 + $image_ids = $property->get_gallery_attachment_ids();
741 + foreach ( $image_ids as $image_id )
742 + {
743 + $image_url = wp_get_attachment_url($image_id);
744 + if ($image_url !== false)
745 + {
746 + $large_image = wp_get_attachment_image_src( $image_id, 'large' );
747 + $medium_image = wp_get_attachment_image_src( $image_id, 'medium' );
748 + $thumbnail_image = wp_get_attachment_image_src( $image_id, 'thumbnail' );
749 +
750 + $images_array[] = array(
751 + 'url' => $image_url,
752 + 'large' => ( $large_image !== FALSE ? $large_image[0] : $image_url ),
753 + 'medium' => ( $medium_image !== FALSE ? $medium_image[0] : $image_url ),
754 + 'thumbnail' => ( $thumbnail_image !== FALSE ? $thumbnail_image[0] : $image_url ),
755 + );
756 + }
757 + }
758 + }
759 + $return = $images_array;
760 + break;
761 + }
762 + case "floorplans":
763 + {
764 + $floorplans_array = array();
765 + if ( get_option('propertyhive_floorplans_stored_as', '') == 'urls' )
766 + {
767 + $floorplan_urls = $property->_floorplan_urls;
768 + if ( !is_array($floorplan_urls) ) { $floorplan_urls = array(); }
769 +
770 + foreach ( $floorplan_urls as $floorplan_url )
771 + {
772 + if ( isset($floorplan_url['url']) )
773 + {
774 + $floorplans_array[] = array(
775 + 'url' => $floorplan_url['url'],
776 + 'large' => $floorplan_url['url'],
777 + 'medium' => $floorplan_url['url'],
778 + 'thumbnail' => $floorplan_url['url'],
779 + );
780 + }
781 + }
782 + }
783 + else
784 + {
785 + $floorplan_ids = $property->get_floorplan_attachment_ids();
786 + foreach ( $floorplan_ids as $floorplan_id )
787 + {
788 + $floorplan_url = wp_get_attachment_url($floorplan_id);
789 + if ($floorplan_url !== false)
790 + {
791 + $large_image = wp_get_attachment_image_src( $floorplan_id, 'large' );
792 + $medium_image = wp_get_attachment_image_src( $floorplan_id, 'medium' );
793 + $thumbnail_image = wp_get_attachment_image_src( $floorplan_id, 'thumbnail' );
794 +
795 + $floorplans_array[] = array(
796 + 'url' => $floorplan_url,
797 + 'large' => ( $large_image !== FALSE ? $large_image[0] : $floorplan_url ),
798 + 'medium' => ( $medium_image !== FALSE ? $medium_image[0] : $floorplan_url ),
799 + 'thumbnail' => ( $thumbnail_image !== FALSE ? $thumbnail_image[0] : $floorplan_url ),
800 + );
801 + }
802 + }
803 + }
804 + $return = $floorplans_array;
805 + break;
806 + }
807 + case "brochures":
808 + {
809 + $brochures_array = array();
810 + if ( get_option('propertyhive_brochures_stored_as', '') == 'urls' )
811 + {
812 + $brochure_urls = $property->_brochure_urls;
813 + if ( !is_array($brochure_urls) ) { $brochure_urls = array(); }
814 +
815 + foreach ( $brochure_urls as $brochure_url )
816 + {
817 + if ( isset($brochure_url['url']) )
818 + {
819 + $brochures_array[] = array(
820 + 'url' => $brochure_url['url'],
821 + );
822 + }
823 + }
824 + }
825 + else
826 + {
827 + $brochure_ids = $property->get_brochure_attachment_ids();
828 + foreach ( $brochure_ids as $brochure_id )
829 + {
830 + $brochure_url = wp_get_attachment_url($brochure_id);
831 + if ($brochure_url !== false)
832 + {
833 + $brochures_array[] = array(
834 + 'url' => $brochure_url,
835 + );
836 + }
837 + }
838 + }
839 + $return = $brochures_array;
840 + break;
841 + }
842 + case "epcs":
843 + {
844 + $epcs_array = array();
845 + if ( get_option('propertyhive_epcs_stored_as', '') == 'urls' )
846 + {
847 + $epc_urls = $property->_epc_urls;
848 + if ( !is_array($epc_urls) ) { $epc_urls = array(); }
849 +
850 + foreach ( $epc_urls as $epc_url )
851 + {
852 + if ( isset($epc_url['url']) )
853 + {
854 + $epcs_array[] = array(
855 + 'url' => $epc_url['url'],
856 + );
857 + }
858 + }
859 + }
860 + else
861 + {
862 + $epc_ids = $property->get_epc_attachment_ids();
863 + foreach ( $epc_ids as $epc_id )
864 + {
865 + $epc_url = wp_get_attachment_url($epc_id);
866 + if ($epc_url !== false)
867 + {
868 + $epcs_array[] = array(
869 + 'url' => $epc_url,
870 + );
871 + }
872 + }
873 + }
874 + $return = $epcs_array;
875 + break;
876 + }
877 + case "virtual_tours":
878 + {
879 + $return = $property->get_virtual_tours();
880 + break;
152 881 }
153 - case "virtual_tours":
882 + case "views_today":
883 + case "views_yesterday":
884 + case "views_last_7_days":
885 + case "views_last_14_days":
886 + case "views_last_30_days":
887 + case "views_total":
154 888 {
155 - return $property->get_virtual_tours();
889 + $view_statistics = $property->_view_statistics;
890 + if ( !is_array($view_statistics) )
891 + {
892 + $view_statistics = array();
893 + }
894 +
895 + $views = 0;
896 +
897 + $today = current_datetime()->setTime(0, 0, 0);
898 +
899 + $date_from = new DateTimeImmutable('2001-01-01', wp_timezone());
900 + $date_to = $today;
901 +
902 + switch ($field_name)
903 + {
904 + case 'views_last_7_days':
905 + {
906 + $date_from = $today->modify('-7 days');
907 + $date_to = $today->modify('-1 day');
908 + break;
909 + }
910 +
911 + case 'views_last_14_days':
912 + {
913 + $date_from = $today->modify('-14 days');
914 + $date_to = $today->modify('-1 day');
915 + break;
916 + }
917 +
918 + case 'views_last_30_days':
919 + {
920 + $date_from = $today->modify('-30 days');
921 + $date_to = $today->modify('-1 day');
922 + break;
923 + }
924 +
925 + case 'views_today':
926 + {
927 + $date_from = $today;
928 + $date_to = $today;
929 + break;
930 + }
931 +
932 + case 'views_yesterday':
933 + {
934 + $date_from = $today->modify('-1 day');
935 + $date_to = $date_from;
936 + break;
937 + }
938 + }
939 +
940 + for ( $date = $date_from; $date <= $date_to; $date = $date->modify('+1 day') )
941 + {
942 + $date_key = $date->format('Y-m-d');
943 +
944 + if ( isset($view_statistics[$date_key]) )
945 + {
946 + $views += (int)$view_statistics[$date_key];
947 + }
948 + }
949 +
950 + $return = $views;
156 951 break;
157 952 }
158 953 default:
159 954 {
160 - return $property->{$field_name};
955 + $return = $property->{$field_name};
161 956 }
162 957 }
958 +
959 + $return = apply_filters( 'propertyhive_rest_api_property_field_callback', $return, $field_name, $property );
960 + return $return;
163 961 },
962 + 'update_callback' => function ( $value, $object, $field_name )
963 + {
964 + if ( taxonomy_exists($field_name) )
965 + {
966 + wp_set_post_terms( $object->ID, $value, $field_name );
967 + }
968 + else
969 + {
970 + switch ( $field_name )
971 + {
972 + case "price":
973 + {
974 + $property = new PH_Property($object->ID);
975 +
976 + $price = '';
977 + if ( $value != '' )
978 + {
979 + $price = preg_replace("/[^0-9.]/", '', $value);
980 + }
981 +
982 + if ( isset($property->_department) && $property->_department == 'residential-lettings' )
983 + {
984 + update_post_meta( $object->ID, '_rent', $price );
985 + }
986 + else
987 + {
988 + update_post_meta( $object->ID, '_price', $price );
989 + }
990 +
991 + if ( $price != '' )
992 + {
993 + // Store price in common currency (GBP) used for ordering
994 + $ph_countries = new PH_Countries();
995 + $ph_countries->update_property_price_actual( $object->ID );
996 + }
997 +
998 + break;
999 + }
1000 + case "features":
1001 + {
1002 + if ( is_array($value) && !empty($value) )
1003 + {
1004 + update_post_meta( $object->ID, '_features', count($value) );
1005 +
1006 + $i = 0;
1007 + foreach ( $value as $feature )
1008 + {
1009 + update_post_meta( $object->ID, '_feature_' . $i, $feature );
1010 +
1011 + ++$i;
1012 + }
1013 + }
1014 + break;
1015 + }
1016 + case "description":
1017 + {
1018 + if ( ! is_string( $value ) ) {
1019 + return new WP_Error( 'propertyhive_rest_description_invalid', __( 'The description must be text.', 'propertyhive' ), array( 'status' => 400 ) );
1020 + }
1021 + $value = propertyhive_sanitize_description( $value );
1022 + $property = new PH_Property($object->ID);
1023 +
1024 + if ( isset($property->_department) && $property->_department == 'commercial' )
1025 + {
1026 + update_post_meta( $object->ID, '_descriptions', '1' );
1027 + update_post_meta( $object->ID, '_description_name_0', '' );
1028 + update_post_meta( $object->ID, '_description_0', wp_slash( $value ) );
1029 + }
1030 + else
1031 + {
1032 + update_post_meta( $object->ID, '_rooms', '1' );
1033 + update_post_meta( $object->ID, '_room_name_0', '' );
1034 + update_post_meta( $object->ID, '_room_dimensions_0', '' );
1035 + update_post_meta( $object->ID, '_room_description_0', wp_slash( $value ) );
1036 + }
1037 + break;
1038 + }
1039 + case "images":
1040 + case "floorplans":
1041 + case "brochures":
1042 + case "epcs":
1043 + {
1044 + if ( ! current_user_can( 'upload_files' ) || ! current_user_can( 'edit_post', $object->ID ) ) {
1045 + return new WP_Error( 'propertyhive_rest_media_forbidden', __( 'You do not have permission to update property media.', 'propertyhive' ), array( 'status' => 403 ) );
1046 + }
1047 + if ( ! is_array( $value ) || count( $value ) > 100 ) {
1048 + return new WP_Error( 'propertyhive_rest_media_invalid', __( 'Supply an array containing no more than 100 media items.', 'propertyhive' ), array( 'status' => 400 ) );
1049 + }
1050 + foreach ( $value as $media_item ) {
1051 + if ( ! is_array( $media_item ) || ! isset( $media_item['url'] ) || ! is_string( $media_item['url'] ) || ! preg_match( '~^(https?:)?//~i', $media_item['url'] ) ) {
1052 + return new WP_Error( 'propertyhive_rest_media_invalid', __( 'Each media item must contain an HTTP or HTTPS URL.', 'propertyhive' ), array( 'status' => 400 ) );
1053 + }
1054 + }
1055 +
1056 + if ( !function_exists('media_handle_upload') ) {
1057 + require_once(ABSPATH . "wp-admin" . '/includes/image.php');
1058 + require_once(ABSPATH . "wp-admin" . '/includes/file.php');
1059 + require_once(ABSPATH . "wp-admin" . '/includes/media.php');
1060 + }
1061 +
1062 + $hive_field_name = trim($field_name, 's');
1063 + if ( $hive_field_name == 'image' ) { $hive_field_name = 'photo'; }
1064 +
1065 + if ( is_array($value) && !empty($value) )
1066 + {
1067 + if ( get_option('propertyhive_' . $field_name . '_stored_as', '') == 'urls' )
1068 + {
1069 + $media_items = array();
1070 + foreach ( $value as $media_item )
1071 + {
1072 + if (
1073 + isset($media_item['url']) &&
1074 + (
1075 + substr( strtolower($media_item['url']), 0, 2 ) == '//' ||
1076 + substr( strtolower($media_item['url']), 0, 4 ) == 'http'
1077 + )
1078 + )
1079 + {
1080 + $media_items[] = array(
1081 + 'url' => $media_item['url'],
1082 + );
1083 + }
1084 + }
1085 + update_post_meta( $object->ID, '_' . $hive_field_name . '_urls', $media_items );
1086 + }
1087 + else
1088 + {
1089 + $media_ids = array();
1090 + $previous_media_ids = get_post_meta( $object->ID, '_' . $hive_field_name . 's', TRUE );
1091 + foreach ( $value as $media_item )
1092 + {
1093 + if (
1094 + isset($media_item['url']) &&
1095 + (
1096 + substr( strtolower($media_item['url']), 0, 2 ) == '//' ||
1097 + substr( strtolower($media_item['url']), 0, 4 ) == 'http'
1098 + )
1099 + )
1100 + {
1101 + // This is a URL
1102 + $url = $media_item['url'];
1103 + $description = '';
1104 +
1105 + $filename = basename( $url );
1106 +
1107 + // Check, based on the URL, whether we have previously imported this media
1108 + $imported_previously = false;
1109 + $imported_previously_id = '';
1110 + if ( is_array($previous_media_ids) && !empty($previous_media_ids) )
1111 + {
1112 + foreach ( $previous_media_ids as $previous_media_id )
1113 + {
1114 + if (
1115 + get_post_meta( $previous_media_id, '_imported_url', TRUE ) == $url
1116 + )
1117 + {
1118 + $imported_previously = true;
1119 + $imported_previously_id = $previous_media_id;
1120 + break;
1121 + }
1122 + }
1123 + }
1124 +
1125 + if ($imported_previously)
1126 + {
1127 + $media_ids[] = $imported_previously_id;
1128 + }
1129 + else
1130 + {
1131 + $tmp = self::download_media( $url );
1132 + $file_array = array(
1133 + 'name' => basename( $url ),
1134 + 'tmp_name' => $tmp
1135 + );
1136 +
1137 + // Check for download errors
1138 + if ( is_wp_error( $tmp ) )
1139 + {
1140 + return $tmp;
1141 + }
1142 + else
1143 + {
1144 + $id = media_handle_sideload( $file_array, $object->ID, $description, array('post_title' => $filename) );
1145 +
1146 + // Check for handle sideload errors.
1147 + if ( is_wp_error( $id ) )
1148 + {
1149 + wp_delete_file( $file_array['tmp_name'] );
1150 +
1151 + return $id;
1152 + }
1153 + else
1154 + {
1155 + $media_ids[] = $id;
1156 +
1157 + update_post_meta( $id, '_imported_url', $url);
1158 + }
1159 + }
1160 + }
1161 + }
1162 + }
1163 + update_post_meta( $object->ID, '_' . $hive_field_name . 's', $media_ids );
1164 +
1165 + // Loop through $previous_media_ids, check each one exists in $media_ids, and if it doesn't then delete
1166 + if ( is_array($previous_media_ids) && !empty($previous_media_ids) )
1167 + {
1168 + foreach ( $previous_media_ids as $previous_media_id )
1169 + {
1170 + if ( ! in_array( $previous_media_id, $media_ids ) && current_user_can( 'delete_post', $previous_media_id ) )
1171 + {
1172 + if ( wp_delete_attachment( $previous_media_id, TRUE ) !== FALSE )
1173 + {
1174 +
1175 + }
1176 + }
1177 + }
1178 + }
1179 + }
1180 + }
1181 +
1182 + break;
1183 + }
1184 + case "virtual_tours":
1185 + {
1186 + if ( is_array($value) && !empty($value) )
1187 + {
1188 + update_post_meta( $object->ID, '_virtual_tours', count($value) );
1189 +
1190 + $i = 0;
1191 + foreach ( $value as $virtual_tour )
1192 + {
1193 + update_post_meta( $object->ID, '_virtual_tour_' . $i, ( isset($virtual_tour['url']) ? $virtual_tour['url'] : '' ) );
1194 + update_post_meta( $object->ID, '_virtual_tour_label_' . $i, ( isset($virtual_tour['label']) ? $virtual_tour['label'] : '' ) );
1195 +
1196 + ++$i;
1197 + }
1198 + }
1199 + break;
1200 + }
1201 + default:
1202 + {
1203 + update_post_meta( $object->ID, '_' . $field_name, $value );
1204 + }
1205 + }
1206 + }
1207 +
1208 + do_action( 'propertyhive_rest_api_property_field_update_callback', $value, $object, $field_name );
1209 + },
1210 + 'schema' => null,
1211 + )
1212 + );
1213 + }
1214 + }
1215 +
1216 + public function rest_prepare_property($response, $post, $request)
1217 + {
1218 + // Hide/show fields dynamically per item before it's returned
1219 +
1220 + $data = $response->get_data();
1221 +
1222 + // Get the department
1223 + $department = $data['department'] ?? get_post_meta($post->ID, '_department', true);
1224 + if ( ph_get_custom_department_based_on( $department ) !== false )
1225 + {
1226 + $department = ph_get_custom_department_based_on( $department );
1227 + }
1228 +
1229 + // Define the "not applicable" fields per department
1230 + $remove_for = [
1231 + 'residential-sales' => [
1232 + // specify non-sales fields
1233 + 'available_date', 'deposit', 'furnished', 'rent_frequency',
1234 + 'commercial_tenure', 'commercial_property_type', 'for_sale', 'to_rent', 'price_from', 'price_to', 'price_units', 'rent_from', 'rent_to', 'rent_units', 'floor_area_from', 'floor_area_to', 'floor_area_units', 'site_area_from', 'site_area_to', 'site_area_units'
1235 + ],
1236 + 'residential-lettings' => [
1237 + // specify non-lettings fields
1238 + 'sale_by', 'tenure',
1239 + 'commercial_tenure', 'commercial_property_type', 'for_sale', 'to_rent', 'price_from', 'price_to', 'price_units', 'rent_from', 'rent_to', 'rent_units', 'floor_area_from', 'floor_area_to', 'floor_area_units', 'site_area_from', 'site_area_to', 'site_area_units',
1240 + ],
1241 + 'commercial' => [
1242 + // specify non-commercial fields
1243 + 'price', 'price_actual', 'bedrooms', 'bathrooms', 'reception_rooms', 'outside_space', 'parking',
1244 + 'available_date', 'deposit', 'furnished', 'rent_frequency',
1245 + ],
1246 + ];
1247 +
1248 + if ( !empty($remove_for[$department]) )
1249 + {
1250 + foreach ( $remove_for[$department] as $k )
1251 + {
1252 + unset($data[$k]);
1253 + }
1254 + }
1255 +
1256 + $response->set_data($data);
1257 +
1258 + return $response;
1259 + }
1260 +
1261 + public function register_rest_api_office_fields()
1262 + {
1263 + $field_array = array(
1264 + 'primary',
1265 + 'office_address_1',
1266 + 'office_address_2',
1267 + 'office_address_3',
1268 + 'office_address_4',
1269 + 'office_address_postcode',
1270 + 'office_latitude',
1271 + 'office_longitude',
1272 + );
1273 +
1274 + $departments = ph_get_departments();
1275 +
1276 + foreach ( $departments as $key => $value )
1277 + {
1278 + $field_array[] = 'office_telephone_number_' . str_replace("residential-", "", $key);
1279 + $field_array[] = 'office_email_address_' . str_replace("residential-", "", $key);
1280 + }
1281 +
1282 + $field_array = apply_filters( 'propertyhive_rest_api_office_fields', $field_array );
1283 +
1284 + foreach ( $field_array as $field )
1285 + {
1286 + register_rest_field( 'office',
1287 + $field,
1288 + array(
1289 + 'get_callback' => function( $object, $field_name, $request )
1290 + {
1291 + $return = '';
1292 +
1293 + switch ($field_name)
1294 + {
1295 + default:
1296 + {
1297 + $return = get_post_meta( $object[ 'id' ], '_' . $field_name, TRUE );
1298 + }
1299 + }
1300 +
1301 + $return = apply_filters( 'propertyhive_rest_api_office_field_callback', $return, $field_name, $object[ 'id' ] );
1302 + return $return;
1303 + },
164 1304 'update_callback' => null,
165 1305 'schema' => null,
166 1306 )
167 1307 );
@@ -168,5 +1308,4 @@
168 1308 }
169 1309 }
170 1310
171 1311 }
172 -