<?php
class Rets extends AppModel {

    var 
$name     'Rets';
    var 
$useTable false;
    
    var 
$connectionString;
    
    var 
$server '';  //  blah.domain.com
    
var $port   = ;    // 6103
    
    
var $username  '';  //  given to you by your mls
    
var $password  '';  //  given to you by your mls
    
var $mlsid     '';  //  given to you by your mls
    
var $userAgent '';  //  given to you by your mls

    
var $loginArea '';  //  given to you by your mls  // 'GET /path/to/login.aspx?BrokerCode%3d'
    
var $loginUri  '';  //  given to you by your mls  // '/path/to/login.aspx?BrokerCode%3d'
    
    
var $searchArea 'GET /Brunswick/BRWC/search.aspx';  //  given to you by your mls  // 'GET /path/to/search.aspx'
    
var $searchUri  '/Brunswick/BRWC/search.aspx';  //  given to you by your mls  // '/path/to/search.aspx'

    
var $retsVersion 'RETS/1.5';
    var 
$qop         'auth';
    var 
$nc          '00000001';
                    
    var 
$cnonce;
    var 
$nonce;
    var 
$opaque;
    var 
$sessionId false;
    var 
$response;
    var 
$A1;
    
    var 
$debug true;
    
    var 
$imageDir     ''//  /path/to/server/location/to/store/images/
    
var $retsImageDir '';  //  given to you by your mls  //  http://sub.domain.com/path/to/images/
    
    
var $resiFields 'MLNumber,Status,ListingPrice,PropertySubtype1,StreetDirection,StreetNumber,StreetName,StreetSuffix,StreetPostDirection,Unit,City,State,ZipCode,Subdivision,Area,RESIPROP,SchoolName1,SchoolName2,SchoolName3,SquareFootage,Acres,LotSquareFootage,YearBuilt,RESILTFR,RESILTLT,RESILTRT,RESILTRR,Bedrooms,FullBathrooms,HalfBathrooms,RESIAPPL,RESIDIND,RESIFLOR,RESIFIRE,RESIINTR,RESIEXTF,RESIWATE,RESIWTRT,RESIWTRV,RESIWTVT,RESIWTRS,RESIANND,Longitude,Latitude,ListingAgentFirstName,ListingAgentLastName,MarketingRemarks,ListingOfficeMLSID';
    var 
$landFields 'MLNumber,Status,ListingPrice,StreetNumber,StreetName,StreetSuffix,City,State,ZipCode,Subdivision,Area,LANDPROP,LANDWATE,LANDWTRT,LANDWTRV,LANDWTVT,LANDLTFR,LANDLTLT,LANDLTRT,LANDLTRR,LANDIRRL,LANDTOPO,LANDUTIL,LANDPOTU,LANDUTAV,LANDSEPT,Longitude,Latitude,ListingAgentFirstName,ListingAgentLastName,LANDMISC,MarketingRemarks,ListingOfficeMLSID';
    
