<?php
/**
 * @filename        Carrier.php
 * @description     This class is the base of all carrier classes.
 * @version         1.0
 * @package         MSHShippingLibrary
 * @author          Mohammad Sajjad Hossain - info@sajjadhossain.com
 *
 * @link            http://sajjadhossain.com
 * @created on      December 07, 2010
 * @dependacies
 * @license
 ***/
abstract class Carrier
{
    /**
    *@desc Enables/disables cache
    */
    private $enableCache = true;

    /**
    *@desc If the response is from cache
    */
    private $responsedFromCache = false;

    /**
    *@desc Suffix of cache table. tbl_cache_shipping_X here X = fedex, canpost, auspost, ups, usps etc.
    */
    private $cacheTableSuffix = '';

    /**
    *@desc For how many days the information will be cached
    */
    private $cacheTime = 1;

    /**
    *@desc Database connection
    */
    private $dbConnection = null;

    /**
    *@desc String used for searching cached information
    */
    protected $searchString = '';

    /**
    *@desc Enables/disables debugging
    */
    private $debug = false;

    /**
    *@desc Debugging output
    */
    private $debugText = '';

    /**
    *@desc Shows/hides cURL error
    */
    private $showCurlError = false;

    /**
    *@desc If default account information will be used or not
    */
    protected $isDefault = false;

    /**
    *@desc The account ID or number. This is named differenly in different carriers.
    */
    private $userId = '';

    /**
    *@desc Default user ID. If no user ID is found it will be used.
    */
    private $defaultUserId = '';

    /**
    *@desc Account parameter 1. Optional
    */
    private $credentialOne = '';

    /**
    *@desc Default account parameter 1. Optional
    */
    private $defaultCredentialOne = '';

    /**
    *@desc Account parameter 2. Optional
    */
    private $credentialTwo = '';

    /**
    *@desc Default account parameter 2. Optional
    */
    private $defaultCredentialTwo = '';

    /**
    *@desc Account parameter 3. Optional
    */
    private $credentialThree = '';

    /**
    *@desc Default account parameter 3. Optional
    */
    private $defaultCredentialThree = '';

    /**
    *@desc URL to which the request will be sent
    */
    private $requestUrl = '';

    /**
    *@desc Request methods. Possible values 'GET' and 'POST'
    */
    private $requestMethood = 'POST';

    /**
    *@desc Request string containing request parameters
    */
    private $requestString = '';

    /**
    *@desc ShippingResponse type object
    */
    protected $response = null;

    /**
    *@desc Request parameters
    */
    private $params = array();

    /**
    *@desc Ship from city
    */
    private $fromCity = '';

    /**
    *@desc Ship from state/province
    */
    private $fromState = '';

    /**
    *@desc Ship from zip or postal code
    */
    private $fromZip = '';

    /**
    *@desc Ship from country code
    */
    private $fromCountryCode = '';

    /**
    *@desc Ship from country full name
    */
    private $fromCountry = '';

    /**
    *@desc Ship to city
    */
    private $toCity = '';

    /**
    *@desc Ship to state/province
    */
    private $toState = '';

    /**
    *@desc Ship to zip or postal code
    */
    private $toZip = '';

    /**
    *@desc Ship to country code
    */
    private $toCountryCode = '';

    /**
    *@desc Ship to country full name
    */
    private $toCountry = '';

    /**
    *@desc Allowed values 'lb', 'kg'
    */
    private $weightUnit = 'lb';

    /**
    *@desc Allowed values 'in', 'cm'
    */
    private $lengthUnit = 'in';

    /**
    *@desc Currency code
    */
    private $currencyCode = 'USD';

    /**
    *@desc ShippingItem object for package information
    */
    protected $item = null;

    /**
    *@desc Request headers
    */
    private $headers = array();

    /**
    *@desc List of services to be returned
    */
    private $services = array();
    
    /**
    *@Anjuman | D: 04242012 (mmdddyyyy)
    * Shipping destination type, default 1 (residential), or 0 (commercial)
    */
    public $shipping_destination_type = ''; 

    /**
    *@desc Constructor
    */
    public function Carrier()
    {
        $this->response = new ShippingResponse();
    }

    /**
    *@desc Function for preparing response. Child class must implement it.
    */
    abstract protected function prepareResponse();

    /**
    *@desc Function for shipping calculation. Child class must implement it.
    */
    abstract protected function calculate();

