<?php

require_once __DIR__ . '/class.utility.php';

/**
 * OVIS API wrapper
 * 
 * @link https://api.ovis.nl/
 * @link https://docs.ovis.nl/portal/site/
 */
class Ovis {

    /**
     * Constants
     */
    const DEBUG_MODE = false;

    const REALMS = array(
        'OVIS' => 'ovis',
        'BOAT' => 'boat'
    );

    const CONDITIONS = array(
        'new',
        'occasion',
        'rental'
    );

    const HTTP_METHODS = array(
        'GET' => 'GET',
        'POST' => 'POST'
    );

    const API_BASE_URLS = array(
        'PROD' => 'https://api.ovis.nl/',
        'DEV' => 'https://api-develop.ovis.nl/'
    );

    const API_ENDPOINTS = array(
        'SEARCH'                    => 'search/',
        'SEARCH_USER'               => 'search/user/',
        'SEARCH_USERS'              => 'search/users/',
        'SEARCH_BRANDS'             => 'search/brands/',
        'SEARCH_CATEGORIES'         => 'search/categories/',
        'SEARCH_DIVISIONS'          => 'search/divisions/',
        'SEARCH_MIN_MAX'            => 'search/minmax/',
        'SEARCH_MIN_MAX_CLIENT'     => 'search/minmax/client/',
        'SEARCH_TRANSLATIONS'       => 'search/translations/',
        'SEARCH_RENTAL_STATUS'      => 'search/rentalstatus/',
        'SEARCH_CONTACT_DEALER'     => 'search/contactdealer/',
        'SEARCH_RENTAL_REQUEST'     => 'search/rentalrequest/',
        'SEARCH_TRADE_IN_REQUEST'   => 'search/tradeinrequest/'
    );

    const DEFAULT_ITEMS_PER_PAGE = 12;
    const DEFAULT_SORTING_PROPERTY = 'brand';
    const ALL_OVIS_REALM_CATEGORIES = array('caravan', 'camper', 'mobilehome', 'tenttrailer', 'trailer');
    const ALL_BOAT_REALM_CATEGORIES = array('boat', 'engine', 'boattrailer');

    /**
     * Custom response object
     * 
     * Consists of:
     *  * boolean 'success'
     *  * array 'errors'
     *  * mixed 'result'
     * 
     * @var object
     */
    private $_oResponseObject;

    /**
     * The API base url
     * 
     * This can either be the production or development endpoints
     * 
     * @var string
     * 
     * @link https://api.ovis.nl/
     * @link https://dev-api.ovis.nl/
     */
    private $_sApiBaseUrl;

    /**
     * The specific API endpoint that gets placed after the base URL
     * 
     * Example: {$_sApiBaseUrl}/{$_sApiEndpoint}/
     * 
     * This gets to passed to the cURL session by curl_setopt($this->_oCurlHandle, CURLOPT_URL, $sUrl);
     * 
     * @var string
     */
    private $_sApiEndpoint;

    /**
     * The API authentication key used to authenticate this client
     * 
     * This will be send in the HTTP request headers
     * 
     * @var string
     */
    private $_sApiAuthenticationKey;

    /**
     * The HTTP request headers being send with every HTTP request to the API
     * 
     * Here the Content-Type and Authentication fields are stored
     * 
     * @var string[]
     */
    private $_aHttpRequestHeaders;

    /**
     * @var resource
     */
    private $_curlLog;

    /**
     * The cURL handle used to make HTTP requests
     * 
     * This will be instantiated by curl_init()
     * 
     * @var object
     * 
     * @link https://www.php.net/manual/en/function.curl-init.php
     */
    private $_oCurlHandle;

    /**
     * The cURL multi handle used to make asynchronous requests
     *
     * This will be instantiated by curl_multi_init()
     * 
     * @var object
     * 
     * @link https://www.php.net/manual/en/function.curl-multi-init.php
     */
    private $oCurlMultiHandle;

    /**
     * The HTTP request body containing JSON object
     * 
     * This object will be encoded into a JSON object with the json_encode() function
     * 
     * @var object
     * 
     * @link https://www.php.net/manual/en/function.json-encode.php
     */
    private $_oHttpRequestBody;

    /**
     * OVIS presentation specifications
     */
    private $_bUseCompactOutput = false;
    private $_aSort;
    private $_bHideSold = false;
    private $_bHideExpected = false;
    private $_bHideReserved = false;
    private $_sRealm = self::REALMS['OVIS'];
    private $_aStatus = array();
    private $_aDealers = array();
    private $_aDivisions = array();
    private $_sAvailable;
    private $_aSelectedCategories = array();
    private $_aSelectedSubCategories = array();
    private $_sImageFormat = 'webp';
    private $_iPage = 1;
    private $_iItemsPerPage = self::DEFAULT_ITEMS_PER_PAGE;
    private $_bIsFastSailing;
    private $_bIsConditionNew = true;
    private $_bIsConditionOccasion = true;
    private $_bIsRental = false;
    private $_aSelectedBrands = array();
    private $_aSelectedModels = array();
    private $_sModelTypeVersion;
    private $_sKeyword;
    private $_sEngineType;
    private $_sEngineBrand;
    private $_aFuel = array();
    private $_aTransmission = array();
    private $_aEuroclass = array();
    private $_sTailLength;
    private $_fPriceFrom;
    private $_fPriceTo;
    private $_iWeightFrom;
    private $_iWeightTo;
    private $_iLengthFrom;
    private $_iLengthTo;
    private $_iWidthFrom;
    private $_iWidthTo;
    private $_iMileageFrom;
    private $_iMileageTo;
    private $_iPowerFrom;
    private $_iPowerTo;
    private $_sRentalDateFrom;
    private $_sRentalDateTo;
    private $_iConstructionYearFrom;
    private $_iConstructionYearTo;
    private $_iModelYearFrom;
    private $_iModelYearTo;
    private $_iSleepingPlacesFrom;
    private $_iSleepingPlacesTo;
    private $_iSeatbeltPlacesFrom;
    private $_iSeatbeltPlacesTo;