    function 
Rets() {
        
$this->A1 md5($this->username ':' $this->mlsid ':' $this->password);       
    }
    
/**
 * This takes a type and a time and will query RETS to get a list of properties that were updated
 *
 * @param integer $type type of property, 1 = residential, 2 = land
 * @param datetime $lastUpdatedTime datetime to check for properties updated since this time
 * @return array This will be an array of CakePHP model
 * @access public
 */ 
    
function get_updated_properties($type,$lastUpdatedTime,$updateId) {
        
$this->login();
        
        
//  switch to search page
        //  ---------------------
        
$this->switch_to_search();
        
        if(
$type == 1) {        
            
$query rawurlencode('Class=RESI&SearchType=Property&Count=1&Limit=NONE&QueryType=DMQL2&Format=COMPACT&Select=MLNumber,LastModifiedDateTime,Status&Query=(MLNumber=1+),(LastModifiedDateTime=' date('Y-m-d',strtotime($lastUpdatedTime)) . 'T' .  date('H:i:s',strtotime($lastUpdatedTime)) . '+)');
        }
        else if(
$type == 2) {
            
$query rawurlencode('Class=LAND&SearchType=Property&Count=1&Limit=NONE&QueryType=DMQL2&Format=COMPACT&Select=MLNumber,LastModifiedDateTime,Status&Query=(MLNumber=1+),(LastModifiedDateTime=' date('Y-m-d',strtotime($lastUpdatedTime)) . 'T' .  date('H:i:s',strtotime($lastUpdatedTime)) . '+)');
        }
         
        
$properties $this->make_request($this->request_string($this->searchArea '?' $query,$this->searchUri));
        
        
App::import('Model','Update');
        
$this->Update = new Update();
        
        if(
$type == 1) {
            
$this->Update->id $updateId;
            
$this->Update->save(array(
                
'resi_query' => $query,
                
'resi_response' => $properties
            
));
        }
        else if(
$type == 2) {
            
$this->Update->id $updateId;
            
$this->Update->save(array(
                
'land_query' => $query,
                
'land_response' => $properties
            
));
        }

        
$rows explode("\n",$properties);
        
        
$output = array();
        
        for(
$i=0;$i<count($rows);$i++) {
            
$checking explode("\t",$rows[$i]);                 
            if(
count($checking) > && $checking[1] != 'MLNumber') { 
                
$data[] = array(          
                    
'mls'      => $checking[1],
                    
'modified' => date('Y-m-d H:i:s',strtotime($checking[2])),
                    
'status'   => $checking[3]
                ); 
                
                
$output[] = $this->get_rets_data($checking[1],$type);
            }
        }
        
        return 
$output;
    }
    
/**
 * This takes a type and a time and will query RETS to get a list of properties that were updated
 *
 * @param datetime $mls datetime to check for properties updated since this time
 * @param integer $type type of property, 1 = residential, 2 = land
 * @return array This will be an array of CakePHP model
 * @access public
 */ 
    
function get_listing($mls$type) {
        
$this->login();
        
        
//  switch to search page
        //  ---------------------
        
$this->switch_to_search();
        
        
$property $this->get_rets_data($mls,$type);
        
        return 
$property;
    }
    
/**
 * Gets RETS info about an mls number
 *
 * @param integer $mls RETS mls number
 * @param integer $type type of property, 1 = residential, 2 = land
 * @return array This will be an array of CakePHP model
 * @access public
 */     
    
function get_rets_data($mls,$type=1) {
        if(
$type == 1) {
            
$query rawurlencode('Class=RESI&Select=' $this->resiFields '&Query=(MLNumber=' $mls ')&SearchType=Property&Format=COMPACT-DECODED&QueryType=DMQL2&Limit=1&OFFSET=0;');
            
            
$property $this->make_request($this->request_string($this->searchArea '?' $query,$this->searchUri));
            
            
$rows explode("\n",$property);
         
             for(
$i=0;$i<count($rows);$i++) {
                
$checking explode("\t",$rows[$i]);
                if(
count($checking) > && $checking[1] != 'MLNumber') {
                    return 
$this->convert_to_usable_format($checking,$type);                   
                }
            }
        }
        else if(
$type == 2) {
            
$query rawurlencode('Class=LAND&Select=' $this->landFields '&Query=(MLNumber=' $mls ')&SearchType=Property&Format=COMPACT-DECODED&QueryType=DMQL2&Limit=1&OFFSET=0;');
            
            
$property $this->make_request($this->request_string($this->searchArea '?' $query,$this->searchUri));
            
            
$rows explode("\n",$property);
         
             for(
$i=0;$i<count($rows);$i++) {
                
$checking explode("\t",$rows[$i]);
                if(
count($checking) > && $checking[1] != 'MLNumber') {
                    return 
$this->convert_to_usable_format($checking,$type);                   
                }
            }
        }
    }
    
    function 
mass_download($type=1,$offset=0) {
        
$this->login();
        
        
//  switch to search page
        //  ---------------------
        
$this->switch_to_search();        

        
//residential
        
if($type==1) {
            
$query rawurlencode('Class=RESI&Select=' $this->resiFields '&Query=(ListingOfficeMLSID=*),(MLNumber=1+)&SearchType=Property&Format=COMPACT-DECODED&QueryType=DMQL2&Limit=500&OFFSET=' $offset);
               
            
$properties $this->make_request($this->request_string('GET /Brunswick/BRWC/search.aspx?' $query,$this->searchUri));

            
//  take these nasty ass naming conventions and convert them to acceptable db naming conventions
            //  --------------------------------------------------------------------------------------------
            
$rows explode("\n",$properties);
         
             for(
$i=0;$i<count($rows);$i++) {
                
$checking explode("\t",$rows[$i]);                     
                if(
count($checking) > && $checking[1] != 'MLNumber') { 
                    
$data[] = $this->convert_to_usable_format($checking,$type);                    
                }
            }
        }
        elseif(
$type==2) {
            
$query rawurlencode('Class=LAND&Select=' $this->landFields '&Query=(ListingOfficeMLSID=*),(MLNumber=1+)&SearchType=Property&Format=COMPACT-DECODED&QueryType=DMQL2&Limit=500&OFFSET=' $offset);      
            
            
$properties $this->make_request($this->request_string('GET /Brunswick/BRWC/search.aspx?' $query,$this->searchUri));

            
//  take these nasty ass naming conventions and convert them to acceptable db naming conventions
            //  --------------------------------------------------------------------------------------------
            
$rows explode("\n",$properties);
         
            for(
$i=0;$i<count($rows);$i++) {
                
$checking explode("\t",$rows[$i]);                     
                if(
count($checking) > && $checking[1] != 'MLNumber') { 
                    
$data[] = $this->convert_to_usable_format($checking,$type);
                }
            }
        }
        
        if(isset(
$data)) {
            return 
$data;
        } else {
            return 
false;   
        }
    }
    
