<?php
/**
 * TSoapService and TSoapServer class file
 *
 * @author Knut Urdalen <knut.urdalen@gmail.com>
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @link http://www.pradosoft.com/
 * @copyright Copyright &copy; 2005-2014 PradoSoft
 * @license http://www.pradosoft.com/license/
 * @package System.Web.Services
 */


/**
 * TSoapServer class.
 *
 * TSoapServer is a wrapper of the PHP SoapServer class.
 * It associates a SOAP provider class to the SoapServer object.
 * It also manages the URI for the SOAP service and WSDL.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @package System.Web.Services
 * @since 3.1
 */
class TSoapServer extends TApplicationComponent
{
	const WSDL_CACHE_PREFIX='wsdl.';

	private $_id;
	private $_provider;

	private $_version='';
	private $_actor='';
	private $_encoding='';
	private $_uri='';
	private $_classMap;
	private $_persistent=false;
	private $_wsdlUri='';

	private $_requestedMethod;

	private $_server;

	/**
	 * @return string the ID of the SOAP server
	 */
	public function getID()
	{
		return $this->_id;
	}

	/**
	 * @param string the ID of the SOAP server
	 * @throws TInvalidDataValueException if the ID ends with '.wsdl'.
	 */
	public function setID($id)
	{
		if(strrpos($this->_id,'.wsdl')===strlen($this->_id)-5)
			throw new TInvalidDataValueException('soapserver_id_invalid',$id);
		$this->_id=$id;
	}

	/**
	 * Handles the SOAP request.
	 */
	public function run()
	{
		if(($provider=$this->getProvider())!==null)
		{
			Prado::using($provider);
			$providerClass=($pos=strrpos($provider,'.'))!==false?substr($provider,$pos+1):$provider;
			$this->guessMethodCallRequested($providerClass);
			$server=$this->createServer();
			$server->setClass($providerClass, $this);
			if($this->_persistent)
				$server->setPersistence(SOAP_PERSISTENCE_SESSION);
		}
		else
			$server=$this->createServer();
		try
		{
			$server->handle();
		}
		catch (Exception $e)
		{
			if($this->getApplication()->getMode()===TApplicationMode::Debug)
				$this->fault($e->getMessage(), $e->__toString());
			else
				$this->fault($e->getMessage());
		}
	}

	/**
	 * Generate a SOAP fault message.
	 * @param string message title
	 * @param mixed message details
	 * @param string message code, defalt is 'SERVER'.
	 * @param string actors
	 * @param string message name
	 */
	public function fault($title, $details='', $code='SERVER', $actor='', $name='')
	{
		Prado::trace('SOAP-Fault '.$code. ' '.$title.' : '.$details, 'System.Web.Services.TSoapService');
		$this->_server->fault($code, $title, $actor, $details, $name);
	}

	/**
	 * Guess the SOAP method request from the actual SOAP message
	 *
	 * @param string $class current handler class.
	 */
	protected function guessMethodCallRequested($class)
	{
		$namespace = $class.'wsdl';
		$message = file_get_contents("php://input");
		$matches= array();
		if(preg_match('/xmlns:([^=]+)="urn:'.$namespace.'"/', $message, $matches))
		{
			if(preg_match('/<'.$matches[1].':([a-zA-Z_]+[a-zA-Z0-9_]+)/', $message, $method))
			{
				$this->_requestedMethod = $method[1];
			}
		}
	}

	/**
	 * Soap method guessed from the SOAP message received.
	 * @return string soap method request, null if not found.
	 */
	public function getRequestedMethod()
	{
		return $this->_requestedMethod;
	}

	/**
	 * Creates the SoapServer instance.
	 * @return SoapServer
	 */
	protected function createServer()
	{
		if($this->_server===null)
		{
			if($this->getApplication()->getMode()===TApplicationMode::Debug)
				ini_set("soap.wsdl_cache_enabled",0);
			$this->_server = new SoapServer($this->getWsdlUri(),$this->getOptions());
		}
		return $this->_server;
	}

	/**
	 * @return array options for creating SoapServer instance
	 */
	protected function getOptions()
	{
		$options=array();
		if($this->_version==='1.1')
			$options['soap_version']=SOAP_1_1;
		else if($this->_version==='1.2')
			$options['soap_version']=SOAP_1_2;
		if(!empty($this->_actor))
			$options['actor']=$this->_actor;
		if(!empty($this->_encoding))
			$options['encoding']=$this->_encoding;
		if(!empty($this->_uri))
			$options['uri']=$this->_uri;
		if(is_string($this->_classMap))
		{
			foreach(preg_split('/\s*,\s*/', $this->_classMap) as $className)
				$options['classmap'][$className]=$className; //complex type uses the class name in the wsdl
		}
		return $options;
	}

