<?php namespace Davispeixoto\ForceDotComToolkitForPhp;

/*
 * Copyright (c) 2007, salesforce.com, inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*
*    Redistributions of source code must retain the above copyright notice, this list of conditions and the
*    following disclaimer.
*
*    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
*    the following disclaimer in the documentation and/or other materials provided with the distribution.
*
*    Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
*    promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
		* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
		* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

use Exception;
use stdClass;

class SObject
{
    public $type;
    public $fields;

    //	public $sobject;

    public function __construct($response = null)
    {
        if (!isset($response) && !$response) {
            return;
        }

        foreach ($response as $responseKey => $responseValue) {
            if (in_array($responseKey, array('Id', 'type', 'any'))) {
                continue;
            }
            $this->$responseKey = $responseValue;
        }

        if (isset($response->Id)) {
            $this->Id = is_array($response->Id) ? $response->Id[0] : $response->Id;
        }

        if (isset($response->type)) {
            $this->type = $response->type;
        }

        if (isset($response->any)) {
            try {
                if ($response->any instanceof stdClass) {
                    if ($this->isSObject($response->any)) {
                        $anArray = array();
                        $sobject = new SObject($response->any);
                        $anArray[] = $sobject;
                        $this->sobjects = $anArray;
                    } else {
                        $this->queryResult = new QueryResult($response->any);
                    }
                } else {
                    if (is_array($response->any)) {
                        $anArray = array();
                        foreach ($response->any as $key => $item) {
                            if ($item instanceof stdClass) {
                                if ($this->isSObject($item)) {
                                    $sobject = new SObject($item);
                                    $anArray[$key] = $sobject;
                                } else {
                                    if (!isset($this->queryResult)) {
                                        $this->queryResult = array();
                                    }
                                    array_push($this->queryResult, new QueryResult($item));
                                }
                            } else {
                                if (strpos($item, 'sf:') === false) {
                                    $currentXmlValue = sprintf('<sf:%s>%s</sf:%s>', $key, $item, $key);
                                } else {
                                    $currentXmlValue = $item;
                                }

                                if (!isset($fieldsToConvert)) {
                                    $fieldsToConvert = $currentXmlValue;
                                } else {
                                    $fieldsToConvert .= $currentXmlValue;
                                }
                            }
                        }

                        if (isset($fieldsToConvert)) {
                            $this->fields = $this->convertFields($fieldsToConvert);
                        }

                        if (sizeof($anArray) > 0) {
                            foreach ($anArray as $key => $children_sobject) {
                                $this->fields->$key = $children_sobject;
                            }
                        }
                    } else {
                        $this->fields = $this->convertFields($response->any);
                    }
                }
            } catch (Exception $e) {
                error_log('salesforce exception: ', $e->getMessage());
                error_log('salesforce exception: ', $e->getTraceAsString());
            }
        }
    }

    public function __get($name)
    {
        return (isset($this->fields->$name)) ? $this->fields->$name : false;
    }

    public function __isset($name)
    {
        return isset($this->fields->$name);
    }

    /**
     * Parse the "any" string from an sObject.  First strip out the sf: and then
     * enclose string with <Object></Object>.  Load the string using
     * simplexml_load_string and return an array that can be traversed.
     */
    public function convertFields($any)
    {
        $str = preg_replace('{sf:}', '', $any);

        $array = $this->xml2array('<Object xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' . $str . '</Object>',
            2);

        $xml = new stdClass();
        if (!count($array['Object'])) {
            return $xml;
        }

        foreach ($array['Object'] as $k => $v) {
            $xml->$k = $v;
        }

        return $xml;
    }

    /**
     *
     * @param string $contents
     * @return array
     */
    public function xml2array($contents, $get_attributes = 1)
    {
        if (!$contents) {
            return array();
        }

        if (!function_exists('xml_parser_create')) {
            return array('not found');
        }

        $parser = xml_parser_create();
        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
        xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
        xml_parse_into_struct($parser, $contents, $xml_values);
        xml_parser_free($parser);

        if (!$xml_values) {
            return;
        }

        //Initializations
        $xml_array = array();
        $parents = array();
        $opened_tags = array();
        $arr = array();

        $current = &$xml_array;

        foreach ($xml_values as $data) {
            unset($attributes, $value);//Remove existing values, or there will be trouble

            //This command will extract these variables into the foreach scope
            // tag(string), type(string), level(int), attributes(array).
            extract($data);//We could use the array by itself, but this cooler.

            $result = '';
            if ($get_attributes) {
                switch ($get_attributes) {
                    case 1:
                        $result = array();
                        if (isset($value)) {
                            $result['value'] = $value;
                        }

                        //Set the attributes too.
                        if (isset($attributes)) {
                            foreach ($attributes as $attr => $val) {
                                if ($get_attributes == 1) {
                                    $result['attr'][$attr] = $val;
                                } //Set all the attributes in a array called 'attr'
                                /**  :TODO: should we change the key name to '_attr'? Someone may use the tagname 'attr'. Same goes for 'value' too */
                            }
                        }
                        break;

                    case 2:
                        $result = array();
                        if (isset($value)) {
                            $result = $value;
                        }

                        //Check for nil and ignore other attributes.
                        if (isset($attributes) && isset($attributes['xsi:nil']) && !strcasecmp($attributes['xsi:nil'],
                                'true')
                        ) {
                            $result = null;
                        }
                        break;
                }
            } elseif (isset($value)) {
                $result = $value;
            }

            //See tag status and do the needed.
            if ($type == "open") {//The starting of the tag '<tag>'
                $parent[$level - 1] = &$current;

                if (!is_array($current) || (!in_array($tag, array_keys($current)))) { //Insert New tag
                    $current[$tag] = $result;
                    $current = &$current[$tag];

                } else { //There was another element with the same tag name
                    if (isset($current[$tag][0])) {
                        array_push($current[$tag], $result);
                    } else {
                        $current[$tag] = array($current[$tag], $result);
                    }
                    $last = count($current[$tag]) - 1;
                    $current = &$current[$tag][$last];
                }

            } elseif ($type == "complete") { //Tags that ends in 1 line '<tag />'
                //See if the key is already taken.
                if (!isset($current[$tag])) { //New Key
                    $current[$tag] = $result;

                } else { //If taken, put all things inside a list(array)
                    if ((is_array($current[$tag]) && $get_attributes == 0)//If it is already an array...
                        || (isset($current[$tag][0]) && is_array($current[$tag][0]) && ($get_attributes == 1 || $get_attributes == 2))
                    ) {
                        array_push($current[$tag], $result); // ...push the new element into that array.
                    } else { //If it is not an array...
                        $current[$tag] = array(
                            $current[$tag],
                            $result
                        ); //...Make it an array using using the existing value and the new value
                    }
                }

            } elseif ($type == 'close') { //End of tag '</tag>'
                $current = &$parent[$level - 1];
            }
        }

        return ($xml_array);
    }

    /*
     * If the stdClass has a done, we know it is a QueryResult
    */
    public function isQueryResult($param)
    {
        return isset($param->done);
    }

    /*
     * If the stdClass has a type, we know it is an SObject
    */
    public function isSObject($param)
    {
        return isset($param->type);
    }
}

?>