    function 
make_request($out) {
        
//  Creates socket connection to the RETS server
        
$fp fsockopen($this->server,$this->port,$errno,$errstr,30);
        
        
//  IMPORTANT - This checks to make sure the socket connection is a valid socket connection
        //  If this wasn't done, and the connection wasn't a valid resource it can send the script into
        //  an endless loop and bomb the server
        
if(is_resource($fp)) {
            if(
$this->debug) {
                
debug($out);
            }
        } else {                    
            
debug('This connection is not a valid connection.<br />This script has been stopped to ensure your server does not crash.');
            
            return 
false;
        }
        
        
$response '';
        
        
fputs($fp$out);
        while (!
feof($fp)) {      
            
$response .= fgets($fp128);  
        }
        
fclose($fp);    

        return 
$response;  
    }
    
    function 
login() {
        
$A2 md5('GET:' $this->loginUri);
        
        
$response $this->make_request($this->request_string($this->loginArea));

        
$this->nonce $this->parse_response($response'nonce=');

        
$this->opaque $this->parse_response($response'opaque=');

        
$this->cnonce md5($this->userAgent ':::' $this->nonce);
                    
        
$this->response md5($this->A1 ':' $this->nonce ':' $this->nc ':' $this->cnonce ':' $this->qop ':' $A2);
        
        
$authedResponse $this->make_request($this->request_string($this->loginArea));
                                
        if(!
$this->sessionId $this->parse_response($authedResponse'RETS-Session-ID')) {
            
debug('authed failed');
            exit;   
        }
    }
    
    function 
switch_to_search() {        
        
$response $this->make_request($this->request_string($this->searchArea,false,false));

        
//  strip nonce from response
        
$this->nonce $this->parse_response($response'nonce=');

        
$this->opaque $this->parse_response($response'opaque=');
                            
        
$A2 md5('GET:' $this->searchUri);
                    
        
$this->cnonce md5($this->userAgent ':::' $this->nonce);
                    
        
$this->response md5($this->A1 ':' $this->nonce ':' $this->nc ':' $this->cnonce ':' $this->qop ':' $A2);
    }
    
    function 
parse_response($haystack$needle){
        return 
substr(strstr($haystack$needle),(strlen($needle) + 1),32);    
    }
    
    function 
request_string($before='',$url=false,$auth true) {
        
$out = !empty($before) ? $before " HTTP/1.1\r\n" '';
        
$out .= 'RETS-Version: ' $this->retsVersion "\r\n";
        
        
//  this will only happen when we are attempting to authorize
        //  ---------------------------------------------------------
        
if($auth) {
            
$out .= 'Authorization: Digest username="' $this->username '", realm="' $this->mlsid '", nonce="' $this->nonce '", uri="' . (($url) ? $url $this->loginUri) . '", cnonce="' $this->cnonce '", nc=' $this->nc ', qop="' $this->qop '", response="' $this->response '", opaque="' $this->opaque '"' " \r\n";
        }
        
        
$out .= 'Host: ' $this->server ':' $this->port "\r\n";
        
        if(
$this->sessionId) {
            
$out .= 'Cookie: RETS-Session-ID=' $this->sessionId "\r\n";
        }
        
        
$out .= "Accept: */*\r\n";
        
$out .= 'User-Agent: ' $this->userAgent "\r\n";
        
        
$out .= "Connection: Close\r\n\r\n";
                
        return 
$out
    }
    