	/**
	 * Returns the WSDL content of the SOAP server.
	 * If {@link getWsdlUri WsdlUri} is set, its content will be returned.
	 * If not, the {@link setProvider Provider} class will be investigated
	 * and the WSDL will be automatically genearted.
	 * @return string the WSDL content of the SOAP server
	 */
	public function getWsdl()
	{
		if($this->_wsdlUri==='')
		{
			$provider=$this->getProvider();
			$providerClass=($pos=strrpos($provider,'.'))!==false?substr($provider,$pos+1):$provider;
			Prado::using($provider);
			if($this->getApplication()->getMode()===TApplicationMode::Performance && ($cache=$this->getApplication()->getCache())!==null)
			{
				$wsdl=$cache->get(self::WSDL_CACHE_PREFIX.$providerClass);
				if(is_string($wsdl))
					return $wsdl;
				Prado::using('System.3rdParty.WsdlGen.WsdlGenerator');
				$wsdl=WsdlGenerator::generate($providerClass, $this->getUri(), $this->getEncoding());
				$cache->set(self::WSDL_CACHE_PREFIX.$providerClass,$wsdl);
				return $wsdl;
			}
			else
			{
				Prado::using('System.3rdParty.WsdlGen.WsdlGenerator');
				return WsdlGenerator::generate($providerClass, $this->getUri(), $this->getEncoding());
			}
		}
		else
			return file_get_contents($this->_wsdlUri);
	}

	/**
	 * @return string the URI for WSDL
	 */
	public function getWsdlUri()
	{
		if($this->_wsdlUri==='')
			return $this->getRequest()->getBaseUrl().$this->getService()->constructUrl($this->getID().'.wsdl',false);
		else
			return $this->_wsdlUri;
	}

	/**
	 * @param string the URI for WSDL
	 */
	public function setWsdlUri($value)
	{
		$this->_wsdlUri=$value;
	}

	/**
	 * @return string the URI for the SOAP service
	 */
	public function getUri()
	{
		if($this->_uri==='')
			return $this->getRequest()->getBaseUrl().$this->getService()->constructUrl($this->getID(),false);
		else
			return $this->_uri;
	}

	/**
	 * @param string the URI for the SOAP service
	 */
	public function setUri($uri)
	{
		$this->_uri=$uri;
	}

	/**
	 * @return string the SOAP provider class (in namespace format)
	 */
	public function getProvider()
	{
		return $this->_provider;
	}

	/**
	 * @param string the SOAP provider class (in namespace format)
	 */
	public function setProvider($provider)
	{
		$this->_provider=$provider;
	}

	/**
	 * @return string SOAP version, defaults to empty (meaning not set).
	 */
	public function getVersion()
	{
		return $this->_version;
	}

	/**
	 * @param string SOAP version, either '1.1' or '1.2'
	 * @throws TInvalidDataValueException if neither '1.1' nor '1.2'
	 */
	public function setVersion($value)
	{
		if($value==='1.1' || $value==='1.2' || $value==='')
			$this->_version=$value;
		else
			throw new TInvalidDataValueException('soapserver_version_invalid',$value);
	}

	/**
	 * @return string actor of the SOAP service
	 */
	public function getActor()
	{
		return $this->_actor;
	}

	/**
	 * @param string actor of the SOAP service
	 */
	public function setActor($value)
	{
		$this->_actor=$value;
	}

	/**
	 * @return string encoding of the SOAP service
	 */
	public function getEncoding()
	{
		return $this->_encoding;
	}

	/**
	 * @param string encoding of the SOAP service
	 */
	public function setEncoding($value)
	{
		$this->_encoding=$value;
	}

	/**
	 * @return boolean whether the SOAP service is persistent within session. Defaults to false.
	 */
	public function getSessionPersistent()
	{
		return $this->_persistent;
	}

	/**
	 * @param boolean whether the SOAP service is persistent within session.
	 */
	public function setSessionPersistent($value)
	{
		$this->_persistent=TPropertyValue::ensureBoolean($value);
	}

	/**
	 * @return string comma delimit list of complex type classes.
	 */
	public function getClassMaps()
	{
		return $this->_classMap;
	}

	/**
	 * @return string comma delimit list of class names
	 */
	public function setClassMaps($classes)
	{
		$this->_classMap = $classes;
	}
}