    public function __construct() {

        $this->_oCurlHandle = curl_init();

        $this->_oHttpRequestBody = new \stdClass();

        $this->_oResponseObject = new \stdClass();
        $this->_oResponseObject->errors = array();
        $this->_oResponseObject->success = false;

        $this->_aSort = array(
            (object)array(
                'field' => self::DEFAULT_SORTING_PROPERTY, 
                'descending' => false
            )
        );

        $this->_oHttpRequestBody->useCache		= true;
		$this->_oHttpRequestBody->page			= $this->_iPage;
		$this->_oHttpRequestBody->itemsPerPage	= $this->_iItemsPerPage;
        $this->_oHttpRequestBody->order         = $this->_aSort;

        $this->_aHttpRequestHeaders = array(
			'Content-Type: application/json; charset=utf-8',
            'Connection: keep-alive'
        );

        curl_setopt($this->_oCurlHandle, CURLOPT_CONNECTTIMEOUT, 20);
        curl_setopt($this->_oCurlHandle, CURLOPT_TIMEOUT, 30);

        if(self::DEBUG_MODE) {
            curl_setopt($this->_oCurlHandle, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($this->_oCurlHandle, CURLOPT_SSL_VERIFYHOST, false);
        } else {
            curl_setopt($this->_oCurlHandle, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($this->_oCurlHandle, CURLOPT_SSL_VERIFYHOST, false);
        }
        
        curl_setopt($this->_oCurlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);

        curl_setopt($this->_oCurlHandle, CURLOPT_HTTPHEADER, $this->_aHttpRequestHeaders);
		curl_setopt($this->_oCurlHandle, CURLOPT_POST, true);
        curl_setopt($this->_oCurlHandle, CURLOPT_POSTFIELDS, json_encode($this->_oHttpRequestBody, JSON_UNESCAPED_SLASHES));
        curl_setopt($this->_oCurlHandle, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($this->_oCurlHandle, CURLOPT_ENCODING, '');

        if(self::DEBUG_MODE) {
            $this->_curlLog = fopen('php://temp', 'w+');
            curl_setopt($this->_oCurlHandle, CURLOPT_VERBOSE, true);
            curl_setopt($this->_oCurlHandle, CURLOPT_STDERR, $this->_curlLog);
        }
    }


    /**
     * Destruct OVIS API client
     */
    public function __destruct() {

        /**
         * Close stream resource
         */
        if(is_resource($this->_curlLog)) {
            fclose($this->_curlLog);
        }

        /**
         * Close cURL session
         */
        curl_close($this->_oCurlHandle);
    }


    /**
     * Set the OVIS API base URL
     * 
     * @param string $sBaseUrl
     * @return void
     */
    public function setBaseUrl($sBaseUrl) {

        /**
         * Set API base URL
         */
        $this->_sApiBaseUrl = $sBaseUrl;


        /**
         * Set cURL option
         */
        curl_setopt(
            $this->_oCurlHandle, 
            CURLOPT_URL, 
            $this->_sApiBaseUrl
        );
    }


    /**
     * Use the production OVIS API
     * 
     * @return void
     */
    public function useProductionApi() {

        /**
         * Set API base URL
         */
        $this->_sApiBaseUrl = self::API_BASE_URLS['PROD'];


        /**
         * Set cURL option
         */
        curl_setopt(
            $this->_oCurlHandle, 
            CURLOPT_URL, 
            $this->_sApiBaseUrl
        );
    }


    /**
     * Use the development OVIS API
     * 
     * @return void
     */
    public function useDevelopmentApi() {

        /**
         * Set API base URL
         */
        $this->_sApiBaseUrl = self::API_BASE_URLS['DEV'];


        /**
         * Set cURL option
         */
        curl_setopt(
            $this->_oCurlHandle, 
            CURLOPT_URL, 
            $this->_sApiBaseUrl
        );
    }

    
    /**
     * Set the OVIS API authentication key
     * 
     * @param string $sAuthKey
     * @return void
     */
    public function setAuthKey($sAuthKey) {
        
        /**
         * Set authentication key property from given key
         */
        $this->_sApiAuthenticationKey = $sAuthKey;


        /**
         * Add auth key to HTTP request headers
         */
        array_push(
            $this->_aHttpRequestHeaders, 
            'Authentication: ' . $this->_sApiAuthenticationKey
        );
		
        /**
         * Add version to HTTP request headers
         */
		
	array_push(
            $this->_aHttpRequestHeaders, 
            'Version: ' . OVIS_VERSION
        );     
    
        /**
         * Attach HTTP request headers to the cURL session
         */
        curl_setopt(
            $this->_oCurlHandle, 
            CURLOPT_HTTPHEADER, 
            $this->_aHttpRequestHeaders
        );
    }


    /**
     * Set compact/simple output
     * 
     * @param bool|string $useCompactOutput
     * @return void
     */
    public function setCompactOutput($useCompactOutput) {
        $this->_bUseCompactOutput = Utility::normalizeBool($useCompactOutput);
    }


    /**
     * Set items per page
     * 
     * @param string $iItemsPerPage
     * @return void
     */
    public function setItemsPerPage($iItemsPerPage) {
        $this->_iItemsPerPage = $iItemsPerPage;
    }


    /**
     * Return info of current cURL session
     * 
     * @return mixed
     */
    public function getCurlInfo() {
        return curl_getinfo($this->_oCurlHandle);
    }


    /**
     * Return HTTP request body
     * 
     * @return object
     */
    public function getHttpRequestBody() {
        return $this->_oHttpRequestBody;
    }


    /**
     * Return HTTP request headers
     * 
     * @return array
     */
    public function getHttpRequestHeaders() {
        return $this->_aHttpRequestHeaders;
    }


    /**
     * Handle cURL error and return appropriate response object.
     *
     * @return object
     */
    private function handleCurlError() {

        $this->_oResponseObject->success = false;
        
        $this->_oResponseObject->errors = array(
            curl_getinfo(
                $this->_oCurlHandle, 
                CURLINFO_HTTP_CODE
            )
        );

        $this->_oResponseObject->result = array();

        return $this->_oResponseObject;
    }


    /**
     * Handle API errors and return appropriate response object.
     *
     * @param array $aErrors
     * @return object
     */
    private function handleApiErrors($aErrors) {
        
        $this->_oResponseObject->success = false;

        $this->_oResponseObject->errors = $aErrors;

        $this->_oResponseObject->result = array();

        return $this->_oResponseObject;
    }


    /**
     * Set OVIS API endpoint
     *
     * @param string $sApiEndpoint
     * @return void
     */
    private function updateApiEndpoint($sApiEndpoint) {

        $this->_sApiEndpoint = $sApiEndpoint;

        curl_setopt(
            $this->_oCurlHandle, 
            CURLOPT_URL, 
            $this->_sApiBaseUrl . $sApiEndpoint . '/'
        );
    }


    /**
     * Set HTTP request method
     *
     * @param string $sHttpRequestMethod
     * @return void
     */
    private function setHttpRequestMethod($sHttpRequestMethod) {
        
        if($sHttpRequestMethod != self::HTTP_METHODS['GET']) {
            curl_setopt(
                $this->_oCurlHandle, 
                CURLOPT_POST, 
                true
            );
        } else {
            curl_setopt(
                $this->_oCurlHandle, 
                CURLOPT_POST, 
                false
            );
        }

    }


    /**
     * Set HTTP request body
     *
     * @return void
     */
    private function setHttpRequestBody($oHttpRequestBody) {

        $this->_oHttpRequestBody = $oHttpRequestBody;

        curl_setopt(
            $this->_oCurlHandle, 
            CURLOPT_POSTFIELDS, 
            json_encode($this->_oHttpRequestBody, JSON_UNESCAPED_SLASHES)
        );
    }


    /**
     * Reset the HTTP request body
     *
     * @return void
     */
    private function resetHttpRequestBody() {

        $oHttpRequestBody = new \stdClass();
        
        /**
         * Cache
         */
        $oHttpRequestBody->useCache = true;
		
		
        /**
         * Sort
         */
        $oHttpRequestBody->order = $this->_aSort;


        /**
         * Realm
         */
        if(!empty($this->_sRealm)) {
            $oHttpRequestBody->realm = $this->_sRealm;
        } else {
            $oHttpRequestBody->realm = self::REALMS['OVIS'];
        }

        /**
         * Compact output
         */
        $oHttpRequestBody->simple = $this->_bUseCompactOutput;

        /**
         * Page
         */
        if(!empty($this->_iPage)) {
            $oHttpRequestBody->page = $this->_iPage;
        } else {
            $oHttpRequestBody->page = 1;
        }

        /**
         * Items per page
         */
        if(!empty($this->_iItemsPerPage)) {
            $oHttpRequestBody->itemsPerPage = $this->_iItemsPerPage;
        } else {
            $oHttpRequestBody->itemsPerPage = self::DEFAULT_ITEMS_PER_PAGE;
        }

        /**
         * Hide sold presentations
         */
        $oHttpRequestBody->hidesold = $this->_bHideSold;
        
        /**
         * Hide expected presentations
         */
        $oHttpRequestBody->hideexpected = $this->_bHideExpected;
        
        /**
         * Hide reserved presentations
         * 
         * * `OVIS` Realm only
         */
        if($this->_sRealm === self::REALMS['OVIS']) {
            $oHttpRequestBody->hidereserved = $this->_bHideReserved;
        }

        /**
         * Set image format
         */
        if(!empty($this->_sImageFormat)) {
            $oHttpRequestBody->imageformat = $this->_sImageFormat;
        } else {
            $oHttpRequestBody->imageformat = 'webp';
        }

        /**
         * Dealer
         */
        if(!empty($this->_aDealers)) {
            $oHttpRequestBody->userId = $this->_aDealers;
        }

        /**
         * New
         */
        $oHttpRequestBody->new = $this->_bIsConditionNew;

        /**
         * Occasion
         */
        $oHttpRequestBody->occasion = $this->_bIsConditionOccasion;
        
        /**
         * Rental
         * 
         * * `OVIS` Realm only
         */
        if($this->_sRealm === self::REALMS['OVIS']) {
            $oHttpRequestBody->rental = $this->_bIsRental;

            if($this->_bIsRental) {
                if(!empty($this->_sRentalDateFrom)) {
                    $oHttpRequestBody->rentalDateFrom = $this->_sRentalDateFrom;
                }
    
                if(!empty($this->_sRentalDateTo)) {
                    $oHttpRequestBody->rentalDateTo = $this->_sRentalDateTo;
                }
            }
        }

        /**
         * Status
         * 
         * * `OVIS` Realm only
         */
        if($this->_sRealm === self::REALMS['OVIS']) {
            if(!empty($this->_aStatus)) {
                $oHttpRequestBody->status = $this->_aStatus;
            }
        }

        /**
         * Available
         * 
         * * `OVIS` Realm only
         */
        if($this->_sRealm === self::REALMS['OVIS']) {
            if(!empty($this->_sAvailable)) {
                $oHttpRequestBody->available = $this->_sAvailable;
            }
        }

        /**
         * Category
         */
        if(!empty($this->_aSelectedCategories)) {
            $oHttpRequestBody->category = $this->_aSelectedCategories;
        } else {
            if($this->_sRealm === self::REALMS['BOAT']) {
                $oHttpRequestBody->category = self::ALL_BOAT_REALM_CATEGORIES;
            } else {
                $oHttpRequestBody->category = self::ALL_OVIS_REALM_CATEGORIES;
            }
        }

        /**
         * Subcategory
         */
        if(count($this->_aSelectedSubCategories) > 0) {
            $oHttpRequestBody->subCategory = $this->_aSelectedSubCategories;
        }

        /**
         * Brand
         */
        if(count($this->_aSelectedBrands) > 0) {
            $oHttpRequestBody->brand = $this->_aSelectedBrands;
        }

        /**
         * Model type version
         */
        if(!empty($this->_sModelTypeVersion)) {
            $oHttpRequestBody->modelTypeVersion = $this->_sModelTypeVersion;
        }

        /**
         * Division
         * 
         * * `OVIS` Realm only
         */
        if(!empty($this->_aDivisions)) {
            $oHttpRequestBody->division = $this->_aDivisions;
        }

        /**
         * Keyword
         */
        if(!empty($this->_sKeyword)) {
            $oHttpRequestBody->keyword = $this->_sKeyword;
        }

        /**
         * Fuel
         */
        if(!empty($this->_aFuel)) {
            $oHttpRequestBody->fuel = $this->_aFuel;
        }

        /**
         * Transmission
         * 
         * * `OVIS` Realm only
         */
        if($this->_sRealm === self::REALMS['OVIS']) {
            if(!empty($this->_aTransmission)) {
                $oHttpRequestBody->transmission = $this->_aTransmission[0];
            }
        }
        
        /**
         * Euroclass
         * 
         * * `OVIS` Realm only
         */
        if($this->_sRealm === self::REALMS['OVIS']) {
            if(!empty($this->_aEuroclass)) {
                $oHttpRequestBody->euroclass = $this->_aEuroclass;
            }
        }

        /**
         * Fast sailing
         * 
         * * `boat` Realm only
         */
        if($this->_sRealm === self::REALMS['BOAT']) {
            if(isset($this->_bIsFastSailing)) {
                $oHttpRequestBody->fastSailing = $this->_bIsFastSailing;
            }
        }

        /**
         * Tail length
         * 
         * * `boat` Realm only
         */
        if($this->_sRealm === self::REALMS['BOAT']) {
            if(!empty($this->_sTailLength)) {
                $oHttpRequestBody->tailLength = $this->_sTailLength;
            }
        }

        /**
         * Engine type
         * 
         * * `boat` Realm only
         */
        if($this->_sRealm === self::REALMS['BOAT']) {
            if(!empty($this->_sEngineType)) {
                $oHttpRequestBody->engineType = $this->_sEngineType;
            }
        }

        /**
         * Engine brand
         * 
         * * `boat` Realm only
         */
        if($this->_sRealm === self::REALMS['BOAT']) {
            if(!empty($this->_sEngineBrand)) {
                $oHttpRequestBody->engineBrand = $this->_sEngineBrand;
            }
        }

        /**
         * Price from
         */
        if(!empty($this->_fPriceFrom)) {
            $oHttpRequestBody->priceFrom = $this->_fPriceFrom;
        }

        /**
         * Price to
         */
        if(!empty($this->_fPriceTo)) {
            $oHttpRequestBody->priceTo = $this->_fPriceTo;
        }

        /**
         * Weight from
         */
        if(!empty($this->_iWeightFrom)) {
            $oHttpRequestBody->maxWeightFrom = $this->_iWeightFrom;
        }

        /**
         * Weight to
         */
        if(!empty($this->_iWeightTo)) {
            $oHttpRequestBody->maxWeightTo = $this->_iWeightTo;
        }

        /**
         * Length from
         */
        if(!empty($this->_iLengthFrom)) {
            $oHttpRequestBody->lengthFrom = $this->_iLengthFrom;
        }

        /**
         * Length to
         */
        if(!empty($this->_iLengthTo)) {
            $oHttpRequestBody->lengthTo = $this->_iLengthTo;
        }

        /**
         * Model year
         * 
         * * `OVIS` Realm only
         */
        if($this->_sRealm === self::REALMS['OVIS']) {

            /**
             * Model year from
             */
            if(!empty($this->_iModelYearFrom)) {
                $oHttpRequestBody->modelYearFrom = $this->_iModelYearFrom;
            }
    
            /**
             * Model year to
             */
            if(!empty($this->_iModelYearTo)) {
                $oHttpRequestBody->modelYearTo = $this->_iModelYearTo;
            }
        }

        /**
         * Construction year from
         */
        if(!empty($this->_iConstructionYearFrom)) {
            $oHttpRequestBody->constructionYearFrom = $this->_iConstructionYearFrom;
        }

        /**
         * Construction year to
         */
        if(!empty($this->_iConstructionYearTo)) {
            $oHttpRequestBody->constructionYearTo = $this->_iConstructionYearTo;
        }


        /**
         * Mileage
         * 
         * * `OVIS` Realm only
         */
        if($this->_sRealm === self::REALMS['OVIS']) {

            /**
             * Mileage from
             */
            if(!empty($this->_iMileageFrom)) {
                $oHttpRequestBody->mileageFrom = $this->_iMileageFrom;
            }

            /**
             * Mileage to
             */
            if(!empty($this->_iMileageTo)) {
                $oHttpRequestBody->mileageTo = $this->_iMileageTo;
            }
        }
        
        /**
         * Sleeping places from
         */
        if(!empty($this->_iSleepingPlacesFrom)) {
            $oHttpRequestBody->sleepingPlacesFrom = $this->_iSleepingPlacesFrom;
        }
        
        /**
         * Sleeping places to
         */
        if(!empty($this->_iSleepingPlacesTo)) {
            $oHttpRequestBody->sleepingPlacesTo = $this->_iSleepingPlacesTo;
        }
        
        /**
         * Seatbelt places
         * 
         * * `OVIS` Realm only
         */
        if($this->_sRealm === self::REALMS['OVIS']) {

            /**
             * Seatbelt places from
             */
            if(!empty($this->_iSeatbeltPlacesFrom)) {
                $oHttpRequestBody->seatbeltsFrom = $this->_iSeatbeltPlacesFrom;
            }
            
            /**
             * Seatbelt places to
             */
            if(!empty($this->_iSeatbeltPlacesTo)) {
                $oHttpRequestBody->seatbeltsTo = $this->_iSeatbeltPlacesTo;
            }
        }

        /**
         * Power
         * 
         * * `boat` Realm only
         */
        if($this->_sRealm === self::REALMS['BOAT']) {

            /**
             * Power from
             */
            if(!empty($this->_iPowerFrom)) {
                $oHttpRequestBody->powerFrom = $this->_iPowerFrom;
            }
    
            /**
             * Power to
             */
            if(!empty($this->_iPowerTo)) {
                $oHttpRequestBody->powerTo = $this->_iPowerTo;
            }
        }

        /**
         * Set HTTP request body
         */
        $this->_oHttpRequestBody = $oHttpRequestBody;
		
        /**
         * Pass to cURL session
         */
        curl_setopt(
            $this->_oCurlHandle, 
            CURLOPT_POSTFIELDS, 
            json_encode($this->_oHttpRequestBody, JSON_UNESCAPED_SLASHES)
        );
    }


    /**
     * Set hide sold presentations
     *
     * @param bool|string $hideSold
     * @return void
     */
    public function setHideSold($hideSold) {
        $this->_bHideSold = Utility::normalizeBool($hideSold);
    }


    /**
     * Set hide expected presentations
     *
     * @param bool|string $hideExpected
     * @return void
     */
    public function setHideExpected($hideExpected) {
        $this->_bHideExpected = Utility::normalizeBool($hideExpected);
    }


    /**
     * Set hide reserved presentations
     *
     * @param bool|string $hideReserved
     * @return void
     */
    public function setHideReserved($hideReserved) {
        $this->_bHideReserved = Utility::normalizeBool($hideReserved);
    }


    /**
     * Set fast sailing
     *
     * @param bool|string $isFastSailing
     * @return void
     */
    public function setFastSailing($isFastSailing) {
        $this->_bIsFastSailing = Utility::normalizeBool($isFastSailing);
    }


    /**
     * Set sorting - supports multiple sort fields
     *
     * @param string|array $sort
     * Format examples:
     * - String: "prijs,asc,bouwjaar,desc"
     * - Array: ['prijs', 'asc', 'bouwjaar', 'desc']
     * - Nested array: [['prijs', 'asc'], ['bouwjaar', 'desc']]
     * 
     * Note: String input is split on spaces, underscores, AND commas
     * 
     * @return void
     */
    public function setSorting($sort) {

        if(!is_string($sort) && !is_array($sort)) return;

        if(is_string($sort)) {
            $sort = preg_split('/[\s_,]+/', $sort);
        }

        if(empty($sort)) return;

        $aAvailableOptions = array(
            'categorie' => 'category',
            'merk' => 'brand',
            'prijs'  => 'price',
            'bouwjaar' => 'constructionYear',
            'gewicht' => 'maxWeight',
            'slaapplaatsen' => 'sleepingPlaces',
            'aanmaakdatum' => 'creationDate',
            'bewerkdatum' => 'updateDate',
            'verkocht' => 'sold',
            'willekeurig' => 'random'
        );
        
        $isNested = is_array($sort[0]);

        if($isNested) {
            $sortPairs = $sort; // already paired
        } else {
            if(count($sort) < 2) return; // needs atleast two items for a pair
            
            $sortPairs = array();
            for($i = 0; $i < count($sort); $i += 2) { // create pairs
                if(isset($sort[$i + 1])) {
                    $sortPairs[] = array($sort[$i], $sort[$i + 1]);
                }
            }
        }

        if(empty($sortPairs)) return;

        $this->_aSort = array();
        $hasPriceField = false;

        foreach($sortPairs as $pair) {
            if(!is_array($pair) || count($pair) < 2) continue;

            $field = $pair[0];
            $direction = $pair[1];

            $aValidatedSortFields = Utility::validateAndNormalizeOptions($field, $aAvailableOptions);
            
            if(empty($aValidatedSortFields)) continue;

            $sortField = $aValidatedSortFields[0];

            if($sortField === 'random') {
                $this->_aSort = array(
                    (object)array(
                        'field' => 'random'
                    )
                );
                return;
            }

            if($sortField === 'price') {
                $hasPriceField = true;
            }

            $isDescending = true;
            if(in_array(strtolower($direction), array('asc', 'up', 'oplopend', 'ascending', 'laag-hoog'))) {
                $isDescending = false;
            }

            array_push(
                $this->_aSort, 
                (object)array(
                    'field' => $sortField, 
                    'descending' => $isDescending
                )
            );
        }

        if($hasPriceField) {
            $hasSoldAscending = false;
            foreach($this->_aSort as $sortItem) {
                if($sortItem->field === 'sold' && $sortItem->descending === false) {
                    $hasSoldAscending = true;
                    break;
                }
            }

            if(!$hasSoldAscending) {
                array_unshift(
                    $this->_aSort,
                    (object)array(
                        'field' => 'sold',
                        'descending' => false
                    )
                );
            }
        }
    }


    /**
     * Set page
     * 
     * @param int $iPage
     * @return void
     */
    public function setPage($iPage) {
        $this->_iPage = $iPage;
    }


    /**
     * Set image format
     * 
     * @param string $sImageFormat
     * @return void
     */
    public function setImageFormat($sImageFormat) {
        $this->_sImageFormat = $sImageFormat;
    }


    /**
     * Set OVIS realm
     * 
     * @param string $sRealm
     * @return void
     * 
     * The realm can be set by one of the following values:
     *  * 'ovis'    - OVIS realm (default)
     *  * 'boat'    - Boat realm
     *  * ''        - OVIS realm (default)
     */
    public function setRealm($sRealm = self::REALMS['OVIS']) {

        /**
         * Store all available options
         */
        $aAvailableOptions = array(
            'ovis' => self::REALMS['OVIS'],
            'boat' => self::REALMS['BOAT'],
            'boats'  => self::REALMS['BOAT'],
            'boot'  => self::REALMS['BOAT'],
            'boten'  => self::REALMS['BOAT']
        );

        $aValidatedOptions = Utility::validateAndNormalizeOptions($sRealm, $aAvailableOptions);

        if(count($aValidatedOptions) > 0) {
            $this->_sRealm = $aValidatedOptions[0];
        }
    }


    /**
     * Set dealer(s)
     * 
     * @param string|array $dealers
     * @return void
     */
    public function setDealers($dealers) {
        $this->_aDealers = Utility::normalizeArray($dealers);
    }


    /**
     * Set rental
     * 
     * @param bool|string $isRental
     * @return void
     */
    public function setRental($isRental = false) {
        $this->_bIsRental = Utility::normalizeBool($isRental);
    }


    /**
     * Set status
     * 
     * @param string|array $status
     * @return void
     */
    public function setStatus($status) {

        /**
         * Store all available options
         */
        $aAvailableOptions = array(
            'verkocht' => 'sold',
            'verwacht' => 'expected',
            'gereserveerd'  => 'reserved'
        );

        $aValidatedStatus = Utility::validateAndNormalizeOptions($status, $aAvailableOptions);

        $this->_aStatus = $aValidatedStatus;
    }


    /**
     * Set tail length
     * 
     * @param string $sTailLength
     * @return void
     */
    public function setTailLength($sTailLength) {
        
        /**
         * Store all available options
         */
        $aAvailableOptions = array(
            'kortstaart' => '',
            'langstaart' => '',
            'extra langstaart'  => ''
        );

        $aValidatedOptions = Utility::validateAndNormalizeOptions($sTailLength, $aAvailableOptions);

        if(count($aValidatedOptions) > 0) {
            $this->_sTailLength = $aValidatedOptions[0];
        }
    }


    /**
     * Set engine type
     * 
     * @param string $sEngineType
     * @return void
     */
    public function setEngineType($sEngineType) {
        $this->_sEngineType = trim(strtolower($sEngineType));
    }


    /**
     * Set engine brand
     * 
     * @param string $sEngineBrand
     * @return void
     */
    public function setEngineBrand($sEngineBrand) {
        $this->_sEngineBrand = trim(strtolower($sEngineBrand));
    }


    /**
     * Set available
     * 
     * @param string $sAvailable
     * @return void
     */
    public function setAvailable($sAvailable) {
        
        /**
         * Store all available options
         */
        $aAvailableOptions = array(
            'inbegrepen' => 'include',
            'uitsluiten' => 'exclude',
            'isoleren'  => 'only'
        );

        $aValidatedOptions = Utility::validateAndNormalizeOptions($sAvailable, $aAvailableOptions);

        if(count($aValidatedOptions) > 0) {
            $this->_sAvailable = $aValidatedOptions[0];
        }
    }


    /**
     * Set division(s)
     * 
     * @param string|array $division
     * @return void
     */
    public function setDivision($division) {
        $this->_aDivisions = Utility::normalizeArray($division);
    }


    /**
     * Set categories
     * 
     * @param string|array $categories
     * @return void
     * 
     * Possible categories
     *  * camper
     *  * caravan
     *  * mobilehome
     *  * trailer
     *  * tenttrailer
     * 
     * The condition can be set by one of the following values:
     *  * 'all'                    - All categories
     *  * []                       - All categories
     *  * ''                       - All categories
     *  * ['camper']               - Campers only
     *  * 'camper'                 - Campers only
     *  * ['camper', 'caravan']    - Campers & caravans only
     *  * 'camper,caravan'         - Campers & caravans only
     */
    public function setCategory($categories) {

        if( is_array($categories) ) {
            if( count($categories) > 0 ) {
                $this->_aSelectedCategories = $categories;
            } 
            else {
                if($this->_sRealm === self::REALMS['BOAT']) {
                    $this->_aSelectedCategories = self::ALL_BOAT_REALM_CATEGORIES;
                } else {
                    $this->_aSelectedCategories = self::ALL_OVIS_REALM_CATEGORIES;
                }
            }
        } 
        else {
            if( strtolower($categories) != 'all' ) { 
                $this->_aSelectedCategories = explode(',', $categories);
            } 
            else { 
                if($this->_sRealm === self::REALMS['BOAT']) {
                    $this->_aSelectedCategories = self::ALL_BOAT_REALM_CATEGORIES;
                } else {
                    $this->_aSelectedCategories = self::ALL_OVIS_REALM_CATEGORIES;
                }
            }
        }
    }

    
    /**
     * Set subcategories
     * 
     * @param string|array $subCategories
     * @return void
     */
    public function setSubCategory($subCategories) {

        $aNormalizedSubCategories = Utility::normalizeArray($subCategories);

        $this->_aSelectedSubCategories = in_array('all', $aNormalizedSubCategories) ? array() : $aNormalizedSubCategories;
    }

    
    /**
     * Set condition
     * 
     * @param string $sCondition
     * @return void
     * 
     * The condition can be set by one of the following values:
     *  * 'all'         - All conditions
     *  * 'new'         - New only
     *  * 'nieuw'       - New only
     *  * 'occasion'    - Occasion only
     *  * 'gebruikt'    - Occasion only
     *  * ''            - All conditions
     * 
     * Related methods:
     *  * setConditionAll()
     *  * setConditionNew()
     *  * setConditionOccasion()
     */
    public function setCondition($sCondition) {

        $sCleanedParameter = trim(strtolower($sCondition));

        if( $sCleanedParameter === 'all' ) {
            $this->_bIsConditionNew = true;
            $this->_bIsConditionOccasion = true;
        } 
        else {
            if( $sCleanedParameter === 'new' || $sCleanedParameter === 'nieuw' ) {
                $this->_bIsConditionNew = true;
            } else {
                $this->_bIsConditionNew = false;
            }
    
            if( $sCleanedParameter === 'occasion' || $sCleanedParameter === 'gebruikt' ) {
                $this->_bIsConditionOccasion = true;
            } else {
                $this->_bIsConditionOccasion = false;
            }
        }
    }


    /**
     * Set condition to **All**
     * 
     * @return void
     */
    public function setConditionAll() {
        $this->_bIsConditionNew = true;
        $this->_bIsConditionOccasion = true;
    }


    /**
     * Set condition to **New**
     * 
     * @return void
     */
    public function setConditionNew() {
        $this->_bIsConditionNew = true;
        $this->_bIsConditionOccasion = false;
    }
    

    /**
     * Set condition to **Occasion**
     * 
     * @return void
     */
    public function setConditionOccasion() {
        $this->_bIsConditionOccasion = true;
        $this->_bIsConditionNew = false;
    }


    /**
     * Set model type version
     * 
     * @param string $sModelTypeVersion
     * @return void
     */
    public function setModelTypeVersion($sModelTypeVersion) {
        if(empty($sModelTypeVersion)) return;
        if(!is_string($sModelTypeVersion)) return;
        $this->_sModelTypeVersion = $sModelTypeVersion;
    }


    /**
     * Set keyword
     * 
     * @param string $sKeyword
     * @return void
     */
    public function setKeyword($sKeyword) {
        if(empty($sKeyword)) return;
        if(!is_string($sKeyword)) return;
        $this->_sKeyword = trim($sKeyword);
    }


    /**
     * Set price from
     * 
     * @param float $fPriceFrom
     * @return void
     */
    public function setPriceFrom($fPriceFrom) {
        if($fPriceFrom === null || $fPriceFrom === '') return;
        if(!is_numeric($fPriceFrom)) return;
        $this->_fPriceFrom = floatval($fPriceFrom);
    }


    /**
     * Set price to
     * 
     * @param float $fPriceTo
     * @return void
     */
    public function setPriceTo($fPriceTo) {
        if($fPriceTo === null || $fPriceTo === '') return;
        if(!is_numeric($fPriceTo)) return;
        $this->_fPriceTo = floatval($fPriceTo);
    }


    /**
     * Set weight from
     * 
     * @param int $iWeightFrom
     * @return void
     */
    public function setWeightFrom($iWeightFrom) {
        if($iWeightFrom === null || $iWeightFrom === '') return;
        if(!is_numeric($iWeightFrom)) return;
        $this->_iWeightFrom = (int)$iWeightFrom;
    }


    /**
     * Set weight to
     * 
     * @param int $iWeightTo
     * @return void
     */
    public function setWeightTo($iWeightTo) {
        if($iWeightTo === null || $iWeightTo === '') return;
        if(!is_numeric($iWeightTo)) return;
        $this->_iWeightTo = (int)$iWeightTo;
    }

    
    /**
     * Set length from
     * 
     * @param int $iLengthFrom
     * @return void
     */
    public function setLengthFrom($iLengthFrom) {
        if($iLengthFrom === null || $iLengthFrom === '') return;
        if(!is_numeric($iLengthFrom)) return;
        $this->_iLengthFrom = (int)$iLengthFrom;
    }


    /**
     * Set length to
     * 
     * @param int $iLengthTo
     * @return void
     */
    public function setLengthTo($iLengthTo) {
        if($iLengthTo === null || $iLengthTo === '') return;
        if(!is_numeric($iLengthTo)) return;
        $this->_iLengthTo = (int)$iLengthTo;
    }


    /**
     * Set width from
     * 
     * @param int $iWidthFrom
     * @return void
     */
    public function setWidthFrom($iWidthFrom) {
        if($iWidthFrom === null || $iWidthFrom === '') return;
        if(!is_numeric($iWidthFrom)) return;
        $this->_iWidthFrom = (int)$iWidthFrom;
    }


    /**
     * Set width to
     * 
     * @param int $iWidthTo
     * @return void
     */
    public function setWidthTo($iWidthTo) {
        if($iWidthTo === null || $iWidthTo === '') return;
        if(!is_numeric($iWidthTo)) return;
        $this->_iWidthTo = (int)$iWidthTo;
    }


    /**
     * Set mileage from
     * 
     * @param int $iMileageFrom
     * @return void
     */
    public function setMileageFrom($iMileageFrom) {
        if($iMileageFrom === null || $iMileageFrom === '') return;
        if(!is_numeric($iMileageFrom)) return;
        $this->_iMileageFrom = (int)$iMileageFrom;
    }


    /**
     * Set mileage to
     * 
     * @param int $iMileageTo
     * @return void
     */
    public function setMileageTo($iMileageTo) {
        if($iMileageTo === null || $iMileageTo === '') return;
        if(!is_numeric($iMileageTo)) return;
        $this->_iMileageTo = (int)$iMileageTo;
    }


    /**
     * Set power from
     * 
     * @param int $iPowerFrom
     * @return void
     */
    public function setPowerFrom($iPowerFrom) {
        if($iPowerFrom === null || $iPowerFrom === '') return;
        if(!is_numeric($iPowerFrom)) return;
        $this->_iPowerFrom = (int)$iPowerFrom;
    }


    /**
     * Set power to
     * 
     * @param int $iPowerTo
     * @return void
     */
    public function setPowerTo($iPowerTo) {
        if($iPowerTo === null || $iPowerTo === '') return;
        if(!is_numeric($iPowerTo)) return;
        $this->_iPowerTo = (int)$iPowerTo;
    }


    /**
     * Set rental date from
     * 
     * @param string $sRentalDateFrom
     * @return void
     */
    public function setRentalDateFrom($sRentalDateFrom) {
        if(!is_string($sRentalDateFrom) || trim($sRentalDateFrom) === '') return;

        try {
            $date = new DateTime($sRentalDateFrom);
            $this->_sRentalDateFrom = $date->format('Y-m-d H:i:s');
        } catch (Exception $e) {
            // Invalid date string
        }
    }


    /**
     * Set rental date to
     * 
     * @param string $sRentalDateTo
     * @return void
     */
    public function setRentalDateTo($sRentalDateTo) {
        if(!is_string($sRentalDateTo) || trim($sRentalDateTo) === '') return;

        try {
            $date = new DateTime($sRentalDateTo);
            $this->_sRentalDateTo = $date->format('Y-m-d H:i:s');
        } catch (Exception $e) {
            // Invalid date string
        }
    }


    /**
     * Set model year from
     * 
     * @param int $iModelYearFrom
     * @return void
     */
    public function setModelYearFrom($iModelYearFrom) {
        if($iModelYearFrom === null || $iModelYearFrom === '') return;
        if(!is_numeric($iModelYearFrom)) return;
        $this->_iModelYearFrom = (int)$iModelYearFrom;
    }


    /**
     * Set model year to
     * 
     * @param int $iModelYearTo
     * @return void
     */
    public function setModelYearTo($iModelYearTo) {
        if($iModelYearTo === null || $iModelYearTo === '') return;
        if(!is_numeric($iModelYearTo)) return;
        $this->_iModelYearTo = (int)$iModelYearTo;
    }


    /**
     * Set construction year from
     * 
     * @param int $iConstructionYearFrom
     * @return void
     */
    public function setConstructionYearFrom($iConstructionYearFrom) {
        if($iConstructionYearFrom === null || $iConstructionYearFrom === '') return;
        if(!is_numeric($iConstructionYearFrom)) return;
        $this->_iConstructionYearFrom = (int)$iConstructionYearFrom;
    }


    /**
     * Set construction year to
     * 
     * @param int $iConstructionYearTo
     * @return void
     */
    public function setConstructionYearTo($iConstructionYearTo) {
        if($iConstructionYearTo === null || $iConstructionYearTo === '') return;
        if(!is_numeric($iConstructionYearTo)) return;
        $this->_iConstructionYearTo = (int)$iConstructionYearTo;
    }


    /**
     * Set sleeping places from
     * 
     * @param int $iSleepingPlacesFrom
     * @return void
     */
    public function setSleepingPlacesFrom($iSleepingPlacesFrom) {
        if($iSleepingPlacesFrom === null || $iSleepingPlacesFrom === '') return;
        if(!is_numeric($iSleepingPlacesFrom)) return;
        $this->_iSleepingPlacesFrom = (int)$iSleepingPlacesFrom;
    }


    /**
     * Set sleeping places to
     * 
     * @param int $iSleepingPlacesTo
     * @return void
     */
    public function setSleepingPlacesTo($iSleepingPlacesTo) {
        if($iSleepingPlacesTo === null || $iSleepingPlacesTo === '') return;
        if(!is_numeric($iSleepingPlacesTo)) return;
        $this->_iSleepingPlacesTo = (int)$iSleepingPlacesTo;
    }


    /**
     * Set seatbelt places from
     * 
     * @param int $iSeatbeltPlacesFrom
     * @return void
     */
    public function setSeatbeltPlacesFrom($iSeatbeltPlacesFrom) {
        if($iSeatbeltPlacesFrom === null || $iSeatbeltPlacesFrom === '') return;
        if(!is_numeric($iSeatbeltPlacesFrom)) return;
        $this->_iSeatbeltPlacesFrom = (int)$iSeatbeltPlacesFrom;
    }


    /**
     * Set seatbelt places to
     * 
     * @param int $iSeatbeltPlacesTo
     * @return void
     */
    public function setSeatbeltPlacesTo($iSeatbeltPlacesTo) {
        if($iSeatbeltPlacesTo === null || $iSeatbeltPlacesTo === '') return;
        if(!is_numeric($iSeatbeltPlacesTo)) return;
        $this->_iSeatbeltPlacesTo = (int)$iSeatbeltPlacesTo;
    }


    /**
     * Set brand(s)
     * 
     * @param string|array $brand
     * @return void
     */
    public function setBrand($brand) {
        $this->_aSelectedBrands = Utility::normalizeArray($brand);
    }


    /**
     * Set fuel(s)
     * 
     * @param string|array $fuel
     * @return void
     */
    public function setSelecteFuelTypes($fuel) {
        $this->_aFuel = Utility::normalizeArray($fuel);
    }


    /**
     * Set transmission(s)
     * 
     * @param string|array $transmission
     * @return void
     */
    public function setSelectedTransmissionTypes($transmission) {
        $this->_aTransmission = Utility::normalizeArray($transmission);
    }


    /**
     * Set euroclass(es)
     * 
     * @param string|array $euroclass
     * @return void
     */
    public function setEuroclass($euroclass) {
        $this->_aEuroclass = Utility::normalizeArray($euroclass);
    }


    /**
     * Retrieve current Realm
     *
     * @return string
     */
    public function getRealm() {
        return $this->_sRealm;
    }


    /**
     * Fetch list of minimum and maximum values available from OVIS API
     * 
     * @return object|null
     * 
     * @link https://docs.ovis.nl/api/rest/searchengine/site/searchminmaxvalues/
     */
    public function fetchMinMax() {
        
        $sVerboseLog = '';

        if(self::DEBUG_MODE) {
            $startMicroTime = microtime(true);
        }

        $oHttpRequestBody = new \stdClass;
        $oHttpRequestBody->selection = array('clean', 'sold', 'noimage');

        $this->updateApiEndpoint(self::API_ENDPOINTS['SEARCH_MIN_MAX'] . $this->_sRealm . '/');
        $this->setHttpRequestMethod(self::HTTP_METHODS['POST']);
        $this->setHttpRequestBody($oHttpRequestBody);

        $sHttpResponse = curl_exec($this->_oCurlHandle);

        if(self::DEBUG_MODE) {
            rewind($this->_curlLog);
            $sVerboseLog = stream_get_contents($this->_curlLog);
        }

        if(self::DEBUG_MODE) {
            $oDebugFields = new \stdClass();
            $oDebugFields->function_ref = 'fetchMinMax';
            $oDebugFields->http_request_headers = $this->getHttpRequestHeaders();
            $oDebugFields->http_request_body = $this->getHttpRequestBody();
            $oDebugFields->curl_info = $this->getCurlInfo();
            $oDebugFields->function_execution_microtime = (microtime(true) - $startMicroTime);
        }

        if(curl_errno($this->_oCurlHandle)) return null;

        return !empty($sHttpResponse) ? json_decode($sHttpResponse) : null;
    }


    /**
     * Fetch list of minimum and maximum values available for this searchengine-client from OVIS API
     * 
     * @return object|null
     * 
     * @link https://docs.ovis.nl/api/rest/searchengine/site/searchminmaxvalues/
     */
    public function fetchMinMaxClient() {
        
        $sVerboseLog = '';

        if(self::DEBUG_MODE) {
            $startMicroTime = microtime(true);
        }

        $oHttpRequestBody = new \stdClass;
        $oHttpRequestBody->selection = array('clean', 'sold', 'noimage');
        $oHttpRequestBody->userId = $this->_aDealers;

        $this->updateApiEndpoint(self::API_ENDPOINTS['SEARCH_MIN_MAX_CLIENT'] . $this->_sRealm . '/');
        $this->setHttpRequestMethod(self::HTTP_METHODS['POST']);
        $this->setHttpRequestBody($oHttpRequestBody);

        $sHttpResponse = curl_exec($this->_oCurlHandle);

        if(self::DEBUG_MODE) {
            rewind($this->_curlLog);
            $sVerboseLog = stream_get_contents($this->_curlLog);
        }

        if(self::DEBUG_MODE) {
            $oDebugFields = new \stdClass();
            $oDebugFields->function_ref = 'fetchMinMaxClient';
            $oDebugFields->http_request_headers = $this->getHttpRequestHeaders();
            $oDebugFields->http_request_body = $this->getHttpRequestBody();
            $oDebugFields->curl_info = $this->getCurlInfo();
            $oDebugFields->function_execution_microtime = (microtime(true) - $startMicroTime);
        }

        if(curl_errno($this->_oCurlHandle)) return null;

        return !empty($sHttpResponse) ? json_decode($sHttpResponse) : null;
    }


    /**
     * Fetch single presentation by ID from OVIS API
     * 
     * @param int $iPresentationId
     * @return object|null
     */
    public function fetchPresentation($iPresentationId) {

        $sVerboseLog = '';

        if(self::DEBUG_MODE) {
            $startMicroTime = microtime(true);
        }

        $oHttpRequestBody = new \stdClass();
        $oHttpRequestBody->presentationId = array((int)$iPresentationId);

        $this->updateApiEndpoint(self::API_ENDPOINTS['SEARCH']);
        $this->setHttpRequestMethod(self::HTTP_METHODS['POST']);
        $this->setHttpRequestBody($oHttpRequestBody);

        $sHttpResponse = curl_exec($this->_oCurlHandle);

        if(self::DEBUG_MODE) {
            rewind($this->_curlLog);
            $sVerboseLog = stream_get_contents($this->_curlLog);
        }

        if(self::DEBUG_MODE) {
            $oDebugFields = new \stdClass();
            $oDebugFields->function_ref = 'fetchPresentation';
            $oDebugFields->http_request_headers = $this->getHttpRequestHeaders();
            $oDebugFields->http_request_body = $this->getHttpRequestBody();
            $oDebugFields->curl_info = $this->getCurlInfo();
            $oDebugFields->function_execution_microtime = (microtime(true) - $startMicroTime);
        }

        if(curl_errno($this->_oCurlHandle)) return null;

        return !empty($sHttpResponse) ? json_decode($sHttpResponse) : null;
    }


    /**
     * Fetch presentations from OVIS API
     * 
     * @return object|null
     */
    public function fetchPresentations() {

        $sVerboseLog = '';
        
        if(self::DEBUG_MODE) {
            $startMicroTime = microtime(true);
        }

        $this->updateApiEndpoint(self::API_ENDPOINTS['SEARCH']);
        $this->setHttpRequestMethod(self::HTTP_METHODS['POST']);
        $this->resetHttpRequestBody();
        
        $sHttpResponse = curl_exec($this->_oCurlHandle);
		
        if(self::DEBUG_MODE) {
            rewind($this->_curlLog);
            $sVerboseLog = stream_get_contents($this->_curlLog);
        }

        if(self::DEBUG_MODE) {
            $oDebugFields = new \stdClass();
            $oDebugFields->function_ref = 'fetchPresentations';
            $oDebugFields->http_request_headers = $this->getHttpRequestHeaders();
            $oDebugFields->http_request_body = $this->getHttpRequestBody();
            $oDebugFields->curl_info = $this->getCurlInfo();
            $oDebugFields->function_execution_microtime = (microtime(true) - $startMicroTime);
        }

        if(curl_errno($this->_oCurlHandle)) return null;

        return !empty($sHttpResponse) ? json_decode($sHttpResponse) : null;
    }


    /**
     * Fetch all the unavailable rental dates for given rental presentation from OVIS API
     * 
     * @param int $iRentalPresentationId
     * @return array|null
     */
    public function fetchUnavailableRentalDates($iRentalPresentationId) {
        
        $sVerboseLog = '';

        if(self::DEBUG_MODE) {
            $startMicroTime = microtime(true);
        }

        $this->setHttpRequestMethod(self::HTTP_METHODS['GET']);
        $this->updateApiEndpoint(self::API_ENDPOINTS['SEARCH_RENTAL_STATUS'] . $iRentalPresentationId);

        $sHttpResponse = curl_exec($this->_oCurlHandle);

        if(self::DEBUG_MODE) {
            rewind($this->_curlLog);
            $sVerboseLog = stream_get_contents($this->_curlLog);
        }

        if(self::DEBUG_MODE) {
            $oDebugFields = new \stdClass();
            $oDebugFields->function_ref = 'fetchUnavailableRentalDates';
            $oDebugFields->http_request_headers = $this->getHttpRequestHeaders();
            $oDebugFields->http_request_body = $this->getHttpRequestBody();
            $oDebugFields->curl_info = $this->getCurlInfo();
            $oDebugFields->function_execution_microtime = (microtime(true) - $startMicroTime);
        }

        if(curl_errno($this->_oCurlHandle)) return null;

        return !empty($sHttpResponse) ? json_decode($sHttpResponse) : null;
    }


    /**
     * Fetch translations from OVIS API
     * 
     * @return object|null
     * 
     * @link https://docs.ovis.nl/api/rest/searchengine/site/translations/
     */
    public function fetchTranslations() {
        
        if(self::DEBUG_MODE) {
            $startMicroTime = microtime(true);
        }

        $this->updateApiEndpoint(self::API_ENDPOINTS['SEARCH_TRANSLATIONS']);
        $this->setHttpRequestMethod(self::HTTP_METHODS['GET']);

        $sHttpResponse = curl_exec($this->_oCurlHandle);

        if(self::DEBUG_MODE) {
            rewind($this->_curlLog);
            $sVerboseLog = stream_get_contents($this->_curlLog);
        }

        if(self::DEBUG_MODE) {
            $oDebugFields = new \stdClass();
            $oDebugFields->function_ref = 'fetchTranslations';
            $oDebugFields->http_request_headers = $this->getHttpRequestHeaders();
            $oDebugFields->http_request_body = $this->getHttpRequestBody();
            $oDebugFields->curl_info = $this->getCurlInfo();
            $oDebugFields->function_execution_microtime = (microtime(true) - $startMicroTime);
        }

        if(curl_errno($this->_oCurlHandle)) return null;

        return !empty($sHttpResponse) ? json_decode($sHttpResponse) : null;
    }


    /**
     * Fetch categories from OVIS API
     * 
     * @return object|null
     * 
     * @link https://docs.ovis.nl/api/rest/searchengine/site/searchcategories/
     */
    public function fetchCategories() {

        $sVerboseLog = '';
        
        if(self::DEBUG_MODE) {
            $startMicroTime = microtime(true);
        }

        $this->updateApiEndpoint(self::API_ENDPOINTS['SEARCH_CATEGORIES'] . $this->_sRealm . '/');
        $this->setHttpRequestMethod(self::HTTP_METHODS['GET']);

        $sHttpResponse = curl_exec($this->_oCurlHandle);

        if(self::DEBUG_MODE) {
            rewind($this->_curlLog);
            $sVerboseLog = stream_get_contents($this->_curlLog);
        }

        if(self::DEBUG_MODE) {
            $oDebugFields = new \stdClass();
            $oDebugFields->function_ref = 'fetchCategories';
            $oDebugFields->http_request_headers = $this->getHttpRequestHeaders();
            $oDebugFields->http_request_body = $this->getHttpRequestBody();
            $oDebugFields->curl_info = $this->getCurlInfo();
            $oDebugFields->function_execution_microtime = (microtime(true) - $startMicroTime);
        }

        if(curl_errno($this->_oCurlHandle)) return null;

        return !empty($sHttpResponse) ? json_decode($sHttpResponse) : null;
    }


    /**
     * Fetch divisions from OVIS API
     * 
     * @return object|null
     * 
     * @link https://docs.ovis.nl/api/rest/searchengine/site/searchdivisions/
     */
    public function fetchDivisions() {

        $sVerboseLog = '';
        
        if(self::DEBUG_MODE) {
            $startMicroTime = microtime(true);
        }

        $this->updateApiEndpoint(self::API_ENDPOINTS['SEARCH_DIVISIONS'] . $this->_sRealm . '/');
        $this->setHttpRequestMethod(self::HTTP_METHODS['GET']);

        $sHttpResponse = curl_exec($this->_oCurlHandle);

        if(self::DEBUG_MODE) {
            rewind($this->_curlLog);
            $sVerboseLog = stream_get_contents($this->_curlLog);
        }

        if(self::DEBUG_MODE) {
            $oDebugFields = new \stdClass();
            $oDebugFields->function_ref = 'fetchDivisions';
            $oDebugFields->http_request_headers = $this->getHttpRequestHeaders();
            $oDebugFields->http_request_body = $this->getHttpRequestBody();
            $oDebugFields->curl_info = $this->getCurlInfo();
            $oDebugFields->function_execution_microtime = (microtime(true) - $startMicroTime);
        }

        if(curl_errno($this->_oCurlHandle)) return null;

        return !empty($sHttpResponse) ? json_decode($sHttpResponse) : null;
    }


    /**
     * Fetch users from OVIS API
     * 
     * @return object|null
     * 
     * @link https://docs.ovis.nl/api/rest/searchengine/site/searchusers/
     */
    public function fetchUsers() {

        $sVerboseLog = '';
        
        if(self::DEBUG_MODE) {
            $startMicroTime = microtime(true);
        }
        
        $oHttpRequestBody = new \stdClass();
        $oHttpRequestBody->itemsPerPage = 100;
        $oHttpRequestBody->realm = $this->_sRealm;

        $this->updateApiEndpoint(self::API_ENDPOINTS['SEARCH_USERS']);
        $this->setHttpRequestMethod(self::HTTP_METHODS['POST']);
        $this->setHttpRequestBody($oHttpRequestBody);

        $sHttpResponse = curl_exec($this->_oCurlHandle);

        if(self::DEBUG_MODE) {
            rewind($this->_curlLog);
            $sVerboseLog = stream_get_contents($this->_curlLog);
        }

        if(self::DEBUG_MODE) {
            $oDebugFields = new \stdClass();
            $oDebugFields->function_ref = 'fetchUsers';
            $oDebugFields->http_request_headers = $this->getHttpRequestHeaders();
            $oDebugFields->http_request_body = $this->getHttpRequestBody();
            $oDebugFields->curl_info = $this->getCurlInfo();
            $oDebugFields->function_execution_microtime = (microtime(true) - $startMicroTime);
        }

        if(curl_errno($this->_oCurlHandle)) return null;

        return !empty($sHttpResponse) ? json_decode($sHttpResponse) : null;
    }


    /**
     * Contact dealer
     *
     * @param object $oFormFields
     * @return object
     */
    public function contactDealer($oFormFields) {

        $sVerboseLog = '';
        
        if(self::DEBUG_MODE) {
            $startMicroTime = microtime(true);
        }

        $oResponse = new \stdClass();
        $oResponse->success = false;
        $oResponse->errors = array();

        foreach($oFormFields as $key => $value) {
            if(empty($value) && $key != 'message') {
                array_push($oResponse->errors, $key);
            }
        };

        if(count($oResponse->errors)) return $oResponse;

        $oHttpRequestBody = new \stdClass();
        $oHttpRequestBody->presentationId = $oFormFields->presentationId;
        $oHttpRequestBody->name = $oFormFields->name;
        $oHttpRequestBody->email = $oFormFields->email;
        $oHttpRequestBody->phoneNumber = $oFormFields->phone;
        $oHttpRequestBody->message = $oFormFields->message;
        $oHttpRequestBody->date = date('Y-m-d');
        $date = new DateTime('now', new DateTimeZone('Europe/Berlin'));
        $oHttpRequestBody->time = $date->format('H:i:s');
        $oHttpRequestBody->detailURL = $oFormFields->url;

        $oUtm = isset($oFormFields->utm) ? $oFormFields->utm : null;

        if(!empty($oUtm)) {
            // $oHttpRequestBody->utmSource = !empty($oUtm->source) ? trim($oUtm->source) : 'ovis-wp-plugin';
            $oHttpRequestBody->utmCampaign = strlen(trim($oUtm->campaign)) > 50 ? substr(trim($oUtm->campaign), 0, 50) : trim($oUtm->campaign);
            // $oHttpRequestBody->utmContent = !empty($oUtm->content) ? trim($oUtm->content) : 'contact';
        }

        $this->updateApiEndpoint(self::API_ENDPOINTS['SEARCH_CONTACT_DEALER']);
        $this->setHttpRequestMethod(self::HTTP_METHODS['POST']);
        $this->setHttpRequestBody($oHttpRequestBody);
		
        $sHttpResponse = curl_exec($this->_oCurlHandle);
        
        if(self::DEBUG_MODE) {
            rewind($this->_curlLog);
            $sVerboseLog = stream_get_contents($this->_curlLog);
        }

        if(curl_errno($this->_oCurlHandle)) return $oResponse;
        if(empty($sHttpResponse)) return $oResponse;
		
		$oHttpResponse = json_decode($sHttpResponse);
		
        $aErrorMapping = array(
            // name
            'missing-parameter-name' => 'name',
            'field-too-long-name'    => 'name',
    
            // email
            'missing-field-email'    => 'email',
            'invalid-format-email'   => 'email',
            'field-too-long-email'   => 'email',
    
            // message
            'missing-field-message'  => 'message',
            'invalid-format-message' => 'message',
    
            // phone
            'missing-field-phonenumber'   => 'phone',
            'invalid-format-phonenumber'  => 'phone',
            'field-too-long-phonenumber'  => 'phone',
        );

		if(!empty($oHttpResponse->errors)) {
            $oResponse->errors = Utility::mapErrors($oHttpResponse->errors, $aErrorMapping);
            return $oResponse;
		}
		
        if(isset($oHttpResponse->result) && $oHttpResponse->result === false) return $oResponse;
        
        $oResponse->success = true;
        return $oResponse;
    }

    
    /**
     * Trade-in request
     *
     * @param object $oFormFields
     * @return object
     */
    public function tradeInRequest($oFormFields) {

        $sVerboseLog = '';
        
        if(self::DEBUG_MODE) {
            $startMicroTime = microtime(true);
        }
        $oResponse = new \stdClass();
        $oResponse->success = false;
        $oResponse->errors = array();
        $bRequiredImage = isset($oFormFields->requiredImage) && $oFormFields->requiredImage == 'yes' ? true : false;
		
		if(isset($oFormFields->category) && $oFormFields->category != 'kampeerauto'){
			unset($oFormFields->mileage);
		}

        foreach($oFormFields as $key => $value) {
			
            if((empty($value) || $value == 'nb') && $key != 'message') {
                if($key == 'images' && $bRequiredImage === false) continue;
				
                array_push($oResponse->errors, $key);
				
            }
        }
		
        if(count($oResponse->errors)) return $oResponse;
		
        $oHttpRequestBody = new \stdClass();
        $oHttpRequestBody->presentationId = (int) $oFormFields->presentationId;
        $oHttpRequestBody->category = $oFormFields->category;
        $oHttpRequestBody->condition = $oFormFields->condition;
        $oHttpRequestBody->damageFree = strtolower($oFormFields->damage) == 'nee' ? false : true;
        $oHttpRequestBody->brand = $oFormFields->brand;
        $oHttpRequestBody->modelType = $oFormFields->modelType;
        $oHttpRequestBody->licensePlate = $oFormFields->licensePlate;
        $oHttpRequestBody->constructionYear = $oFormFields->constructionYear;
        $oHttpRequestBody->mileage = $oFormFields->mileage;
        $oHttpRequestBody->conditionUpholstery = $oFormFields->upholstery;
        $oHttpRequestBody->conditionTires = $oFormFields->tires;
        $oHttpRequestBody->name = $oFormFields->name;
        $oHttpRequestBody->email = $oFormFields->email;
        $oHttpRequestBody->phoneNumber = $oFormFields->phone;
        $oHttpRequestBody->description = $oFormFields->message;
        $oHttpRequestBody->images = $oFormFields->images;
        $oUtm = isset($oFormFields->utm) ? $oFormFields->utm : null;

        if(!empty($oUtm)) {
            // $oHttpRequestBody->utmSource = !empty($oUtm->source) ? trim($oUtm->source) : 'ovis-wp-plugin';
            $oHttpRequestBody->utmCampaign = strlen(trim($oUtm->campaign)) > 50 ? substr(trim($oUtm->campaign), 0, 50) : trim($oUtm->campaign);
            // $oHttpRequestBody->utmContent = !empty($oUtm->content) ? trim($oUtm->content) : 'contact';
        }
        $this->updateApiEndpoint(self::API_ENDPOINTS['SEARCH_TRADE_IN_REQUEST']);
        $this->setHttpRequestMethod(self::HTTP_METHODS['POST']);
        $this->setHttpRequestBody($oHttpRequestBody);

        $sHttpResponse = curl_exec($this->_oCurlHandle);

        if(self::DEBUG_MODE) {
            rewind($this->_curlLog);
            $sVerboseLog = stream_get_contents($this->_curlLog);
        }

        if(curl_errno($this->_oCurlHandle)) return $oResponse;
        if(empty($sHttpResponse)) return $oResponse;
		
		$oHttpResponse = json_decode($sHttpResponse);
		
		
        $aErrorMapping = array(
            // name
            'missing-parameter-name'      => 'name',
            'field-too-long-name'         => 'name',
    
            // category
            'missing-field-category'      => 'category',
            'unknown-category'            => 'category',
    
            // condition
            'invalid-format-condition'    => 'condition',
            'unknown-condition'           => 'condition',
    
            // damage
            'invalid-format-damagefree'   => 'damage',
    
            // brand
            'invalid-format-brand'        => 'brand',
            'field-too-long-brand'        => 'brand',
    
            // model type
            'invalid-format-modelType'    => 'modelType',
            'field-too-long-modeltype'    => 'modelType',
    
            // construction year
            'invalid-format-constructionyear' => 'constructionYear',
            'out-of-range-constructionyear'   => 'constructionYear',
    
            // mileage
            'invalid-format-mileage'      => 'mileage',
    
            // tires
            'invalid-format-conditiontires'  => 'tires',
            'unknown-conditiontires'         => 'tires',
    
            // upholstery
            'invalid-format-conditionupholstery' => 'upholstery',
            'unknown-conditionupholstery'        => 'upholstery',
    
            // license plate
            'invalid-format-licenseplate'  => 'licensePlate',
            'field-too-long-licenseplate'  => 'licensePlate',
    
            // price
            'field-too-long-priceindication' => 'price',
    
            // phone
            'missing-field-phonenumber'   => 'phone',
            'invalid-format-phonenumber'  => 'phone',
            'field-too-long-phonenumber'  => 'phone',
    
            // email
            'missing-field-email'         => 'email',
            'invalid-format-email'        => 'email',
            'field-too-long-email'        => 'email',
    
            // message
            'missing-field-description'   => 'message',
            'invalid-format-description'  => 'message',
        );

		if(!empty($oHttpResponse->errors)) {
            $oResponse->errors = Utility::mapErrors($oHttpResponse->errors, $aErrorMapping);
            return $oResponse;
		}
		
        if(isset($sHttpResponse->result) && $sHttpResponse->result === false) return $oResponse;

        $oResponse->success = true;

        return $oResponse;
    }


    /**
     * Rental request
     * 
     * @param object $oFormFields
     * @return object
     */
    public function rentalRequest($oFormFields) {

        $sVerboseLog = '';
        
        if(self::DEBUG_MODE) {
            $startMicroTime = microtime(true);
        }

        $oResponse = new \stdClass();
        $oResponse->success = false;
        $oResponse->errors = array();

        foreach($oFormFields as $key => $value) {
            if(empty($value) && $key != 'message') {
                array_push($oResponse->errors, $key);
            }
        }

        if(count($oResponse->errors)) return $oResponse;

        $oHttpRequestBody = new \stdClass();
        $oHttpRequestBody->presentationId = (int) $oFormFields->presentationId;
        $oHttpRequestBody->name = $oFormFields->name;
        $oHttpRequestBody->email = $oFormFields->email;
        $oHttpRequestBody->phoneNumber = $oFormFields->phone;
        $oHttpRequestBody->numberOfPersons = $oFormFields->numberOfPersons;
        $oHttpRequestBody->city = $oFormFields->city;
        $oHttpRequestBody->postalCode = $oFormFields->zipcode;
        $oHttpRequestBody->address = $oFormFields->address;
        $oHttpRequestBody->dateStart = $oFormFields->dateStart;
        $oHttpRequestBody->dateEnd = $oFormFields->dateEnd;
        $oHttpRequestBody->description = $oFormFields->message;

        $oUtm = isset($oFormFields->utm) ? $oFormFields->utm : null;

        if(!empty($oUtm)) {
            // $oHttpRequestBody->utmSource = !empty($oUtm->source) ? trim($oUtm->source) : 'ovis-wp-plugin';
            $oHttpRequestBody->utmCampaign = strlen(trim($oUtm->campaign)) > 50 ? substr(trim($oUtm->campaign), 0, 50) : trim($oUtm->campaign);
            // $oHttpRequestBody->utmContent = !empty($oUtm->content) ? trim($oUtm->content) : 'rentalrequest';
        }

        $this->updateApiEndpoint(self::API_ENDPOINTS['SEARCH_RENTAL_REQUEST']);
        $this->setHttpRequestMethod(self::HTTP_METHODS['POST']);
        $this->setHttpRequestBody($oHttpRequestBody);

        $sHttpResponse = curl_exec($this->_oCurlHandle);

        if(self::DEBUG_MODE) {
            rewind($this->_curlLog);
            $sVerboseLog = stream_get_contents($this->_curlLog);
        }

        if(curl_errno($this->_oCurlHandle)) return $oResponse;
        if(empty($sHttpResponse)) return $oResponse;
		
		$oHttpResponse = json_decode($sHttpResponse);
		
        $aErrorMapping = array(
            // name
            'missing-parameter-name'   => 'name',
            'field-too-long-name'      => 'name',

            // address
            'missing-field-address'    => 'address',
            'invalid-format-address'   => 'address',

            // postal code
            'missing-field-postalcode' => 'zipcode',
            'invalid-format-postalcode'=> 'zipcode',
            'field-too-long-postalcode'=> 'zipcode',

            // city
            'missing-field-city'       => 'city',
            'invalid-format-city'      => 'city',
            'field-too-long-city'      => 'city',

            // phone
            'missing-field-phonenumber' => 'phone',
            'invalid-format-phonenumber'=> 'phone',
            'field-too-long-phonenumber'=> 'phone',

            // email
            'missing-field-email'      => 'email',
            'invalid-format-email'     => 'email',
            'field-too-long-email'     => 'email',

            // message
            'missing-field-description' => 'message',
            'invalid-format-description'=> 'message',

            // dates
            'missing-field-datestart'  => 'date',
            'invalid-format-datestart' => 'date',
            'missing-field-dateend'    => 'date',
            'invalid-format-dateend'   => 'date',
        );

		if(!empty($oHttpResponse->errors)) {
            $oResponse->errors = Utility::mapErrors($oHttpResponse->errors, $aErrorMapping);
            return $oResponse;
		}
		
        if(isset($sHttpResponse->result) && $sHttpResponse->result === false) return $oResponse;

        $oResponse->success = true;

        return $oResponse;
    }

    
    /**
     * Simplify OVIS presentations object
     * 
     * @param object $oPresentations
     * 
     * @return object|null
     */
    public function simplifyPresentations($oPresentations) {

        if(empty($oPresentations)) return null;
        if(!(is_object($oPresentations))) return null;
        if(!isset($oPresentations->data) || empty($oPresentations->data)) return null;
        if(!isset($oPresentations->data->totalInSet) || (int)$oPresentations->data->totalInSet < 1) return null;
        if(!isset($oPresentations->data->data) || empty($oPresentations->data->data) || !(is_array($oPresentations->data->data))) return null;

        if(self::DEBUG_MODE) $startMicroTime = microtime(true);

        $oOriginalPresentations = $oPresentations->data;
        $aOriginalPresentations = $oPresentations->data->data;
        $aSimplifiedPresentations = array();
        $iOriginalPresentationsCount = count($aOriginalPresentations);
        $bSendImgUrlOnly = true;
        
        /**
         * Loop through all presentations
         */
        for($index = 0; $index < $iOriginalPresentationsCount; $index++) {

            $_oPresentation = $aOriginalPresentations[$index]->presentation;
            $_oUser = $aOriginalPresentations[$index]->user;
            $sRealm = trim(strtolower($_oPresentation->realm));
            $bIsRental = isset($_oPresentation->specifications->rental) ? $_oPresentation->specifications->rental : false;

            $oSimplifiedPresentation = new \stdClass();
            $oSimplifiedPresentationUser = new \stdClass();
            $oSimplifiedPresentationSpecifications = new \stdClass();
            $oSimplifiedPresentationBeds = new \stdClass();
            $oSimplifiedPresentationWeightMeasures = new \stdClass();
            $oSimplifiedPresentationDates = new \stdClass();
            $oSimplifiedPresentationMediainfo = new \stdClass();
            $oSimplifiedPresentationMediainfoImage = new \stdClass();
            
            /**
             * User
             */
            $oSimplifiedPresentationUser->companyName = isset($_oUser->companyName) ? $_oUser->companyName : null;
            $oSimplifiedPresentationUser->address = isset($_oUser->address) ? $_oUser->address : null;
            $oSimplifiedPresentationUser->tradeInProposal = isset($_oUser->userSettings->tradeInProposal) ? $_oUser->userSettings->tradeInProposal : false;

            /**
             * Beds
             */
            if($sRealm === self::REALMS['OVIS']) {
                $oSimplifiedPresentationBeds->numberOfBeds = $_oPresentation->specifications->beds->numberOfBeds;
                $oSimplifiedPresentationBeds->numberOfSleepingPlaces = $_oPresentation->specifications->beds->numberOfSleepingPlaces;
                $oSimplifiedPresentationSpecifications->beds = $oSimplifiedPresentationBeds;
            }

            /**
             * Weight measures
             */
            $oSimplifiedPresentationWeightMeasures->width = $_oPresentation->specifications->weightsMeasures->width;
            if($sRealm === self::REALMS['OVIS']) $oSimplifiedPresentationWeightMeasures->lengthTotal = $_oPresentation->specifications->weightsMeasures->lengthTotal;
            if($sRealm === self::REALMS['BOAT']) $oSimplifiedPresentationWeightMeasures->length = $_oPresentation->specifications->weightsMeasures->length;
            $oSimplifiedPresentationWeightMeasures->weightMaximum = $_oPresentation->specifications->weightsMeasures->weightMaximum;
            if($sRealm === self::REALMS['OVIS']) $oSimplifiedPresentationWeightMeasures->capacity = $_oPresentation->specifications->weightsMeasures->capacity;
            $oSimplifiedPresentationSpecifications->weightsMeasures = $oSimplifiedPresentationWeightMeasures;

            /**
             * Dates
             */
            $oSimplifiedPresentationDates->constructionYear = $_oPresentation->specifications->dates->constructionYear;
            $oSimplifiedPresentationSpecifications->dates = $oSimplifiedPresentationDates;

            /**
             * Specifications
             */
            $oSimplifiedPresentationSpecifications->category = $_oPresentation->specifications->category;
            $oSimplifiedPresentationSpecifications->brand = $_oPresentation->specifications->brand;
            if($sRealm === self::REALMS['OVIS']) $oSimplifiedPresentationSpecifications->brandDisplay = $_oPresentation->specifications->brandDisplay;
            $oSimplifiedPresentationSpecifications->model = $_oPresentation->specifications->model;
            if($sRealm === self::REALMS['OVIS']) $oSimplifiedPresentationSpecifications->modelDisplay = $_oPresentation->specifications->modelDisplay;
            $oSimplifiedPresentationSpecifications->modelVersion = isset($_oPresentation->specifications->modelVersion) ? $_oPresentation->specifications->modelVersion : null;
            $oSimplifiedPresentationSpecifications->version = isset($_oPresentation->specifications->version) ? $_oPresentation->specifications->version : null;
            $oSimplifiedPresentationSpecifications->title = $_oPresentation->specifications->title;
            
			if($sRealm === self::REALMS['BOAT']){
				if(!empty($_oPresentation->specifications->titleManual)){
					$oSimplifiedPresentationSpecifications->title = $_oPresentation->specifications->titleManual;
				}
			}
			
			$oSimplifiedPresentationSpecifications->titleSuffix = $_oPresentation->specifications->titleSuffix;
            $oSimplifiedPresentationSpecifications->prices = $_oPresentation->specifications->prices;
            if($sRealm === self::REALMS['OVIS'] && $bIsRental) $oSimplifiedPresentationSpecifications->pricesRental = isset($_oPresentation->specifications->pricesRental) ? $_oPresentation->specifications->pricesRental : null;
            $oSimplifiedPresentationSpecifications->new = $_oPresentation->specifications->new;
            $oSimplifiedPresentationSpecifications->sold = $_oPresentation->specifications->sold;
            $oSimplifiedPresentationSpecifications->expected = $_oPresentation->specifications->expected;
            $oSimplifiedPresentationSpecifications->reserved = $_oPresentation->specifications->reserved;
            if($sRealm === self::REALMS['OVIS']) $oSimplifiedPresentationSpecifications->rental = $_oPresentation->specifications->rental;

            /**
             * Category specifications
             */
            if($sRealm === self::REALMS['OVIS']) {
                switch($_oPresentation->specifications->category) {
                    case 'caravan':
                        if(!empty($_oPresentation->specifications->specsCaravan)) {
                            $oSimplifiedPresentationSpecifications->specsCaravan = $_oPresentation->specifications->specsCaravan;
                        }
                        break;
                    
                    case 'camper':
                        if(!empty($_oPresentation->specifications->specsCamper)) {
                            $oSimplifiedPresentationSpecifications->specsCamper = $_oPresentation->specifications->specsCamper;
                        }
                        break;
                    
                    case 'mobilehome':
                        if(!empty($_oPresentation->specifications->specsMobileHome)) {
                            $oSimplifiedPresentationSpecifications->specsMobileHome = $_oPresentation->specifications->specsMobileHome;
                        }
                        break;
                    
                    case 'trailer':
                        if(!empty($_oPresentation->specifications->specsTrailer)) {
                            $oSimplifiedPresentationSpecifications->specsTrailer = $_oPresentation->specifications->specsTrailer;
                        }
                        break;
                }
            }

            /**
             * Category specifications
             */
            if($sRealm === self::REALMS['BOAT']) {

                $oSimplifiedPresentationSpecifications->subCategory = $_oPresentation->specifications->subCategory;

                switch($_oPresentation->specifications->category) {
                    case 'boat':
                        if(!empty($_oPresentation->specifications->specsBoat)) {
                            $oSimplifiedPresentationSpecifications->specsBoat = $_oPresentation->specifications->specsBoat;
                        }
                        break;
                    
                    case 'engine':
                        if(!empty($_oPresentation->specifications->specsEngine)) {
                            $oSimplifiedPresentationSpecifications->specsEngine = $_oPresentation->specifications->specsEngine;
                        }
                        break;
                    
                    case 'boattrailer':
                        if(!empty($_oPresentation->specifications->specsBoatTrailer)) {
                            $oSimplifiedPresentationSpecifications->specsBoatTrailer = $_oPresentation->specifications->specsBoatTrailer;
                        }
                        break;
                }
            }

            /**
             * Mediainfo Image
             */
            if($_oPresentation->mediainfo->images && is_array($_oPresentation->mediainfo->images) && count($_oPresentation->mediainfo->images) > 0) {
                if($_oPresentation->mediainfo->images[0]->traditional) {
                    if($bSendImgUrlOnly) {
                        $oSimplifiedPresentationMediainfo->thumb = $_oPresentation->mediainfo->images[0]->traditional->original->default->url;
                        $oSimplifiedPresentationMediainfo->thumbLowRes = $_oPresentation->mediainfo->images[0]->traditional->thumbs->url;
                    } else {
                        $oSimplifiedPresentationMediainfoImage->traditional = $_oPresentation->mediainfo->images[0]->traditional;
                    }
                } else {
                    if($bSendImgUrlOnly) {
                        $oSimplifiedPresentationMediainfo->thumb = null;
                    } else {
                        $oSimplifiedPresentationMediainfoImage->traditional = false;
                    }
                }
            }
            
            /**
             * Mediainfo
             */
            $oSimplifiedPresentationMediainfo->{'360'} = isset($_oPresentation->mediainfo->{'360'}) ? $_oPresentation->mediainfo->{'360'} : null;
            if(!$bSendImgUrlOnly) $oSimplifiedPresentationMediainfo->images = array($oSimplifiedPresentationMediainfoImage);

            /**
             * Presentation
             */
            $oSimplifiedPresentation->id = $_oPresentation->id;
            $oSimplifiedPresentation->realm = $_oPresentation->realm;
            $oSimplifiedPresentation->backlink = isset($_oPresentation->backlink) ? $_oPresentation->backlink : false;
            $oSimplifiedPresentation->specifications = $oSimplifiedPresentationSpecifications;
            $oSimplifiedPresentation->mediainfo = $oSimplifiedPresentationMediainfo;
            $oSimplifiedPresentation->user = $oSimplifiedPresentationUser;

            array_push($aSimplifiedPresentations, $oSimplifiedPresentation);
        }

        /**
         * Create simplified presentations object
         */
        $oSimplifiedPresentations = new \stdClass();
        $oSimplifiedPresentations->allowTranslation = $oOriginalPresentations->allowTranslation;
        $oSimplifiedPresentations->itemsPerPage = $oOriginalPresentations->itemsPerPage;
        $oSimplifiedPresentations->page = $oOriginalPresentations->page;
        $oSimplifiedPresentations->totalItems = $oOriginalPresentations->totalItems;
        $oSimplifiedPresentations->totalInSet = $oOriginalPresentations->totalInSet;
        $oSimplifiedPresentations->presentations = $aSimplifiedPresentations;
        $oSimplifiedPresentations->simplified = true;

        if(self::DEBUG_MODE) {
            $oDebugFields = new \stdClass();
            $oDebugFields->simplifyPresentations_microtime = (microtime(true) - $startMicroTime);
        }

        return $oSimplifiedPresentations;
    }

}