    function 
download_images($id,$mls) {
        
$imageCount 0;
        
        
//  check that we have a folder to put this stuff in
        //  ------------------------------------------------
        
if(!is_dir($this->imageDir)) {
            
mkdir($this->imageDir);
            
            
chmod($this->imageDir,'0777');   
        }
        
        
$image $this->retsImageDir '0' substr($mls, -22) . '/' $mls '.jpg';
              
        if(
$data = @file_get_contents($image)){
            
$file fopen($this->imageDir $id '_01.jpg'"w+");
                        
            if(!
fputs($file$data)){
                
debug('error opening image for local save');
            }
            
fclose($file);
            
            
$imageCount++;
        }

        for(
$i=1;$i<20;$i++) {
            if(
$i 10){
                
$mod '_0' $i;
            }
            else {
                
$mod '_' $i;
            }
            
            if((
$imageCount+1) < 10){
                
$localMod '_0' . ($imageCount+1);
            }
            else {
                
$localMod '_' . ($imageCount+1);
            }
            
//original image
            
$image $this->retsImageDir '0' substr($mls, -22) . '/' $mls $mod '.jpg';
            
            
//  check that the remote file exists
            //  ---------------------------------
            
if(@fopen($image"r") !== false) {
                
//  get that image
                //  --------------
                
if($data file_get_contents($image)){
                    
//  open the local file for writing
                    //  -------------------------------
                    
$file fopen($this->imageDir $id $localMod '.jpg'"w+");
                
                    
//  save the image locally
                    //  ----------------------
                    
if(!fputs($file$data)){
                        
debug('error opening image for local save');
                    }
                    
fclose($file);
                
                    
$imageCount++;
                }
            }                             
        }
        
        return 
$imageCount;   
    }
    
    function 
convert_to_usable_format($row,$type) {
        if(
$type == 1) {
            return array(          
                
'mls'                   => $row[1],
                
'status'                => $row[2],
                
'listing_price'         => $row[3],
                
'property_type'         => $row[16],
                
'address'               => $row[6] . ' ' $row[7] . ' ' $row[8],
                
'street_direction'      => $row[5],
                
'street_post_direction' => $row[9], 
                
'city'                  => $row[11],
                
'state'                 => $row[12],
                
'zip'                   => $row[13],
                
'subdivision'           => $row[14],
                
'area'                  => $row[15],
                
'school_1'              => $row[17],
                
'school_2'              => $row[18],
                
'school_3'              => $row[19],
                
'sqft'                  => $row[20],
                
'acres'                 => $row[21],
                
'lot_sqft'              => $row[22],
                
'year_built'            => $row[23],
                
'resi_ltfr'             => $row[24],
                
'resi_ltlt'             => $row[25],
                
'resi_ltrt'             => $row[26],
                
'resi_ltrr'             => $row[27],
                
'bedrooms'              => $row[28],
                
'full_bathrooms'        => $row[29],
                
'half_bathrooms'        => $row[30],
                
'resi_appl'             => $row[31],
                
'resi_dind'             => $row[32],
                
'resi_flor'             => $row[33],
                
'resi_fire'             => $row[34],
                
'resi_intr'             => $row[35],
                
'resi_extf'             => $row[36],
                
'resi_wate'             => $row[37],
                
'resi_wtrt'             => $row[38],
                
'resi_wtrv'             => $row[39],
                
'resi_wtvt'             => $row[40],
                
'resi_wtrs'             => $row[41],
                
'resi_annd'             => $row[42],
                
'longitude'             => $row[43],
                
'latitude'              => $row[44],
                
'agent_first_name'      => $row[45],
                
'agent_last_name'       => $row[46],
                
'marketing_remarks'     => $row[47],
                
'office_id'             => $row[48],
                
'type'                  => $type,
                
'rets_status'           => $row[2]
            );    
        }
        else if(
$type == 2) {
            return array(          
                
'mls'               => $row[1],
                
'status'            => $row[2],
                
'listing_price'     => $row[3],
                
'address'           => $row[4] . ' ' $row[5] . ' ' $row[6],
                
'city'              => $row[7],
                
'state'             => $row[8],
                
'zip'               => $row[9],
                
'subdivision'       => $row[10],
                
'area'              => $row[11],
                
'property_type'     => $row[12],
                
'land_wate'         => $row[13],
                
'land_wtrt'         => $row[14],
                
'land_wtrv'         => $row[15],
                
'land_wtvt'         => $row[16],
                
'land_ltfr'         => $row[17],
                
'land_ltlt'         => $row[18],
                
'land_ltrt'         => $row[19],
                
'land_ltrr'         => $row[20],
                
'land_irrl'         => $row[21],
                
'land_topo'         => $row[22],
                
'land_util'         => $row[23],
                
'land_potu'         => $row[24],
                
'land_utav'         => $row[25],
                
'land_sept'         => $row[26],     
                
'longitude'         => $row[27],
                
'latitude'          => $row[28],
                
'agent_first_name'  => $row[29],
                
'agent_last_name'   => $row[30],
                
'land_misc'         => $row[31],
                
'marketing_remarks' => $row[32],
                
'office_id'         => $row[33],
                
'type'              => $type,
                
'rets_status'       => $row[2]
            );    
        }
    }
}
?>