    /**
    *@desc Required for caching. Child class must implement it.
    */
    abstract protected function getSearchString();

    /**
    *@desc Sends the request and receives the response
    */
    protected function makeAPICall()
    {
        if(!$this->enableCache || !$this->getResponseFromDB())
        {
            $this->logMessage('Preparing request...');
            $this->prepareRequest();

            $this->logMessage($this->requestString);
            $this->logMessage('Initializing cURL...');
            $this->logMessage('Sending Request to : ' . $this->requestUrl);

            $ch = curl_init();

            if($this->requestMethood == 'POST')
            {
                curl_setopt($ch, CURLOPT_URL, $this->requestUrl);
                curl_setopt($ch, CURLOPT_POST, 1);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $this->requestString);
            }
            else
            {
                curl_setopt($ch, CURLOPT_URL, $this->requestUrl . '?' . $this->requestString);
            }

            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($ch, CURLOPT_VERBOSE, ($this->debug ? 1 : 0));

            if(!empty($this->headers))
            {
                $this->headers[] = 'Content-Length: ' . strlen($this->requestString);
                curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
            }

            $this->logMessage('Getting response...');
            $startTime = microtime(true);

            $this->response->setRawResponse(curl_exec($ch));

            $endTime = microtime(true);
            $timePassed = $endTime - $startTime;
            $this->logMessage("It took $timePassed seconds to complete the request");

            $this->responsedFromCache = false;

            $this->logMessage(urldecode($this->response->getRawResponse()));

            if (curl_errno($ch))
            {
                $error = 'cURL Error # ' . curl_errno($ch) . ' : ' . curl_error($ch);

                $this->logMessage($error);

                if($this->showCurlError)
                {
                    $this->response->setError($error);
                }
            }
            else
            {
                curl_close($ch);
            }
        }
        $this->logMessage('Preparing response...');
        $this->prepareResponse();
        $this->logMessage('Done...');
    }
    /**
    *@desc Sets ShippingItem object with package information
    */
    public function setItem($item)
    {
        if(!empty($item))
        {
            $item->setWeightUnit($this->weightUnit);
            $item->setLengthUnit($this->lengthUnit);
            $this->item = $item;
        }
        else
        {
            $this->response->setError('Item is missing');
        }
    }

    /**
    *@desc Sets request string
    */
    protected function setRequestString($str)
    {
        $this->requestString = $str;
    }

    /**
    *@desc Sets user ID
    */
    public function setUserId($id)
    {
        $this->userId = trim($id);
    }

    /**
    *@desc Returns user ID
    */
    public function getUserId()
    {
        if(empty($this->userId) && !empty($this->defaultUserId))
        {
            $this->isDefault = true;
            return $this->defaultUserId;
        }

        return $this->userId;
    }

    /**
    *@desc Sets default user ID
    */
    protected function setDefaultUserId($id)
    {
        $this->defaultUserId = $id;
    }

    /**
    *@desc Sets additional account information
    */
    public function setCredentialOne($cred)
    {
        $this->credentialOne = trim($cred);
    }

    /**
    *@desc Returns additional account information
    */
    public function getCredentialOne()
    {
        if(empty($this->credentialOne) && !empty($this->defaultCredentialOne))
        {
            $this->isDefault = true;
            return $this->defaultCredentialOne;
        }

        return $this->credentialOne;
    }

    /**
    *@desc Sets default additional account information
    */
    public function setDefaultCredentialOne($cred)
    {
        $this->defaultCredentialOne = $cred;
    }

    /**
    *@desc Sets additional account information
    */
    public function setCredentialTwo($cred)
    {
        $this->credentialTwo = trim($cred);
    }

    /**
    *@desc Returns additional account information
    */
    public function getCredentialTwo()
    {
        if(empty($this->credentialTwo) && !empty($this->defaultCredentialTwo))
        {
            $this->isDefault = true;
            return $this->defaultCredentialTwo;
        }

        return $this->credentialTwo;
    }

    /**
    *@desc Sets default additional account information
    */
    public function setDefaultCredentialTwo($cred)
    {
        $this->defaultCredentialTwo = $cred;
    }

    /**
    *@desc Sets additional account information
    */
    public function setCredentialThree($cred)
    {
        $this->credentialThree = trim($cred);
    }

    /**
    *@desc Returns additional account information
    */
    public function getCredentialThree()
    {
        if(empty($this->credentialThree) && !empty($this->defaultCredentialThree))
        {
            $this->isDefault = true;
            return $this->defaultCredentialThree;
        }

        return $this->credentialThree;
    }

    /**
    *@desc Sets default additional account information
    */
    public function setDefaultCredentialThree($cred)
    {
        $this->defaultCredentialThree = $cred;
    }

    /**
    *@desc Sets the cache table names suffix
    *
    * @param string fedex, ups, usps, canpost, auspost
    */
    protected function setCacheTableSuffix($suffix)
    {
        $this->cacheTableSuffix = $suffix;
    }

    public function getFromCity()
    {
        return $this->fromCity;
    }

    public function getFromState()
    {
        return $this->fromState;
    }

    protected function getFromStateCode()
    {
        $isoCountry = new ISOCountry();

        if($this->fromCountryCode == 'US')
        {
            return $isoCountry->getUSStateCode($this->fromState);
        }
        else if($this->fromCountryCode == 'CA')
        {
            return $isoCountry->getCanadianStateCode($this->fromState);
        }
        return $this->fromState;
    }

    public function getFromZip()
    {
        return $this->fromZip;
    }

    public function getFromCountry()
    {
        return $this->fromCountry;
    }

    public function getFromCountryCode()
    {
        return $this->fromCountryCode;
    }

    public function getToCity()
    {
        return $this->toCity;
    }

    public function getToState()
    {
        return $this->toState;
    }

    protected function getToStateCode()
    {
        $isoCountry = new ISOCountry();

        if($this->toCountryCode == 'US')
        {
            return $isoCountry->getUSStateCode($this->toState);
        }
        else if($this->toCountryCode == 'CA')
        {
            return $isoCountry->getCanadianStateCode($this->toState);
        }
        return $this->toState;
    }

    public function getToZip()
    {
        return $this->toZip;
    }

    public function getToCountry()
    {
        return $this->toCountry;
    }

    public function getToCountryCode()
    {
        return $this->toCountryCode;
    }

    public function setFromCity($city)
    {
        $this->fromCity = trim($city);
    }

    public function setFromState($state)
    {
        $this->fromState = trim($state);
    }

    public function setFromZip($zip)
    {
        $this->fromZip = trim($zip);
    }

    public function setFromCountry($country)
    {
        $this->fromCountry = trim($country);
    }

    public function setFromCountryCode($code)
    {
        $this->fromCountryCode = trim($code);
    }

    public function setToCity($city)
    {
        $this->toCity = trim($city);
    }

    public function setToState($state)
    {
        $this->toState = trim($state);
    }

    public function setToZip($zip)
    {
        $this->toZip = trim($zip);
    }

    public function setToCountry($country)
    {
        $this->toCountry = trim($country);
    }

    public function setToCountryCode($code)
    {
        $this->toCountryCode = trim($code);
    }

    public function setWeightUnit($unit)
    {
        $this->weightUnit = $unit;
    }

    public function getWeightUnit()
    {
        return $this->weightUnit;
    }

    public function setLengthUnit($unit)
    {
        $this->lengthUnit = $unit;
    }

    public function getLengthUnit()
    {
        return $this->lengthUnit;
    }

    public function setCurrencyCode($code)
    {
        $this->currencyCode = $code;
    }

    public function getCurrencyCode()
    {
        return $this->currencyCode;
    }

    /**
    *@desc Sets request URL
    */
    protected function setRequestUrl($url)
    {
        $this->requestUrl = $url;
    }

    protected function setRequestMethod($method)
    {
        $this->requestMethood = $method;
    }

    /**
    *@desc Sets header information to be sent with the request
    */
    protected function setHeader($key, $value)
    {
        $this->headers[] = $key . ': ' . $value;
    }

    /**
    *@desc Prepares request string form the parameters
    *
    * @return null
    */
    protected function prepareRequest()
    {
        if(!empty($this->params))
        {
            $nameValuePairs = array();

            foreach($this->params as $key => $value)
            {
                if($value)
                {
                    $nameValuePairs[] = $key .'=' . urlencode($value);
                }
            }

            $this->requestString = implode('&', $nameValuePairs);
        }
    }

    /**
    *@desc Stores keys and values
    *
    * @param string
    * @param string
    *
    * @return void
    */
    public function setParam($key, $value)
    {
        !empty($key) ? ($this->params[$key] = $value) : '';
    }

    /**
    *@desc For logging debug messages
    *
    * @param string
    */
    protected function logMessage($message)
    {
        if($this->debug)
        {
            $fileName = MSHSL_LOG_FILE_PATH . 'log_' . date('Y-m-d') . '.php';

            $text = "$message\n\r-----------------------------------------------------------------------------\n\r";

            if(!file_exists($fileName))
            {
                $text = "<?php die('Access Denied'); ?>\n\n" . $text;
            }

            $this->debugText .= $text;

            $f = fopen($fileName, 'ab');
            flock($f, LOCK_EX);
            fwrite($f, $text);
            flock($f, LOCK_UN);
            fclose($f);
        }
    }

    /**
    *@desc Returns ShippingResponse object
    */
    public function getResponseText()
    {
        return $this->response;
    }

    /**
    *@desc Enables debugging
    */
    public function enableDebugging()
    {
        $this->debug = true;
    }

    /**
    *@desc Enables caching
    */
    public function enableCache()
    {
        $this->enableCache = true;
    }

    /**
    *@desc Disables caching
    */
    public function disableCache()
    {
        $this->enableCache = false;
    }

    /**
    *@desc Shows/hides cURL error
    */
    public function showCurlError()
    {
        $this->showCurlError = true;
    }

    /**
    *@desc Validation function
    */
    protected function validateFromCity()
    {
        if(empty($this->fromCity))
        {
            $this->response->setError('From City name is missing');
        }
    }

    /**
    *@desc Validation function
    */
    protected function validateFromState()
    {
        if(empty($this->fromState))
        {
            $this->response->setError('From State name is missing');
        }
    }

    /**
    *@desc Validation function
    */
    protected function validateFromZip()
    {
        if(empty($this->fromZip))
        {
            $this->response->setError('From Zip Code is missing');
        }
    }

    /**
    *@desc Validation function
    */
    protected function validateFromCountyCode($code = '')
    {
        if(empty($this->fromCountryCode))
        {
            $this->response->setError('From Country Code is missing');
        }

        if(!empty($code))
        {
            if($this->fromCountryCode != $code)
            {
                $this->response->setError('Invalid From Country Code. Expected: ' . $code);
            }
        }
    }

    /**
    *@desc Validation function
    */
    protected function validateFromCounty()
    {
        if(empty($this->fromCountry))
        {
            $this->response->setError('From Country is missing');
        }
    }

    /**
    *@desc Validation function
    */
    protected function validateToCity()
    {
        if(empty($this->toCity))
        {
            $this->response->setError('To City name is missing');
        }
    }

    /**
    *@desc Validation function
    */
    protected function validateToState()
    {
        if(empty($this->toState))
        {
            $this->response->setError('To State name is missing');
        }
    }

    /**
    *@desc Validation function
    */
    protected function validateToZip()
    {
        if(empty($this->toZip))
        {
            $this->response->setError('To Zip Code is missing');
        }
    }

    /**
    *@desc Validation function
    */
    protected function validateToCountyCode()
    {
        if(empty($this->toCountryCode))
        {
            $this->response->setError('To Country Code is missing');
        }
    }

    /**
    *@desc Validation function
    */
    protected function validateToCounty()
    {
        if(empty($this->toCountry))
        {
            $this->response->setError('To Country is missing');
        }
    }

    protected function validateServiceSelected()
    {
        if(empty($this->services))
        {
            $this->response->setError('No service has been selected.');
        }
    }

    /**
    *@desc Returns true if all inputs are valid
    * @return bool
    */
    protected function isValid()
    {
        if(empty($this->requestUrl))
        {
            $this->logMessage('Request URL is missing.');
            $this->response->setError('Request URL has not been set');
        }

        if($this->response->hasError())
        {
            $this->logMessage('Request could not be made because of errors:' . $this->response->getErrorString());
            return false;
        }

        return true;
    }

    /**
    *@desc Copy of xml_convert function of CI XML helper
    * Convert Reserved XML characters to Entities
    *
    * @access   public
    * @param    string
    * @return   string
    */
    protected function convertForXML($str)
    {
        $temp = '__TEMP_AMPERSANDS__';

        // Replace entities to temporary markers so that
        // ampersands won't get messed up
        $str = preg_replace("/&#(\d+);/", "$temp\\1;", $str);
        $str = preg_replace("/&(\w+);/",  "$temp\\1;", $str);

        $str = str_replace(array("&","<",">","\"", "'", "-"),
                   array("&amp;", "&lt;", "&gt;", "&quot;", "&#39;", "&#45;"),
                   $str);

        // Decode the temp markers back to entities
        $str = preg_replace("/$temp(\d+);/","&#\\1;",$str);
        $str = preg_replace("/$temp(\w+);/","&\\1;", $str);

        return $str;
    }

    /**
    *@desc Utility function
    */
    public function dumpVar($var)
    {
        header("Content-type: text/plain");
        echo '<pre>';
        print_r($var);
        die('</pre>');
    }

    /**
    *@desc Connects to the database
    */
    private function connectDB()
    {
        if(!MSHSL_DB_HOST || !MSHSL_DB_NAME || !MSHSL_DB_USER)
        {
            $this->response->setError('Database information missing');
        }
        $this->dbConnection = @mysql_connect(MSHSL_DB_HOST, MSHSL_DB_USER, MSHSL_DB_PASS);

        if(!$this->dbConnection)
        {
            $this->response->setError('Unable to connect to the database server with given information');
        }
        else
        {
            if(!mysql_select_db(MSHSL_DB_NAME, $this->dbConnection))
            {
                $this->response->setError('Could not select the database');
            }
        }
    }

    /**
    *@desc Returns cached response from database
    */
    private function getResponseFromDB()
    {
        $this->logMessage('Checking database');

        if(!empty($this->cacheTableSuffix))
        {
            $sql = 'SELECT *
                    FROM tbl_cache_shipping_' . $this->cacheTableSuffix . '
                    WHERE search_string = \'' . addslashes($this->getSearchString()) . '\'
                    ORDER BY created_on DESC
                    LIMIT 1';

            $this->connectDB();

            if($this->response->hasError())
            {
                $this->logMessage('Request could not be made because of errors: ' . $this->response->getErrorString());
            }
            else
            {
                $q = @mysql_query($sql, $this->dbConnection);

                if($q)
                {
                    $row = @mysql_fetch_object($q);

                    if($row)
                    {
                        if($row->response)
                        {
                            $this->logMessage('Response from cache');
                            $this->response->setRawResponse(stripslashes($row->response), ENT_QUOTES);
                            $this->responsedFromCache = true;
                            return true;
                        }
                    }
                }
                else
                {
                    $this->logMessage('Invalid query: ' . mysql_error());
                }
            }
        }
        else
        {
            $this->logMessage('No cache table');
        }

        return false;
    }

    /**
    *@desc Inserts cached response in database
    */
    protected function insertResponseToDB()
    {
        if(!$this->enableCache || $this->responsedFromCache)
        {
            return;
        }

        if(!empty($this->cacheTableSuffix))
        {
            $response = addslashes($this->response->getRawResponse());

            $sql = 'INSERT INTO tbl_cache_shipping_' . $this->cacheTableSuffix . '(search_string, response)
                    VALUES(\''. addslashes($this->searchString) . '\', \'' . $response . '\')';

            if(!$this->dbConnection)
            {
                $this->connectDB();
            }

            if(@mysql_query($sql, $this->dbConnection))
            {
                $this->logMessage('Response inserted in to DB');
            }
            else
            {
                $this->logMessage('Response could not be inserted in to DB');
            }
        }
        else
        {
            $this->logMessage('No cache table');
        }
    }

    /**
    *@desc Deletes cached data
    */
    public function deleteInvalidData()
    {
        $cacheTables = array(   'tbl_cache_shipping_fedex',
                                'tbl_cache_shipping_ups',
                                'tbl_cache_shipping_usps',
                                'tbl_cache_shipping_canpost',
                                'tbl_cache_shipping_auspost');

        $this->connectDB();

        foreach($cacheTables as $table)
        {
            $sql = 'DELETE FROM ' . $table . '
                    WHERE DATEDIFF(CURDATE(), created_on) >= ' . $this->cacheTime;

            @mysql_query($sql, $this->dbConnection);
        }
    }

    public function setServices($services)
    {
        if(is_array($services))
        {
            $this->services = $services;
        }
        else
        {
            $this->services[] = $services;
        }
    }

    protected function getServices()
    {
        return $this->services;
    }
    
    //Anjuman | D: 04242012 (mmddyyyy)  --  for residential or commercial shipping address
    public function setShippingDestinationType($shipping_destination_type=1)
    {
          return $this->shippping_destination_type = $shipping_destination_type;
    }
    
    public function getShippingDestinationType()
    {
          return $this->shippping_destination_type;
    }
    
    //End of Anjuman
}
//End of file Carrier.php