VaultRE Integration (Custom Codes)

VaultRE is a 3rd party properties management system

1.) Fill up and submit the form to add us as an "approved integrator",
after being approved you will be provided(emailed) an API KEY

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.

2.) Generate Token

Login to VaultRE Account
Dashboard > Office Integrations > Third-Party Access > Add Token > Select the name you submitted in step 1

Implementation Process

  • Create CRON (Scheduled task) to pull data from the API periodically (repeating)
  • Store the data to a cache/transient for quicker access later on
  • Main CRON task would read all the data from the API, parse it and identify what has been updated
  • Only process items that has been updated
  • Schedule each item to update as a one-off (non-repeating) cron task, to avoid server timeout for long tasks(the downloading of images takes the most time)
  • On the update item codes, after processing the data, add a post meta e.g: “vaultre_modifited_time” store the value “modified” (modified time of the current item). This is used to identify if the current item has changed since the last update.

Coding

Entire Class Codes

Here’s the entire class codes used on this project, we will explain the key parts to our process below.

				
					<?php
class VaultREAPI {
    
    private $api_key = 'ENTER_API_KEY_HERE';
    private $access_token = 'ENTER_TOKEN_HERE';
    private $base_url = 'https://ap-southeast-2.api.vaultre.com.au/api/v1.3';

    public function __construct() {}

    public function schedule_update($type, $request_url, $page){
        
        $data = $this->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);
    }
    
}
				
			

Setting up CRON to pull data from the API periodically

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:

  1. $type (string) = “Buy” or “Sold” , this is used later on the code to assign the item to the correct categories and pull specific data for each type
  2. $request_url (string) = API endpoint. see list here: https://docs.api.vaultre.com.au/swagger/index.html
  3. $page (int) = used by the API for pagination
Additional cron event hook to handle the next set of items from the pagination.

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);
				
			
Additional cron event hook to handle update for each individual item
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/9VS2fBtNQ3Hw
				
					// 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);
				
			

Codes Explanation:

Getting data from the API

Here's a basic example code to get data from the API
				
					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');
				
			

Processing the data from the API

codes below are from the schedule_update method

Save/cache the data from the API for faster access later
				
					// 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);
				
			
Loop through the items and identify updated items. Create new posts for new items. Schedule cron task for updated items.
				
					// 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;
}
				
			
Check if there are more items from the API (paginated)
				
					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] );
}
				
			

Updating data for individual items

codes below are from the update_property method
Additional check if to make sure we are only updating the latest data from the API. Sometimes the API will return multiple entries of the same item with different update times.
				
					$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
    }
}
				
			
Save a raw copy of data from the API to the post meta, just in-case it will be needed later on, so that we don't have access the API for additional updates later
				
					update_post_meta($post_id, 'vaultre_data', $property);
				
			
Updating post data, the post meta keys used on this project would probably differ from your current project so adjust accordingly
				
					// 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);
				
			
Finally, save the update/modified time from vaultre. This is crucial to identify what items has been changed since the last update.
				
					update_post_meta($post_id, 'vaultre_modified_time', $property['modified']);
				
			

Share This

Select your desired option below to share a direct link to this page.
Your friends or family will thank you later.