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
propertyhive / includes / class-ph-rest-api.php

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

1,312 lines 42.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit; // Exit if accessed directly
5 }
6
7 /**
8 * Serve properties using the REST API
9 *
10 * @class PH_Rest_Api
11 * @version 1.0.0
12 * @package PropertyHive/Classes/
13 * @category Class
14 * @author PropertyHive
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.
17 class PH_Rest_Api {
18
19 /** @var PH_Rest_Api The single instance of the class */
20 protected static $_instance = null;
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 /**
52 * Main PH_Rest_Api Instance.
53 *
54 * Ensures only one instance of PH_Rest_Api is loaded or can be loaded.
55 *
56 * @since 1.0.0
57 * @static
58 * @return PH_Licenses Main instance
59 */
60 public static function instance() {
61 if ( is_null( self::$_instance ) ) {
62 self::$_instance = new self();
63 }
64 return self::$_instance;
65 }
66
67 /**
68 * Cloning is forbidden.
69 *
70 * @since 1.0.0
71 */
72 public function __clone() {
73 _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin&#8217; huh?', 'propertyhive' ), '1.0.0' );
74 }
75
76 /**
77 * Unserializing instances of this class is forbidden.
78 *
79 * @since 1.0.0
80 */
81 public function __wakeup() {
82 _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin&#8217; huh?', 'propertyhive' ), '1.0.0' );
83 }
84
85 /**
86 * Constructor for the licenses class
87 *
88 */
89 public function __construct() {
90 // Property
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 );
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 );
108 }
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
550 public function modify_rest_property_query($args, $request)
551 {
552 /*if ( !isset( $args['meta_query'] ) )
553 {
554 $args['meta_query'] = array();
555 }*/
556
557 $PH_Query = new PH_Query();
558
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.
561 $args['meta_query'] = $PH_Query->get_meta_query();
562
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.
565 $args['tax_query'] = $PH_Query->get_tax_query();
566
567 // Date query
568 $args['date_query'] = $PH_Query->get_date_query();
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
577 $args = apply_filters( 'propertyhive_rest_api_query_args', $args );
578
579 return $args;
580 }
581
582 public function register_rest_api_property_fields()
583 {
584 $field_array = array(
585 'department',
586 'reference_number',
587 'address_street',
588 'address_two',
589 'address_three',
590 'address_four',
591 'address_postcode',
592 'address_country',
593 'latitude',
594 'longitude',
595 'price_actual',
596 'price',
597 'price_from',
598 'price_to',
599 'price_units',
600 'rent_from',
601 'rent_to',
602 'rent_units',
603 'price_formatted',
604 'rent_frequency',
605 'currency',
606 'price_qualifier',
607 'sale_by',
608 'tenure',
609 'council_tax_band',
610 'deposit',
611 'furnished',
612 'available_date',
613 'bedrooms',
614 'bathrooms',
615 'reception_rooms',
616 'property_type',
617 'parking',
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',
627 'on_market',
628 'featured',
629 'location',
630 'availability',
631 'marketing_flag',
632 'features',
633 'description',
634 'office',
635 'negotiator',
636 'images',
637 'floorplans',
638 'brochures',
639 'epcs',
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',
647 );
648
649 $field_array = apply_filters( 'propertyhive_rest_api_property_fields', $field_array );
650
651 foreach ( $field_array as $field )
652 {
653 register_rest_field( 'property',
654 $field,
655 array(
656 'get_callback' => function( $object, $field_name, $request )
657 {
658 $property = new PH_Property($object[ 'id' ]);
659
660 $return = '';
661
662 switch ($field_name)
663 {
664 case "price":
665 {
666 if ( $property->_poa != 'yes' )
667 {
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; }
674 }
675 break;
676 }
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; }
698 case "office":
699 {
700 $return = array(
701 'name' => $property->office_name,
702 'address' => $property->office_address,
703 'telephone_number' => $property->office_telephone_number,
704 'email_address' => $property->office_email_address,
705 );
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;
881 }
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":
888 {
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;
951 break;
952 }
953 default:
954 {
955 $return = $property->{$field_name};
956 }
957 }
958
959 $return = apply_filters( 'propertyhive_rest_api_property_field_callback', $return, $field_name, $property );
960 return $return;
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 },
1304 'update_callback' => null,
1305 'schema' => null,
1306 )
1307 );
1308 }
1309 }
1310
1311 }
1312