VaultRE is a 3rd party properties management system
When I first submitted an application, I named it to as the current project https://prnt.sc/zg3CODQW6u1_
I should have just named it with our Company Name (Five by Five) instead so we can use the same API Key for all our future projects.
Next time when you submit an application as an integrator use our Company name.
Here’s the entire class codes used on this project, we will explain the key parts to our process below.
get_data($request_url);
$cache_key = 'vaultre_properties_'. $type . '_'. $page;
set_transient($cache_key, $data, 7200);
if( !$data ) return;
$property_posts = $this->get_property_posts();
$interval = 2; // secs
$i = $interval;
foreach( $data['items'] as $item ) {
$property_id = $item['id'];
$post = $this->get_property_post_from_arr($property_id, $property_posts);
if( $post ) {
// update existing
// check if item has been modified since last update
if( $post['vaultre_modified_time'] >= $item['modified'] ) {
// no change
continue;
}
$post_id = $post['id'];
}
else {
// create new post
$post_id = $this->create_post_property($item);
}
if( !$post_id ) continue;
wp_schedule_single_event( time() + $i, 'vaultre_update_property', [$property_id, $post_id, $type, $cache_key] );
$i += $interval;
}
if( $data['urls']['next'] ) {
// have more items
$request_url = $data['urls']['next'];
wp_schedule_single_event( time() + 10, 'vaultre_process_next_batch', [$type, $request_url, $page+1] );
}
}
public function get_property($property_id, $cache_key){
$data = get_transient($cache_key);
if( !$data ) return false;
foreach( $data['items'] as $item ) {
if( $item['id'] == $property_id ) {
return $item;
}
}
return false;
}
public function get_properties_data($type){
$request_url = '/properties/residential/sale/available';
if( $type == 'Sold' ) {
$request_url = '/properties/residential/sale/sold';
}
return $this->get_data($request_url);
}
public function get_data($request_url){
list($code, $result) = $this->get($request_url);
if ($code == 403) {
echo "HTTP 403: Invalid API Key";
return false;
} elseif ($code == 401) {
echo "HTTP 401: Invalid bearer token\n";
return false;
}
$result = json_decode($result, true);
return $result;
}
public function create_post_property($property){
$suburb = $property['address']['suburb']['name'];
$address = $property['displayAddress'];
$post_title = $suburb .' - '. $address;
$post_date = $property['inserted'];
if( !$post_date ) {
$post_date = $property['modified'];
}
$post_id = wp_insert_post([
'post_title' => $post_title,
'post_type' => 'property',
'post_status' => 'publish',
'post_date' => $post_date,
]);
update_post_meta($post_id, 'property_id', $property['id']);
return $post_id;
}
public function update_property($post_id, $property, $type){
$vaultre_modified_time = get_post_meta($post_id, 'vaultre_modified_time', true);
if( $vaultre_modified_time ) {
if( $vaultre_modified_time >= $property['modified'] ) {
// no change
return;
}
}
update_post_meta($post_id, 'vaultre_data', $property);
// description / content
wp_update_post([
'ID' => $post_id,
'post_content' => $property['description'],
]);
// Category = Buy / Sold
$this->assign_terms($post_id, [$type], 'property_category');
// property type
$property_type = $property['type']['propertyClass']['name']; // Land / Commercial / Residential
if( $property_type == 'Residential' ) {
$property_type = $property['type']['name']; // House / Unit
}
$this->assign_terms($post_id, [$property_type], 'property_type');
$suburb = $property['address']['suburb']['name'];
// price
// pricing is different per type: Buy = searchPrice | Search = salePrice
$price = ( $type == 'Sold' ) ? $property['saleDetails']['salePrice'] : $property['searchPrice'];
$metas = [
'property_type' => $property_type,
'property_sqm' => $property['landArea']['value'],
'property_address' => $property['displayAddress'],
'property_price' => $price,
'property_no_of_beds' => $property['bed'],
'property_no_of_baths' => $property['bath'],
'property_no_of_car' => $property['garages'],
'suburb' => $suburb,
'heading' => $property['heading'],
'location' => $this->get_location_post_id($suburb),
];
if( $property['photos'] ) {
// featured image
$featured_image_id = $this->upload_image_from_url($property['photos'][0]['url']);
if( $featured_image_id ) {
$metas['_thumbnail_id'] = $featured_image_id;
};
// Gallery
if( count($property['photos']) > 1 ) {
$gallery_ids = [ $featured_image_id ];
// $images_limit = 12 + 1; // +1 for featured image
$i = 0;
foreach( $property['photos'] as $photo ) { $i++;
if($i == 1) continue; // featured image - already added
$image_id = $this->upload_image_from_url($photo['url']);
if($image_id) $gallery_ids[] = $image_id;
// if( $i == $images_limit ) break;
}
$metas['property_gallery'] = $gallery_ids;
}
}
$this->update_post_metas($post_id, $metas);
$this->assign_contact_staff($post_id, $property);
update_post_meta($post_id, 'vaultre_modified_time', $property['modified']);
}
public function get_property_post($property_id){
$args = [
'post_type' => 'property',
'showposts' => 1,
'meta_query' => [
[
'key' => 'property_id',
'value' => $property_id,
'compare' => '='
]
]
];
$q = new WP_Query($args);
if( $q->posts ) {
$post = $q->posts[0];
return [
'id' => $post->ID,
'title' => $post->post_title,
'vaultre_modified_time' => get_post_meta($post->ID, 'vaultre_modified_time', true),
];
}
return false;
}
public function get_property_posts(){
$posts = [];
$args = [
'post_type' => 'property',
'showposts' => -1,
];
$q = new WP_Query($args);
if( !$q->posts ) return $posts;
foreach( $q->posts as $post ) {
$property_id = get_post_meta($post->ID, 'property_id', true);
if( !$property_id ) continue;
$posts[] = [
'id' => $post->ID,
'property_id' => $property_id,
'title' => $post->post_title,
'vaultre_modified_time' => get_post_meta($post->ID, 'vaultre_modified_time', true),
];
}
return $posts;
}
public function get_property_post_from_arr($property_id, $posts_array){
foreach( $posts_array as $post ) {
if( $post['property_id'] == $property_id ) {
return $post;
}
}
return false;
}
public function assign_contact_staff($post_id, $property) {
$team_members_ids = $this->get_team_members_cache();
$property_staff_ids = [];
foreach( $property['contactStaff'] as $staff ) {
$staff_name = $staff['firstName'] . ' ' . $staff['lastName'];
if( isset($team_members_ids[$staff_name]) ) {
$staff_id = $team_members_ids[$staff_name];
$property_staff_ids[] = $staff_id;
}
}
if( $property_staff_ids ) {
update_post_meta($post_id, 'contact_staff', $property_staff_ids);
}
}
public function get_location_post_id($title){
$args = [
'post_type' => 'location',
'title' => $title,
'showposts' => 1,
'fields' => 'ids',
];
$q = new WP_Query($args);
if( $q->posts ) {
return $q->posts[0];
}
return false;
}
function update_post_metas($post_id, $metas) {
foreach( $metas as $meta_key => $meta_value ) {
if( !$meta_value ) continue;
$current_meta = get_post_meta( $post_id, $meta_key, true);
if( $current_meta == $meta_value ) continue; // skip, no change
update_post_meta($post_id, $meta_key, $meta_value);
}
}
function assign_terms($post_id, $terms, $taxonomy, $append = false) {
$term_ids = [];
foreach( $terms as $term_name ) {
$term = term_exists( $term_name, $taxonomy );
if( $term ) {
$term_id = $term['term_id'];
}
else {
$term_id = wp_insert_term( $term_name, $taxonomy );
}
if( $term_id ) {
$term_ids[] = $term_id;
}
}
if( $term_ids ) {
wp_set_post_terms( $post_id, $term_ids, $taxonomy, $append );
}
}
function upload_image_from_url($image_url, $post_id = '', $description = ''){
require_once(ABSPATH . 'wp-admin/includes/media.php');
require_once(ABSPATH . 'wp-admin/includes/file.php');
require_once(ABSPATH . 'wp-admin/includes/image.php');
$path_info = pathinfo($image_url);
$file_name = $path_info['filename'];
$image_id = $this->get_image_id_by_filename($file_name);
if( !$image_id ) {
// only upload if it doesn't exist in the media yet
$image_id = media_sideload_image( $image_url, $post_id, $description, 'id' );
}
return $image_id;
}
function get_image_id_by_filename($file_name){
global $wpdb;
$result = $wpdb->get_col($wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE guid LIKE '%s' LIMIT 1;", '%' . $wpdb->esc_like($file_name) . '%' ));
if( $result ) {
return $result[0];
}
return false;
}
public function get_team_members_ids(){
$team = [];
$args = [
'post_type' => 'team',
'showposts' => -1,
'no_found_rows' => true,
'post_status' => [
'publish',
'draft',
],
];
$q = new WP_Query($args);
foreach($q->posts as $post){
pre_debug([
$post->ID,
$post->post_title,
]);
$team[$post->post_title] = $post->ID;
}
return $team;
}
public function update_team_members_cache(){
$team_member_ids = $this->get_team_members_ids();
set_transient('team_members_ids', $team_member_ids, 7200);
}
public function get_team_members_cache(){
$cache_key = 'team_members_ids';
$cache = get_transient($cache_key);
if( $cache ) {
return $cache;
}
$team_member_ids = $this->get_team_members_ids();
set_transient($cache_key, $team_member_ids, 7200);
return $team_member_ids;
}
private function get_url($url) {
return (strpos($url, $this->base_url) !== false) ? $url : $this->base_url . $url;
}
public function get($endpoint) {
$ch = curl_init($this->get_url($endpoint));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: application/json', 'X-Api-Key: ' . $this->api_key, 'Authorization: Bearer ' . $this->access_token));
$result = curl_exec($ch);
$response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
return array($response_code, $result);
}
}
Tools > Cron Events > Add New
set name and interval time to what is required on the current project. You could name it “vaultre_update_properties_buy” for example.
We will create 2 separate cron events for “buy/for sale” and “sold” to split the process and avoid server timeout.
Add action hooks for the newly created cron events, e.g:
// Properties - Buy
add_action('vaultre_update_properties_buy', function(){
require_once 'VaultREAPI.php';
$vaultre = new VaultREAPI();
$vaultre->schedule_update('Buy', '/properties/residential/sale/available?pagesize=100', 1);
$vaultre->schedule_update('Buy', '/properties/commercial/sale/available?pagesize=100', 1);
$vaultre->schedule_update('Buy', '/properties/land/sale/available?pagesize=100', 1);
});
// Properties - Sold
add_action('vaultre_update_properties_sold', function(){
require_once 'VaultREAPI.php';
$vaultre = new VaultREAPI();
$vaultre->schedule_update('Sold', '/properties/residential/sale/sold?pagesize=100', 1);
$vaultre->schedule_update('Sold', '/properties/commercial/sale/sold?pagesize=100', 1);
$vaultre->schedule_update('Sold', '/properties/land/sale/sold?pagesize=100', 1);
});
The schedule_update method accepts 3 parameters:
This is a one-time only task (non-repeating) no need to add from WP-Crontrol plugin. This is registered inside the schedule_update method https://prnt.sc/NWqRy2rtMvPg
// Process - next batch from pagination
add_action('vaultre_process_next_batch', function($type, $request_url, $page){
require_once 'VaultREAPI.php';
$vaultre = new VaultREAPI();
$vaultre->schedule_update($type, $request_url, $page);
}, 10, 3);
// Process individual item update
add_action('vaultre_update_property', function($property_id, $post_id, $type, $cache_key){
require_once 'VaultREAPI.php';
$vaultre = new VaultREAPI();
$property = $vaultre->get_property($property_id, $cache_key);
if( !$property ) return;
$vaultre->update_property($post_id, $property, $type);
}, 10, 4);
class VaultREAPI {
private $api_key = 'ENTER_API_KEY_HERE';
private $access_token = 'ENTER_ACCESS_TOKEN_HERE';
private $base_url = 'https://ap-southeast-2.api.vaultre.com.au/api/v1.3';
public function __construct() {}
private function get_url($url) {
return (strpos($url, $this->base_url) !== false) ? $url : $this->base_url . $url;
}
public function get($endpoint) {
$ch = curl_init($this->get_url($endpoint));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: application/json', 'X-Api-Key: ' . $this->api_key, 'Authorization: Bearer ' . $this->access_token));
$result = curl_exec($ch);
$response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
return array($response_code, $result);
}
}
$vaultre = new VaultREAPI();
// get sold residential properties
$data = $vaultre->get('/properties/residential/sale/sold');
codes below are from the schedule_update method
// get data from API
$data = $this->get_data($request_url);
// cache - for quicker access later
// our cache key will have dynamic name for type (Buy/Sold) and pagination number
// this cache will be automatically cleared after 2 hours
$cache_key = 'vaultre_properties_'. $type . '_'. $page;
set_transient($cache_key, $data, 7200);
// query all property(post_type) posts and get the data for:
// id = post id
// property_id = unique property id from vaultre
// vaultre_modified_time = update time from vaultre
$property_posts = $this->get_property_posts();
$interval = 2; // secs
$i = $interval;
foreach( $data['items'] as $item ) {
$property_id = $item['id'];
// check if current item is already created as a post, based on property id
$post = $this->get_property_post_from_arr($property_id, $property_posts);
if( $post ) {
// update existing
// check if item has been modified since last update
if( $post['vaultre_modified_time'] >= $item['modified'] ) {
continue; // no change
}
$post_id = $post['id'];
}
else {
// create new post
$post_id = $this->create_post_property($item);
}
if( !$post_id ) continue;
// item has been updated, schedule update cron task
// note, we are passing the $cache_key, which be used later to access the data of the specific item/property
wp_schedule_single_event( time() + $i, 'vaultre_update_property', [$property_id, $post_id, $type, $cache_key] );
$i += $interval;
}
if( $data['urls']['next'] ) {
// have more items, schedule processing next batch
$request_url = $data['urls']['next'];
wp_schedule_single_event( time() + 10, 'vaultre_process_next_batch', [$type, $request_url, $page+1] );
}
$vaultre_modified_time = get_post_meta($post_id, 'vaultre_modified_time', true);
if( $vaultre_modified_time ) {
if( $vaultre_modified_time >= $property['modified'] ) {
return; // no change
}
}
update_post_meta($post_id, 'vaultre_data', $property);
// description / content
wp_update_post([
'ID' => $post_id,
'post_content' => $property['description'],
]);
// Category = Buy / Sold
$this->assign_terms($post_id, [$type], 'property_category');
// Property type
$property_type = $property['type']['propertyClass']['name']; // Land / Commercial / Residential
if( $property_type == 'Residential' ) {
$property_type = $property['type']['name']; // House / Unit
}
$this->assign_terms($post_id, [$property_type], 'property_type');
$suburb = $property['address']['suburb']['name'];
// price
// pricing is different per type: Buy = searchPrice | Search = salePrice
$price = ( $type == 'Sold' ) ? $property['saleDetails']['salePrice'] : $property['searchPrice'];
$metas = [
'property_type' => $property_type,
'property_sqm' => $property['landArea']['value'],
'property_address' => $property['displayAddress'],
'property_price' => $price,
'property_no_of_beds' => $property['bed'],
'property_no_of_baths' => $property['bath'],
'property_no_of_car' => $property['garages'],
'suburb' => $suburb,
'heading' => $property['heading'],
'location' => $this->get_location_post_id($suburb),
];
if( $property['photos'] ) {
// featured image
$featured_image_id = $this->upload_image_from_url($property['photos'][0]['url']);
if( $featured_image_id ) {
$metas['_thumbnail_id'] = $featured_image_id;
};
// Gallery
if( count($property['photos']) > 1 ) {
$gallery_ids = [ $featured_image_id ];
// $images_limit = 12 + 1; // +1 for featured image
$i = 0;
foreach( $property['photos'] as $photo ) { $i++;
if($i == 1) continue; // featured image - already added
$image_id = $this->upload_image_from_url($photo['url']);
if($image_id) $gallery_ids[] = $image_id;
// if( $i == $images_limit ) break;
}
$metas['property_gallery'] = $gallery_ids;
}
}
$this->update_post_metas($post_id, $metas);
$this->assign_contact_staff($post_id, $property);
update_post_meta($post_id, 'vaultre_modified_time', $property['modified']);
Select your desired option below to share a direct link to this page.
Your friends or family will thank you later.