summaryrefslogtreecommitdiff
path: root/lib/prado/framework/Web/Services
diff options
context:
space:
mode:
authoremkael <emkael@tlen.pl>2016-02-24 23:18:07 +0100
committeremkael <emkael@tlen.pl>2016-02-24 23:18:07 +0100
commit6f7fdef0f500cd4bb540affd3bc1482243f337c1 (patch)
tree4853eecd0769a903e6130c1896e1d070848150dd /lib/prado/framework/Web/Services
parent61f2ea48a4e11cb5fb941b3783e19c9e9ef38a45 (diff)
* Prado 3.3.0
Diffstat (limited to 'lib/prado/framework/Web/Services')
-rw-r--r--lib/prado/framework/Web/Services/TFeedService.php187
-rw-r--r--lib/prado/framework/Web/Services/TJsonService.php209
-rw-r--r--lib/prado/framework/Web/Services/TPageService.php890
-rw-r--r--lib/prado/framework/Web/Services/TRpcService.php722
-rw-r--r--lib/prado/framework/Web/Services/TSoapService.php622
5 files changed, 2630 insertions, 0 deletions
diff --git a/lib/prado/framework/Web/Services/TFeedService.php b/lib/prado/framework/Web/Services/TFeedService.php
new file mode 100644
index 0000000..dc9c064
--- /dev/null
+++ b/lib/prado/framework/Web/Services/TFeedService.php
@@ -0,0 +1,187 @@
+<?php
+/**
+ * TFeedService and TFeed class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @author Knut Urdalen <knut.urdalen@gmail.com>
+ * @link https://github.com/pradosoft/prado
+ * @copyright Copyright &copy; 2005-2015 The PRADO Group
+ * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
+ * @version $Id$
+ * @package System.Web.Services
+ */
+
+/**
+ * TFeedService class
+ *
+ * TFeedService provides to end-users feed content.
+ *
+ * TFeedService manages a set of feeds. The service parameter, referring
+ * to the ID of the feed, specifies which feed content to be provided to end-users.
+ *
+ * To use TFeedService, configure it in application configuration as follows,
+ * <code>
+ * <service id="feed" class="System.Web.Services.TFeedService">
+ * <feed id="ch1" class="Path.To.FeedClass1" .../>
+ * <feed id="ch2" class="Path.To.FeedClass2" .../>
+ * <feed id="ch3" class="Path.To.FeedClass3" .../>
+ * </service>
+ * </code>
+ * where each &lt;feed&gt; element specifies a feed identified by its "id" value (case-sensitive).
+ *
+ * PHP configuration style:
+ * <code>
+ * array(
+ * 'feed' => array(
+ * 'ch1' => array(
+ * 'class' => 'Path.To.FeedClass1',
+ * 'properties' => array(
+ * ...
+ * ),
+ * ),
+ * )
+ * </code>
+ *
+ * The class attribute indicates which PHP class will provide the actual feed
+ * content. Note, the class must implement {@link IFeedContentProvider} interface.
+ * Other initial properties for the feed class may also be specified in the
+ * corresponding &lt;feed&gt; element.
+ *
+ * To retrieve the feed content identified by "ch2", use the URL
+ * <code>/path/to/index.php?feed=ch2</code>
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @author Knut Urdalen <knut.urdalen@gmail.com>
+ * @author Carl G. Mathisen <carlgmathisen@gmail.com>
+ * @package System.Web.Services
+ * @since 3.1
+ */
+class TFeedService extends TService
+{
+ private $_feeds=array();
+
+ /**
+ * Initializes this module.
+ * This method is required by the IModule interface.
+ * @param mixed configuration for this module, can be null
+ */
+ public function init($config)
+ {
+ if($this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP)
+ {
+ if(is_array($config))
+ {
+ foreach($config as $id => $feed)
+ $this->_feeds[$id] = $feed;
+ }
+ }
+ else
+ {
+ foreach($config->getElementsByTagName('feed') as $feed)
+ {
+ if(($id=$feed->getAttributes()->remove('id'))!==null)
+ $this->_feeds[$id]=$feed;
+ else
+ throw new TConfigurationException('feedservice_id_required');
+ }
+ }
+ }
+
+ /**
+ * @return string the requested feed path
+ */
+ protected function determineRequestedFeedPath()
+ {
+ return $this->getRequest()->getServiceParameter();
+ }
+
+ /**
+ * Runs the service.
+ * This method is invoked by application automatically.
+ */
+ public function run()
+ {
+ $id=$this->getRequest()->getServiceParameter();
+ if(isset($this->_feeds[$id]))
+ {
+ $feedConfig=$this->_feeds[$id];
+ $properties = array();
+ $feed = null;
+ if($this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP)
+ {
+ if(isset($feedConfig['class']))
+ {
+ $feed=Prado::createComponent($feedConfig['class']);
+ if($service instanceof IFeedContentProvider)
+ $properties=isset($feedConfig['properties'])?$feedConfig['properties']:array();
+ else
+ throw new TConfigurationException('jsonservice_response_type_invalid',$id);
+ }
+ else
+ throw new TConfigurationException('jsonservice_class_required',$id);
+ }
+ else
+ {
+ $properties=$feedConfig->getAttributes();
+ if(($class=$properties->remove('class'))!==null)
+ {
+ $feed=Prado::createComponent($class);
+ if(!($feed instanceof IFeedContentProvider))
+ throw new TConfigurationException('feedservice_feedtype_invalid',$id);
+ }
+ else
+ throw new TConfigurationException('feedservice_class_required',$id);
+ }
+
+ // init feed properties
+ foreach($properties as $name=>$value)
+ $feed->setSubproperty($name,$value);
+ $feed->init($feedConfig);
+
+ $content=$feed->getFeedContent();
+ //$this->getResponse()->setContentType('application/rss+xml');
+ $this->getResponse()->setContentType($feed->getContentType());
+ $this->getResponse()->write($content);
+ }
+ else
+ throw new THttpException(404,'feedservice_feed_unknown',$id);
+ }
+}
+
+/**
+ * IFeedContentProvider interface.
+ *
+ * IFeedContentProvider interface must be implemented by a feed class who
+ * provides feed content.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @author Knut Urdalen <knut.urdalen@gmail.com>
+ * @package System.Web.Services
+ * @since 3.1
+ */
+interface IFeedContentProvider
+{
+ /**
+ * Initializes the feed content provider.
+ * This method is invoked (before {@link getFeedContent})
+ * when the feed provider is requested by a user.
+ * @param TXmlElement configurations specified within the &lt;feed&gt; element
+ * corresponding to this feed provider when configuring {@link TFeedService}.
+ */
+ public function init($config);
+ /**
+ * @return string feed content in proper XML format
+ */
+ public function getFeedContent();
+ /**
+ * Sets the content type of the feed content to be sent.
+ * Some examples are:
+ * RSS 1.0 feed: application/rdf+xml
+ * RSS 2.0 feed: application/rss+xml or application/xml or text/xml
+ * ATOM feed: application/atom+xml
+ * @return string the content type for the feed content.
+ * @since 3.1.1
+ */
+ public function getContentType();
+}
+
diff --git a/lib/prado/framework/Web/Services/TJsonService.php b/lib/prado/framework/Web/Services/TJsonService.php
new file mode 100644
index 0000000..d17b330
--- /dev/null
+++ b/lib/prado/framework/Web/Services/TJsonService.php
@@ -0,0 +1,209 @@
+<?php
+/**
+ * TJsonService and TJsonResponse class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
+ * @link https://github.com/pradosoft/prado
+ * @copyright Copyright &copy; 2005-2015 The PRADO Group
+ * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
+ * @package System.Web.Services
+ */
+
+/**
+ * TJsonService class provides to end-users javascript content response in
+ * JSON format.
+ *
+ * TJsonService manages a set of {@link TJsonResponse}, each
+ * representing specific response with javascript content.
+ * The service parameter, referring to the ID of the service, specifies
+ * which javascript content to be provided to end-users.
+ *
+ * To use TJsonService, configure it in application configuration as follows,
+ * <code>
+ * <service id="json" class="System.Web.Services.TJsonService">
+ * <json id="get_article" class="Path.To.JsonResponseClass1" .../>
+ * <json id="register_rating" class="Path.To.JsonResponseClass2" .../>
+ * </service>
+ * </code>
+ * where each JSON response is specified via a &lt;json&gt; element.
+ * Initial property values can be configured in a &lt;json&gt; element.
+ *
+ *
+ * PHP configuration style:
+ * <code>
+ * 'services' => array(
+ * 'get_article' => array(
+ * 'class' => 'Path.To.JsonResponseClass1',
+ * 'properties' => array(
+ * ...
+ * )
+ * )
+ * )
+ * </code>
+ *
+ * To retrieve the JSON content provided by "get_article", use the URL
+ * <code>index.php?json=get_article</code>
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @author Carl G. Mathisen <carlgmathisen@gmail.com>
+ * @package System.Web.Services
+ * @since 3.1
+ */
+class TJsonService extends TService
+{
+ /**
+ * @var array registered services
+ */
+ private $_services=array();
+
+ /**
+ * Initializes this module.
+ * This method is required by the IModule interface.
+ * @param mixed configuration for this module, can be null
+ */
+ public function init($xml)
+ {
+ $this->loadJsonServices($xml);
+ }
+
+ /**
+ * Load the service definitions.
+ * @param mixed configuration for this module, can be null
+ */
+ protected function loadJsonServices($config)
+ {
+ if($this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP)
+ {
+ if(is_array($config))
+ {
+ foreach($config['json'] as $id => $json)
+ $this->_services[$id] = $json;
+ }
+ }
+ else
+ {
+ foreach($config->getElementsByTagName('json') as $json)
+ {
+ if(($id=$json->getAttribute('id'))!==null)
+ $this->_services[$id]=$json;
+ else
+ throw new TConfigurationException('jsonservice_id_required');
+ }
+ }
+ }
+
+ /**
+ * Runs the service.
+ * This method is invoked by application automatically.
+ */
+ public function run()
+ {
+ $id=$this->getRequest()->getServiceParameter();
+ if(isset($this->_services[$id]))
+ {
+ $serviceConfig=$this->_services[$id];
+ if($this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP)
+ {
+ if(isset($serviceConfig['class']))
+ {
+ $service=Prado::createComponent($serviceConfig['class']);
+ if($service instanceof TJsonResponse)
+ {
+ $properties = isset($serviceConfig['properties'])?$serviceConfig['properties']:array();
+ $this->createJsonResponse($service,$properties,$serviceConfig);
+ }
+ else
+ throw new TConfigurationException('jsonservice_response_type_invalid',$id);
+ }
+ else
+ throw new TConfigurationException('jsonservice_class_required',$id);
+ }
+ else
+ {
+ $properties=$serviceConfig->getAttributes();
+ if(($class=$properties->remove('class'))!==null)
+ {
+ $service=Prado::createComponent($class);
+ if($service instanceof TJsonResponse)
+ $this->createJsonResponse($service,$properties,$serviceConfig);
+ else
+ throw new TConfigurationException('jsonservice_response_type_invalid',$id);
+ }
+ else
+ throw new TConfigurationException('jsonservice_class_required',$id);
+ }
+ }
+ else
+ throw new THttpException(404,'jsonservice_provider_unknown',$id);
+ }
+
+ /**
+ * Renders content provided by TJsonResponse::getJsonContent() as
+ * javascript in JSON format.
+ */
+ protected function createJsonResponse($service,$properties,$config)
+ {
+ // init service properties
+ foreach($properties as $name=>$value)
+ $service->setSubproperty($name,$value);
+ $service->init($config);
+
+ //send content if not null
+ if(($content=$service->getJsonContent())!==null)
+ {
+ $response = $this->getResponse();
+ $response->setContentType('application/json');
+ $response->setCharset('UTF-8');
+ //send content
+ $response->write(TJavaScript::jsonEncode($content));
+ }
+ }
+}
+
+/**
+ * TJsonResponse Class
+ *
+ * TJsonResponse is the base class for all JSON response provider classes.
+ *
+ * Derived classes must implement {@link getJsonContent()} to return
+ * an object or literals to be converted to JSON format. The response
+ * will be empty if the returned content is null.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @package System.Web.Services
+ * @since 3.1
+ */
+abstract class TJsonResponse extends TApplicationComponent
+{
+ private $_id='';
+
+ /**
+ * Initializes the feed.
+ * @param TXmlElement configurations specified in {@link TJsonService}.
+ */
+ public function init($config)
+ {
+ }
+
+ /**
+ * @return string ID of this response
+ */
+ public function getID()
+ {
+ return $this->_id;
+ }
+
+ /**
+ * @param string ID of this response
+ */
+ public function setID($value)
+ {
+ $this->_id=$value;
+ }
+
+ /**
+ * @return object json response content, null to suppress output.
+ */
+ abstract public function getJsonContent();
+}
+
diff --git a/lib/prado/framework/Web/Services/TPageService.php b/lib/prado/framework/Web/Services/TPageService.php
new file mode 100644
index 0000000..4a422c4
--- /dev/null
+++ b/lib/prado/framework/Web/Services/TPageService.php
@@ -0,0 +1,890 @@
+<?php
+/**
+ * TPageService class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link https://github.com/pradosoft/prado
+ * @copyright Copyright &copy; 2005-2015 The PRADO Group
+ * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
+ * @package System.Web.Services
+ */
+
+/**
+ * Include classes to be used by page service
+ */
+Prado::using('System.Web.UI.TPage');
+Prado::using('System.Web.UI.TTemplateManager');
+Prado::using('System.Web.UI.TThemeManager');
+
+/**
+ * TPageService class.
+ *
+ * TPageService implements the service for serving user page requests.
+ *
+ * Pages that are available to client users are stored under a directory specified by
+ * {@link setBasePath BasePath}. The directory may contain subdirectories.
+ * Pages serving for a similar goal are usually placed under the same directory.
+ * A directory may contain a configuration file <b>config.xml</b> whose content
+ * is similar to that of application configuration file.
+ *
+ * A page is requested via page path, which is a dot-connected directory names
+ * appended by the page name. Assume '<BasePath>/Users/Admin' is the directory
+ * containing the page 'Update'. Then the page can be requested via 'Users.Admin.Update'.
+ * By default, the {@link setBasePath BasePath} of the page service is the "pages"
+ * directory under the application base path. You may change this default
+ * by setting {@link setBasePath BasePath} with a different path you prefer.
+ *
+ * Page name refers to the file name (without extension) of the page template.
+ * In order to differentiate from the common control template files, the extension
+ * name of the page template files must be '.page'. If there is a PHP file with
+ * the same page name under the same directory as the template file, that file
+ * will be considered as the page class file and the file name is the page class name.
+ * If such a file is not found, the page class is assumed as {@link TPage}.
+ *
+ * Modules can be configured and loaded in page directory configurations.
+ * Configuration of a module in a subdirectory will overwrite its parent
+ * directory's configuration, if both configurations refer to the same module.
+ *
+ * By default, TPageService will automatically load two modules:
+ * - {@link TTemplateManager} : manages page and control templates
+ * - {@link TThemeManager} : manages themes used in a Prado application
+ *
+ * In page directory configurations, static authorization rules can also be specified,
+ * which governs who and which roles can access particular pages.
+ * Refer to {@link TAuthorizationRule} for more details about authorization rules.
+ * Page authorization rules can be configured within an <authorization> tag in
+ * each page directory configuration as follows,
+ * <authorization>
+ * <deny pages="Update" users="?" />
+ * <allow pages="Admin" roles="administrator" />
+ * <deny pages="Admin" users="*" />
+ * </authorization>
+ * where the 'pages' attribute may be filled with a sequence of comma-separated
+ * page IDs. If 'pages' attribute does not appear in a rule, the rule will be
+ * applied to all pages in this directory and all subdirectories (recursively).
+ * Application of authorization rules are in a bottom-up fashion, starting from
+ * the directory containing the requested page up to all parent directories.
+ * The first matching rule will be used. The last rule always allows all users
+ * accessing to any resources.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @author Carl G. Mathisen <carlgmathisen@gmail.com>
+ * @package System.Web.Services
+ * @since 3.0
+ */
+class TPageService extends TService
+{
+ /**
+ * Configuration file name
+ */
+ const CONFIG_FILE_XML='config.xml';
+ /**
+ * Configuration file name
+ */
+ const CONFIG_FILE_PHP='config.php';
+ /**
+ * Default base path
+ */
+ const DEFAULT_BASEPATH='Pages';
+ /**
+ * Fallback base path - used to be the default up to Prado < 3.2
+ */
+ const FALLBACK_BASEPATH='pages';
+ /**
+ * Prefix of ID used for storing parsed configuration in cache
+ */
+ const CONFIG_CACHE_PREFIX='prado:pageservice:';
+ /**
+ * Page template file extension
+ */
+ const PAGE_FILE_EXT='.page';
+ /**
+ * @var string root path of pages
+ */
+ private $_basePath=null;
+ /**
+ * @var string base path class in namespace format
+ */
+ private $_basePageClass='TPage';
+ /**
+ * @var string clientscript manager class in namespace format
+ * @since 3.1.7
+ */
+ private $_clientScriptManagerClass='System.Web.UI.TClientScriptManager';
+ /**
+ * @var string default page
+ */
+ private $_defaultPage='Home';
+ /**
+ * @var string requested page (path)
+ */
+ private $_pagePath=null;
+ /**
+ * @var TPage the requested page
+ */
+ private $_page=null;
+ /**
+ * @var array list of initial page property values
+ */
+ private $_properties=array();
+ /**
+ * @var boolean whether service is initialized
+ */
+ private $_initialized=false;
+ /**
+ * @var TThemeManager theme manager
+ */
+ private $_themeManager=null;
+ /**
+ * @var TTemplateManager template manager
+ */
+ private $_templateManager=null;
+
+ /**
+ * Initializes the service.
+ * This method is required by IService interface and is invoked by application.
+ * @param TXmlElement service configuration
+ */
+ public function init($config)
+ {
+ Prado::trace("Initializing TPageService",'System.Web.Services.TPageService');
+
+ $pageConfig=$this->loadPageConfig($config);
+
+ $this->initPageContext($pageConfig);
+
+ $this->_initialized=true;
+ }
+
+ /**
+ * Initializes page context.
+ * Page context includes path alias settings, namespace usages,
+ * parameter initialization, module loadings, page initial properties
+ * and authorization rules.
+ * @param TPageConfiguration
+ */
+ protected function initPageContext($pageConfig)
+ {
+ $application=$this->getApplication();
+ foreach($pageConfig->getApplicationConfigurations() as $appConfig)
+ $application->applyConfiguration($appConfig);
+
+ $this->applyConfiguration($pageConfig);
+ }
+
+ /**
+ * Applies a page configuration.
+ * @param TPageConfiguration the configuration
+ */
+ protected function applyConfiguration($config)
+ {
+ // initial page properties (to be set when page runs)
+ $this->_properties=array_merge($this->_properties, $config->getProperties());
+ $this->getApplication()->getAuthorizationRules()->mergeWith($config->getRules());
+ $pagePath=$this->getRequestedPagePath();
+ // external configurations
+ foreach($config->getExternalConfigurations() as $filePath=>$params)
+ {
+ list($configPagePath,$condition)=$params;
+ if($condition!==true)
+ $condition=$this->evaluateExpression($condition);
+ if($condition)
+ {
+ if(($path=Prado::getPathOfNamespace($filePath,Prado::getApplication()->getConfigurationFileExt()))===null || !is_file($path))
+ throw new TConfigurationException('pageservice_includefile_invalid',$filePath);
+ $c=new TPageConfiguration($pagePath);
+ $c->loadFromFile($path,$configPagePath);
+ $this->applyConfiguration($c);
+ }
+ }
+
+ }
+
+ /**
+ * Determines the requested page path.
+ * @return string page path requested
+ */
+ protected function determineRequestedPagePath()
+ {
+ $pagePath=$this->getRequest()->getServiceParameter();
+ if(empty($pagePath))
+ $pagePath=$this->getDefaultPage();
+ return $pagePath;
+ }
+
+ /**
+ * Collects configuration for a page.
+ * @param TXmlElement additional configuration specified in the application configuration
+ * @return TPageConfiguration
+ */
+ protected function loadPageConfig($config)
+ {
+ $application=$this->getApplication();
+ $pagePath=$this->getRequestedPagePath();
+ if(($cache=$application->getCache())===null)
+ {
+ $pageConfig=new TPageConfiguration($pagePath);
+ if($config!==null)
+ {
+ if($application->getConfigurationType()==TApplication::CONFIG_TYPE_PHP)
+ $pageConfig->loadPageConfigurationFromPhp($config,$application->getBasePath(),'');
+ else
+ $pageConfig->loadPageConfigurationFromXml($config,$application->getBasePath(),'');
+ }
+ $pageConfig->loadFromFiles($this->getBasePath());
+ }
+ else
+ {
+ $configCached=true;
+ $currentTimestamp=array();
+ $arr=$cache->get(self::CONFIG_CACHE_PREFIX.$this->getID().$pagePath);
+ if(is_array($arr))
+ {
+ list($pageConfig,$timestamps)=$arr;
+ if($application->getMode()!==TApplicationMode::Performance)
+ {
+ foreach($timestamps as $fileName=>$timestamp)
+ {
+ if($fileName===0) // application config file
+ {
+ $appConfigFile=$application->getConfigurationFile();
+ $currentTimestamp[0]=$appConfigFile===null?0:@filemtime($appConfigFile);
+ if($currentTimestamp[0]>$timestamp || ($timestamp>0 && !$currentTimestamp[0]))
+ $configCached=false;
+ }
+ else
+ {
+ $currentTimestamp[$fileName]=@filemtime($fileName);
+ if($currentTimestamp[$fileName]>$timestamp || ($timestamp>0 && !$currentTimestamp[$fileName]))
+ $configCached=false;
+ }
+ }
+ }
+ }
+ else
+ {
+ $configCached=false;
+ $paths=explode('.',$pagePath);
+ $configPath=$this->getBasePath();
+ $fileName = $this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP
+ ? self::CONFIG_FILE_PHP
+ : self::CONFIG_FILE_XML;
+ foreach($paths as $path)
+ {
+ $configFile=$configPath.DIRECTORY_SEPARATOR.$fileName;
+ $currentTimestamp[$configFile]=@filemtime($configFile);
+ $configPath.=DIRECTORY_SEPARATOR.$path;
+ }
+ $appConfigFile=$application->getConfigurationFile();
+ $currentTimestamp[0]=$appConfigFile===null?0:@filemtime($appConfigFile);
+ }
+ if(!$configCached)
+ {
+ $pageConfig=new TPageConfiguration($pagePath);
+ if($config!==null)
+ {
+ if($application->getConfigurationType()==TApplication::CONFIG_TYPE_PHP)
+ $pageConfig->loadPageConfigurationFromPhp($config,$application->getBasePath(),'');
+ else
+ $pageConfig->loadPageConfigurationFromXml($config,$application->getBasePath(),'');
+ }
+ $pageConfig->loadFromFiles($this->getBasePath());
+ $cache->set(self::CONFIG_CACHE_PREFIX.$this->getID().$pagePath,array($pageConfig,$currentTimestamp));
+ }
+ }
+ return $pageConfig;
+ }
+
+ /**
+ * @return TTemplateManager template manager
+ */
+ public function getTemplateManager()
+ {
+ if(!$this->_templateManager)
+ {
+ $this->_templateManager=new TTemplateManager;
+ $this->_templateManager->init(null);
+ }
+ return $this->_templateManager;
+ }
+
+ /**
+ * @param TTemplateManager template manager
+ */
+ public function setTemplateManager(TTemplateManager $value)
+ {
+ $this->_templateManager=$value;
+ }
+
+ /**
+ * @return TThemeManager theme manager
+ */
+ public function getThemeManager()
+ {
+ if(!$this->_themeManager)
+ {
+ $this->_themeManager=new TThemeManager;
+ $this->_themeManager->init(null);
+ }
+ return $this->_themeManager;
+ }
+
+ /**
+ * @param TThemeManager theme manager
+ */
+ public function setThemeManager(TThemeManager $value)
+ {
+ $this->_themeManager=$value;
+ }
+
+ /**
+ * @return string the requested page path
+ */
+ public function getRequestedPagePath()
+ {
+ if($this->_pagePath===null)
+ {
+ $this->_pagePath=strtr($this->determineRequestedPagePath(),'/\\','..');
+ if(empty($this->_pagePath))
+ throw new THttpException(404,'pageservice_page_required');
+ }
+ return $this->_pagePath;
+ }
+
+ /**
+ * @return TPage the requested page
+ */
+ public function getRequestedPage()
+ {
+ return $this->_page;
+ }
+
+ /**
+ * @return string default page path to be served if no explicit page is request. Defaults to 'Home'.
+ */
+ public function getDefaultPage()
+ {
+ return $this->_defaultPage;
+ }
+
+ /**
+ * @param string default page path to be served if no explicit page is request
+ * @throws TInvalidOperationException if the page service is initialized
+ */
+ public function setDefaultPage($value)
+ {
+ if($this->_initialized)
+ throw new TInvalidOperationException('pageservice_defaultpage_unchangeable');
+ else
+ $this->_defaultPage=$value;
+ }
+
+ /**
+ * @return string the URL for the default page
+ */
+ public function getDefaultPageUrl()
+ {
+ return $this->constructUrl($this->getDefaultPage());
+ }
+
+ /**
+ * @return string the root directory for storing pages. Defaults to the 'pages' directory under the application base path.
+ */
+ public function getBasePath()
+ {
+ if($this->_basePath===null)
+ {
+ $basePath=$this->getApplication()->getBasePath().DIRECTORY_SEPARATOR.self::DEFAULT_BASEPATH;
+ if(($this->_basePath=realpath($basePath))===false || !is_dir($this->_basePath))
+ {
+ $basePath=$this->getApplication()->getBasePath().DIRECTORY_SEPARATOR.self::FALLBACK_BASEPATH;
+ if(($this->_basePath=realpath($basePath))===false || !is_dir($this->_basePath))
+ throw new TConfigurationException('pageservice_basepath_invalid',$basePath);
+ }
+ }
+ return $this->_basePath;
+ }
+
+ /**
+ * @param string root directory (in namespace form) storing pages
+ * @throws TInvalidOperationException if the service is initialized already or basepath is invalid
+ */
+ public function setBasePath($value)
+ {
+ if($this->_initialized)
+ throw new TInvalidOperationException('pageservice_basepath_unchangeable');
+ else if(($path=Prado::getPathOfNamespace($value))===null || !is_dir($path))
+ throw new TConfigurationException('pageservice_basepath_invalid',$value);
+ $this->_basePath=realpath($path);
+ }
+
+ /**
+ * Sets the base page class name (in namespace format).
+ * If a page only has a template file without page class file,
+ * this base page class will be instantiated.
+ * @param string class name
+ */
+ public function setBasePageClass($value)
+ {
+ $this->_basePageClass=$value;
+ }
+
+ /**
+ * @return string base page class name in namespace format. Defaults to 'TPage'.
+ */
+ public function getBasePageClass()
+ {
+ return $this->_basePageClass;
+ }
+
+ /**
+ * Sets the clientscript manager class (in namespace format).
+ * @param string class name
+ * @since 3.1.7
+ */
+ public function setClientScriptManagerClass($value)
+ {
+ $this->_clientScriptManagerClass=$value;
+ }
+
+ /**
+ * @return string clientscript manager class in namespace format. Defaults to 'System.Web.UI.TClientScriptManager'.
+ * @since 3.1.7
+ */
+ public function getClientScriptManagerClass()
+ {
+ return $this->_clientScriptManagerClass;
+ }
+
+ /**
+ * Runs the service.
+ * This will create the requested page, initializes it with the property values
+ * specified in the configuration, and executes the page.
+ */
+ public function run()
+ {
+ Prado::trace("Running page service",'System.Web.Services.TPageService');
+ $this->_page=$this->createPage($this->getRequestedPagePath());
+ $this->runPage($this->_page,$this->_properties);
+ }
+
+ /**
+ * Creates a page instance based on requested page path.
+ * @param string requested page path
+ * @return TPage the requested page instance
+ * @throws THttpException if requested page path is invalid
+ * @throws TConfigurationException if the page class cannot be found
+ */
+ protected function createPage($pagePath)
+ {
+ $path=$this->getBasePath().DIRECTORY_SEPARATOR.strtr($pagePath,'.',DIRECTORY_SEPARATOR);
+ $hasTemplateFile=is_file($path.self::PAGE_FILE_EXT);
+ $hasClassFile=is_file($path.Prado::CLASS_FILE_EXT);
+
+ if(!$hasTemplateFile && !$hasClassFile)
+ throw new THttpException(404,'pageservice_page_unknown',$pagePath);
+
+ if($hasClassFile)
+ {
+ $className=basename($path);
+ if(!class_exists($className,false))
+ include_once($path.Prado::CLASS_FILE_EXT);
+ }
+ else
+ {
+ $className=$this->getBasePageClass();
+ Prado::using($className);
+ if(($pos=strrpos($className,'.'))!==false)
+ $className=substr($className,$pos+1);
+ }
+
+ if(!class_exists($className,false) || ($className!=='TPage' && !is_subclass_of($className,'TPage')))
+ throw new THttpException(404,'pageservice_page_unknown',$pagePath);
+
+ $page=Prado::createComponent($className);
+ $page->setPagePath($pagePath);
+
+ if($hasTemplateFile)
+ $page->setTemplate($this->getTemplateManager()->getTemplateByFileName($path.self::PAGE_FILE_EXT));
+
+ return $page;
+ }
+
+ /**
+ * Executes a page.
+ * @param TPage the page instance to be run
+ * @param array list of initial page properties
+ */
+ protected function runPage($page,$properties)
+ {
+ foreach($properties as $name=>$value)
+ $page->setSubProperty($name,$value);
+ $page->run($this->getResponse()->createHtmlWriter());
+ }
+
+ /**
+ * Constructs a URL with specified page path and GET parameters.
+ * @param string page path
+ * @param array list of GET parameters, null if no GET parameters required
+ * @param boolean whether to encode the ampersand in URL, defaults to true.
+ * @param boolean whether to encode the GET parameters (their names and values), defaults to true.
+ * @return string URL for the page and GET parameters
+ */
+ public function constructUrl($pagePath,$getParams=null,$encodeAmpersand=true,$encodeGetItems=true)
+ {
+ return $this->getRequest()->constructUrl($this->getID(),$pagePath,$getParams,$encodeAmpersand,$encodeGetItems);
+ }
+}
+
+
+/**
+ * TPageConfiguration class
+ *
+ * TPageConfiguration represents the configuration for a page.
+ * The page is specified by a dot-connected path.
+ * Configurations along this path are merged together to be provided for the page.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Web.Services
+ * @since 3.0
+ */
+class TPageConfiguration extends TComponent
+{
+ /**
+ * @var array list of application configurations
+ */
+ private $_appConfigs=array();
+ /**
+ * @var array list of page initial property values
+ */
+ private $_properties=array();
+ /**
+ * @var TAuthorizationRuleCollection list of authorization rules
+ */
+ private $_rules=array();
+ /**
+ * @var array list of included configurations
+ */
+ private $_includes=array();
+ /**
+ * @var string the currently request page in the format of Path.To.PageName
+ */
+ private $_pagePath='';
+
+ /**
+ * Constructor.
+ * @param string the currently request page in the format of Path.To.PageName
+ */
+ public function __construct($pagePath)
+ {
+ $this->_pagePath=$pagePath;
+ }
+
+ /**
+ * @return array list of external configuration files. Each element is like $filePath=>$condition
+ */
+ public function getExternalConfigurations()
+ {
+ return $this->_includes;
+ }
+
+ /**
+ * Returns list of page initial property values.
+ * Each array element represents a single property with the key
+ * being the property name and the value the initial property value.
+ * @return array list of page initial property values
+ */
+ public function getProperties()
+ {
+ return $this->_properties;
+ }
+
+ /**
+ * Returns list of authorization rules.
+ * The authorization rules are aggregated (bottom-up) from configuration files
+ * along the path to the specified page.
+ * @return TAuthorizationRuleCollection collection of authorization rules
+ */
+ public function getRules()
+ {
+ return $this->_rules;
+ }
+
+ /**
+ * @return array list of application configurations specified along page path
+ */
+ public function getApplicationConfigurations()
+ {
+ return $this->_appConfigs;
+ }
+
+ /**
+ * Loads configuration for a page specified in a path format.
+ * @param string root path for pages
+ */
+ public function loadFromFiles($basePath)
+ {
+ $paths=explode('.',$this->_pagePath);
+ $page=array_pop($paths);
+ $path=$basePath;
+ $configPagePath='';
+ $fileName = Prado::getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP
+ ? TPageService::CONFIG_FILE_PHP
+ : TPageService::CONFIG_FILE_XML;
+ foreach($paths as $p)
+ {
+ $this->loadFromFile($path.DIRECTORY_SEPARATOR.$fileName,$configPagePath);
+ $path.=DIRECTORY_SEPARATOR.$p;
+ if($configPagePath==='')
+ $configPagePath=$p;
+ else
+ $configPagePath.='.'.$p;
+ }
+ $this->loadFromFile($path.DIRECTORY_SEPARATOR.$fileName,$configPagePath);
+ $this->_rules=new TAuthorizationRuleCollection($this->_rules);
+ }
+
+ /**
+ * Loads a specific config file.
+ * @param string config file name
+ * @param string the page path that the config file is associated with. The page path doesn't include the page name.
+ */
+ public function loadFromFile($fname,$configPagePath)
+ {
+ Prado::trace("Loading page configuration file $fname",'System.Web.Services.TPageService');
+ if(empty($fname) || !is_file($fname))
+ return;
+
+ if(Prado::getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP)
+ {
+ $fcontent = include $fname;
+ $this->loadFromPhp($fcontent,dirname($fname),$configPagePath);
+ }
+ else
+ {
+ $dom=new TXmlDocument;
+ if($dom->loadFromFile($fname))
+ $this->loadFromXml($dom,dirname($fname),$configPagePath);
+ else
+ throw new TConfigurationException('pageserviceconf_file_invalid',$fname);
+ }
+ }
+
+ public function loadFromPhp($config,$configPath,$configPagePath)
+ {
+ $this->loadApplicationConfigurationFromPhp($config,$configPath);
+ $this->loadPageConfigurationFromPhp($config,$configPath,$configPagePath);
+ }
+
+ /**
+ * Loads a page configuration.
+ * The configuration includes information for both application
+ * and page service.
+ * @param TXmlElement config xml element
+ * @param string the directory containing this configuration
+ * @param string the page path that the config XML is associated with. The page path doesn't include the page name.
+ */
+ public function loadFromXml($dom,$configPath,$configPagePath)
+ {
+ $this->loadApplicationConfigurationFromXml($dom,$configPath);
+ $this->loadPageConfigurationFromXml($dom,$configPath,$configPagePath);
+ }
+
+ public function loadApplicationConfigurationFromPhp($config,$configPath)
+ {
+ $appConfig=new TApplicationConfiguration;
+ $appConfig->loadFromPhp($config,$configPath);
+ $this->_appConfigs[]=$appConfig;
+ }
+
+ /**
+ * Loads the configuration specific for application part
+ * @param TXmlElement config xml element
+ * @param string base path corresponding to this xml element
+ */
+ public function loadApplicationConfigurationFromXml($dom,$configPath)
+ {
+ $appConfig=new TApplicationConfiguration;
+ $appConfig->loadFromXml($dom,$configPath);
+ $this->_appConfigs[]=$appConfig;
+ }
+
+ public function loadPageConfigurationFromPhp($config, $configPath, $configPagePath)
+ {
+ // authorization
+ if(isset($config['authorization']) && is_array($config['authorization']))
+ {
+ $rules = array();
+ foreach($config['authorization'] as $authorization)
+ {
+ $patterns=isset($authorization['pages'])?$authorization['pages']:'';
+ $ruleApplies=false;
+ if(empty($patterns) || trim($patterns)==='*') // null or empty string
+ $ruleApplies=true;
+ else
+ {
+ foreach(explode(',',$patterns) as $pattern)
+ {
+ if(($pattern=trim($pattern))!=='')
+ {
+ // we know $configPagePath and $this->_pagePath
+ if($configPagePath!=='') // prepend the pattern with ConfigPagePath
+ $pattern=$configPagePath.'.'.$pattern;
+ if(strcasecmp($pattern,$this->_pagePath)===0)
+ {
+ $ruleApplies=true;
+ break;
+ }
+ if($pattern[strlen($pattern)-1]==='*') // try wildcard matching
+ {
+ if(strncasecmp($this->_pagePath,$pattern,strlen($pattern)-1)===0)
+ {
+ $ruleApplies=true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ if($ruleApplies)
+ {
+ $action = isset($authorization['action'])?$authorization['action']:'';
+ $users = isset($authorization['users'])?$authorization['users']:'';
+ $roles = isset($authorization['roles'])?$authorization['roles']:'';
+ $verb = isset($authorization['verb'])?$authorization['verb']:'';
+ $ips = isset($authorization['ips'])?$authorization['ips']:'';
+ $rules[]=new TAuthorizationRule($action,$users,$roles,$verb,$ips);
+ }
+ }
+ $this->_rules=array_merge($rules,$this->_rules);
+ }
+ // pages
+ if(isset($config['pages']) && is_array($config['pages']))
+ {
+ if(isset($config['pages']['properties']))
+ {
+ $this->_properties = array_merge($this->_properties, $config['pages']['properties']);
+ unset($config['pages']['properties']);
+ }
+ foreach($config['pages'] as $id => $page)
+ {
+ $properties = array();
+ if(isset($page['properties']))
+ {
+ $properties=$page['properties'];
+ unset($page['properties']);
+ }
+ $matching=false;
+ $id=($configPagePath==='')?$id:$configPagePath.'.'.$id;
+ if(strcasecmp($id,$this->_pagePath)===0)
+ $matching=true;
+ else if($id[strlen($id)-1]==='*') // try wildcard matching
+ $matching=strncasecmp($this->_pagePath,$id,strlen($id)-1)===0;
+ if($matching)
+ $this->_properties=array_merge($this->_properties,$properties);
+ }
+ }
+
+ // external configurations
+ if(isset($config['includes']) && is_array($config['includes']))
+ {
+ foreach($config['includes'] as $include)
+ {
+ $when = isset($include['when'])?true:false;
+ if(!isset($include['file']))
+ throw new TConfigurationException('pageserviceconf_includefile_required');
+ $filePath = $include['file'];
+ if(isset($this->_includes[$filePath]))
+ $this->_includes[$filePath]=array($configPagePath,'('.$this->_includes[$filePath][1].') || ('.$when.')');
+ else
+ $this->_includes[$filePath]=array($configPagePath,$when);
+ }
+ }
+ }
+
+ /**
+ * Loads the configuration specific for page service.
+ * @param TXmlElement config xml element
+ * @param string base path corresponding to this xml element
+ * @param string the page path that the config XML is associated with. The page path doesn't include the page name.
+ */
+ public function loadPageConfigurationFromXml($dom,$configPath,$configPagePath)
+ {
+ // authorization
+ if(($authorizationNode=$dom->getElementByTagName('authorization'))!==null)
+ {
+ $rules=array();
+ foreach($authorizationNode->getElements() as $node)
+ {
+ $patterns=$node->getAttribute('pages');
+ $ruleApplies=false;
+ if(empty($patterns) || trim($patterns)==='*') // null or empty string
+ $ruleApplies=true;
+ else
+ {
+ foreach(explode(',',$patterns) as $pattern)
+ {
+ if(($pattern=trim($pattern))!=='')
+ {
+ // we know $configPagePath and $this->_pagePath
+ if($configPagePath!=='') // prepend the pattern with ConfigPagePath
+ $pattern=$configPagePath.'.'.$pattern;
+ if(strcasecmp($pattern,$this->_pagePath)===0)
+ {
+ $ruleApplies=true;
+ break;
+ }
+ if($pattern[strlen($pattern)-1]==='*') // try wildcard matching
+ {
+ if(strncasecmp($this->_pagePath,$pattern,strlen($pattern)-1)===0)
+ {
+ $ruleApplies=true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ if($ruleApplies)
+ $rules[]=new TAuthorizationRule($node->getTagName(),$node->getAttribute('users'),$node->getAttribute('roles'),$node->getAttribute('verb'),$node->getAttribute('ips'));
+ }
+ $this->_rules=array_merge($rules,$this->_rules);
+ }
+
+ // pages
+ if(($pagesNode=$dom->getElementByTagName('pages'))!==null)
+ {
+ $this->_properties=array_merge($this->_properties,$pagesNode->getAttributes()->toArray());
+ // at the page folder
+ foreach($pagesNode->getElementsByTagName('page') as $node)
+ {
+ $properties=$node->getAttributes();
+ $id=$properties->remove('id');
+ if(empty($id))
+ throw new TConfigurationException('pageserviceconf_page_invalid',$configPath);
+ $matching=false;
+ $id=($configPagePath==='')?$id:$configPagePath.'.'.$id;
+ if(strcasecmp($id,$this->_pagePath)===0)
+ $matching=true;
+ else if($id[strlen($id)-1]==='*') // try wildcard matching
+ $matching=strncasecmp($this->_pagePath,$id,strlen($id)-1)===0;
+ if($matching)
+ $this->_properties=array_merge($this->_properties,$properties->toArray());
+ }
+ }
+
+ // external configurations
+ foreach($dom->getElementsByTagName('include') as $node)
+ {
+ if(($when=$node->getAttribute('when'))===null)
+ $when=true;
+ if(($filePath=$node->getAttribute('file'))===null)
+ throw new TConfigurationException('pageserviceconf_includefile_required');
+ if(isset($this->_includes[$filePath]))
+ $this->_includes[$filePath]=array($configPagePath,'('.$this->_includes[$filePath][1].') || ('.$when.')');
+ else
+ $this->_includes[$filePath]=array($configPagePath,$when);
+ }
+ }
+}
+
diff --git a/lib/prado/framework/Web/Services/TRpcService.php b/lib/prado/framework/Web/Services/TRpcService.php
new file mode 100644
index 0000000..cf57944
--- /dev/null
+++ b/lib/prado/framework/Web/Services/TRpcService.php
@@ -0,0 +1,722 @@
+<?php
+
+/**
+ * @author Robin J. Rogge <rrogge@bigpoint.net>
+ * @link https://github.com/pradosoft/prado
+ * @copyright 2010 Bigpoint GmbH
+ * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
+ * @version $Id$
+ * @since 3.2
+ * @package System.Web.Services
+ */
+
+/**
+ * TRpcService class
+ *
+ * The TRpcService class is a generic class that can be extended and used to implement
+ * rpc services using different servers and protocols.
+ *
+ * A server is a {@link TModule} that must subclass {@link TRpcServer}: its role is
+ * to be an intermediate, moving data between the service and the provider. The base
+ * {@link TRpcServer} class should suit the most common needs, but can be sublassed for
+ * logging and debugging purposes, or to filter and modify the request/response on the fly.
+ *
+ * A protocol is a {@link TModule} that must subclass {@link TRpcProtocol}: its role is
+ * to implement the protocol that exposes the rpc api. Prado already implements two
+ * protocols: {@link TXmlRpcProtocol} for Xml-Rpc request and {@link TJsonRpcProtocol} for
+ * JSON-Rpc requests.
+ *
+ * A provider is a {@link TModule} that must subclass {@link TRpcApiProvider}: its role is
+ * to implement the methods that are available through the api. Each defined api must be
+ * a sublass of the abstract class {@link TRpcApiProvider} and implement its methods.
+ *
+ * The flow of requests and reponses is the following:
+ * Request <-> TRpcService <-> TRpcServer <-> TRpcProtocol <-> TRpcApiProvider <-> Response
+ *
+ * To define an rpc service, add the proper application configuration:
+ *
+ * <code>
+ * <service id="rpc" class="System.Web.Services.TRpcService">
+ * <rpcapi id="customers" class="Application.Api.CustomersApi" />
+ * <modules>
+ * <!-- register any module needed by the service here -->
+ * </modules>
+ * </service>
+ * </code>
+ *
+ * An api can be registered adding a proper <rpcapi ..> definition inside the service
+ * configuration. Each api definition must contain an id property and a class name
+ * expressed in namespace format. When the service receives a request for that api,
+ * the specified class will be instanciated in order to satisfy the request.
+ *
+ * @author Robin J. Rogge <rrogge@bigpoint.net>
+ * @version $Id$
+ * @package System.Web.Services
+ * @since 3.2
+ **/
+class TRpcService extends TService
+{
+ /**
+ * const string base api provider class which every API must extend
+ */
+ const BASE_API_PROVIDER = 'TRpcApiProvider';
+
+ /**
+ * const string base RPC server implementation
+ */
+ const BASE_RPC_SERVER = 'TRpcServer';
+
+ /**
+ * @var array containing mimetype to protocol handler mappings
+ */
+ protected $protocolHandlers = array(
+ 'application/json' => 'TJsonRpcProtocol',
+ 'text/xml' => 'TXmlRpcProtocol'
+ );
+
+ /**
+ * @var array containing API provider and their configured properties
+ */
+ protected $apiProviders = array();
+
+ // methods
+
+ /**
+ * Creates the API provider instance for the current request
+ * @param TRpcProtocol $protocolHandler instance
+ * @param string $providerId
+ */
+ public function createApiProvider(TRpcProtocol $protocolHandler, $providerId)
+ {
+ $_properties = $this->apiProviders[$providerId];
+
+ if(($_providerClass = $_properties->remove('class')) === null)
+ throw new TConfigurationException('rpcservice_apiprovider_required');
+
+ Prado::using($_providerClass);
+
+ $_providerClassName = ($_pos = strrpos($_providerClass, '.')) !== false ? substr($_providerClass, $_pos + 1) : $_providerClass;
+ if(!is_subclass_of($_providerClassName, self::BASE_API_PROVIDER))
+ throw new TConfigurationException('rpcservice_apiprovider_invalid');
+
+ if(($_rpcServerClass = $_properties->remove('server')) === null)
+ $_rpcServerClass = self::BASE_RPC_SERVER;
+
+ Prado::using($_rpcServerClass);
+
+ $_rpcServerClassName = ($_pos = strrpos($_rpcServerClass, '.')) !== false ? substr($_rpcServerClass, $_pos + 1) : $_rpcServerClass;
+ if($_rpcServerClassName!==self::BASE_RPC_SERVER && !is_subclass_of($_rpcServerClassName, self::BASE_RPC_SERVER))
+ throw new TConfigurationException('rpcservice_rpcserver_invalid');
+
+ $_apiProvider = new $_providerClassName(new $_rpcServerClassName($protocolHandler));
+ $_apiProvider->setId($providerId);
+
+ foreach($_properties as $_key => $_value)
+ $_apiProvider->setSubProperty($_key, $_value);
+
+ return $_apiProvider;
+ }
+
+ /**
+ * Initializes the service
+ * @param TXmlElement $config containing the module configuration
+ */
+ public function init($config)
+ {
+ $this->loadConfig($config);
+ }
+
+ /**
+ * Loads the service configuration
+ * @param TXmlElement $xml configuration
+ */
+ public function loadConfig(TXmlElement $xml)
+ {
+ foreach($xml->getElementsByTagName('rpcapi') as $_apiProviderXml)
+ {
+ $_properties = $_apiProviderXml->getAttributes();
+
+ if(($_id = $_properties->remove('id')) === null || $_id == "")
+ throw new TConfigurationException('rpcservice_apiproviderid_required');
+
+ if(isset($this->apiProviders[$_id]))
+ throw new TConfigurationException('rpcservice_apiproviderid_duplicated');
+
+ $this->apiProviders[$_id] = $_properties;
+ }
+ }
+
+ /**
+ * Runs the service
+ */
+ public function run()
+ {
+ $_request = $this->getRequest();
+
+ if(($_providerId = $_request->getServiceParameter()) == "")
+ throw new THttpException(400, 'RPC API-Provider id required');
+
+ if(($_method = $_request->getRequestType()) != 'POST')
+ throw new THttpException(405, 'Invalid request method "'.$_method.'"!'); // TODO Exception muss "Allow POST" Header setzen
+
+ if(($_mimeType = $_request->getContentType()) === null)
+ throw new THttpException(406, 'Content-Type is missing!'); // TODO Exception muss gültige Content-Type werte zurück geben
+
+ if(!in_array($_mimeType, array_keys($this->protocolHandlers)))
+ throw new THttpException(406, 'Unsupported Content-Type!'); // TODO see previous
+
+ $_protocolHandlerClass = $this->protocolHandlers[$_mimeType];
+ $_protocolHandler = new $_protocolHandlerClass;
+
+ if(($_result = $this->createApiProvider($_protocolHandler, $_providerId)->processRequest()) !== null)
+ {
+ $_response = $this->getResponse();
+ $_protocolHandler->createResponseHeaders($_response);
+ $_response->write($_result);
+ }
+ }
+}
+
+/**
+ * TRpcServer class
+ *
+ * TRpcServer is a class
+ *
+ * TRpcServer is the base class used to creare a server to be used in conjunction with
+ * {@link TRpcService}.
+ * The role of TRpcServer is to be an intermediate, moving data between the service and
+ * the provider. This base class should suit the most common needs, but can be sublassed for
+ * logging and debugging purposes, or to filter and modify the request/response on the fly.
+ *
+ * @author Robin J. Rogge <rrogge@bigpoint.net>
+ * @version $Id$
+ * @package System.Web.Services
+ * @since 3.2
+ **/
+class TRpcServer extends TModule
+{
+ /**
+ * @var TRpcProtocol instance
+ */
+ protected $handler;
+
+ /**
+ * Constructor
+ * @param TRpcProtocol $protocolHandler instance
+ */
+ public function __construct(TRpcProtocol $protocolHandler)
+ {
+ $this->handler = $protocolHandler;
+ }
+
+ /**
+ * Registers the method in the protocol handler
+ * @param string $methodName
+ * @param array $methodDetails
+ */
+ public function addRpcMethod($methodName, $methodDetails)
+ {
+ $this->handler->addMethod($methodName, $methodDetails);
+ }
+
+ /**
+ * Retrieves the request payload
+ * @return string request payload
+ */
+ public function getPayload()
+ {
+ return file_get_contents('php://input');
+ }
+
+ /**
+ * Passes the request payload to the protocol handler and returns the result
+ * @return string rpc response
+ */
+ public function processRequest()
+ {
+ try
+ {
+ return $this->handler->callMethod($this->getPayload());
+ }
+ catch(TRpcException $e)
+ {
+ return $this->handler->createErrorResponse($e);
+ }
+ }
+}
+
+/**
+ * TRpcException class
+ *
+ * A TRpcException represents a RPC fault i.e. an error that is caused by the input data
+ * sent from the client.
+ *
+ * @author Robin J. Rogge <rrogge@bigpoint.net>
+ * @version $Id$
+ * @package System.Web.Services
+ * @since 3.2
+ */
+class TRpcException extends TException
+{
+ public function __construct($message, $errorCode = -1)
+ {
+ $this->setErrorCode($errorCode);
+
+ parent::__construct($message);
+ }
+}
+
+/**
+ * TRpcApiProvider class
+ *
+ * TRpcApiProvider is an abstract class the can be subclasses in order to implement an
+ * api for a {@link TRpcService}. A subclass of TRpcApiProvider must implement the
+ * {@link registerMethods} method in order to declare the available methods, their
+ * names and the associated callback.
+ *
+ * <code>
+ * public function registerMethods()
+ * {
+ * return array(
+ * 'apiMethodName1' => array('method' => array($this, 'objectMethodName1')),
+ * 'apiMethodName2' => array('method' => array('ClassName', 'staticMethodName')),
+ * );
+ * }
+ * </code>
+ *
+ * In this example, two api method have been defined. The first refers to an object
+ * method that must be implemented in the same class, the second to a static method
+ * implemented in a 'ClassName' class.
+ * In both cases, the method implementation will receive the request parameters as its
+ * method parameters. Since the number of received parameters depends on
+ * external-supplied data, it's adviced to use php's func_get_args() funtion to
+ * validate them.
+ *
+ * Providers must be registered in the service configuration in order to be available,
+ * as explained in {@link TRpcService}'s documentation.
+ *
+ * @author Robin J. Rogge <rrogge@bigpoint.net>
+ * @version $Id$
+ * @package System.Web.Services
+ * @since 3.2
+ */
+abstract class TRpcApiProvider extends TModule
+{
+ /**
+ * @var TRpcServer instance
+ */
+ protected $rpcServer;
+
+ /**
+ * Must return an array of the available methods
+ * @abstract
+ */
+ abstract public function registerMethods();
+
+ /**
+ * Constructor: informs the rpc server of the registered methods
+ */
+ public function __construct(TRpcServer $rpcServer)
+ {
+ $this->rpcServer = $rpcServer;
+
+ foreach($this->registerMethods() as $_methodName => $_methodDetails)
+ $this->rpcServer->addRpcMethod($_methodName, $_methodDetails);
+ }
+
+ /**
+ * Processes the request using the server
+ * @return processed request
+ */
+ public function processRequest()
+ {
+ return $this->rpcServer->processRequest();
+ }
+
+ /**
+ * @return rpc server instance
+ */
+ public function getRpcServer()
+ {
+ return $this->rpcServer;
+ }
+}
+
+/**
+ * TRpcProtocol class
+ *
+ * TRpcProtocol is the base class used to implement a protocol in a {@link TRpcService}.
+ * Prado already implements two protocols: {@link TXmlRpcProtocol} for Xml-Rpc request
+ * and {@link TJsonRpcProtocol} for JSON-Rpc requests.
+ *
+ * @author Robin J. Rogge <rrogge@bigpoint.net>
+ * @version $Id$
+ * @package System.Web.Services
+ * @since 3.2
+ **/
+abstract class TRpcProtocol
+{
+ /**
+ * @var array containing the mapping from RPC method names to the actual handlers
+ */
+ protected $rpcMethods = array();
+
+ // abstracts
+
+ /**
+ * @param string request payload
+ * Processed the request ans returns the response, if any
+ * @return processed response
+ * @abstract
+ */
+ abstract public function callMethod($requestPayload);
+ /**
+ * @param TRpcException the exception with error details
+ * Creates a proper response for an error condition
+ * @return a response representing the error
+ * @abstract
+ */
+ abstract public function createErrorResponse(TRpcException $exception);
+ /**
+ * @param response
+ * Sets the needed headers for the response (eg: content-type, charset)
+ * @abstract
+ */
+ abstract public function createResponseHeaders($response);
+ /**
+ * Encodes the response
+ * @param mixed reponse data
+ * @return string encoded response
+ * @abstract
+ */
+ abstract public function encode($data);
+ /**
+ * Decodes the request payload
+ * @param string request payload
+ * @return mixed decoded request
+ * @abstract
+ */
+ abstract public function decode($data);
+
+ // methods
+
+ /**
+ * Registers a new RPC method and handler details
+ * @param string $methodName
+ * @param array $handlerDetails containing the callback handler
+ */
+ public function addMethod($methodName, $handlerDetails)
+ {
+ $this->rpcMethods[$methodName] = $handlerDetails;
+ }
+
+ /**
+ * Calls the callback handler for the given method
+ * @param string $methodName of the RPC
+ * @param array $parameters for the callback handler as provided by the client
+ * @return mixed whatever the callback handler returns
+ */
+ public function callApiMethod($methodName, $parameters)
+ {
+ if(!isset($this->rpcMethods[$methodName]))
+ throw new TRpcException('Method "'.$methodName.'" not found');
+
+ if($parameters === null)
+ $parameters = array();
+
+ if(!is_array($parameters))
+ $parameters = array($parameters);
+ return call_user_func_array($this->rpcMethods[$methodName]['method'], $parameters);
+ }
+}
+
+/**
+ * TJsonRpcProtocol class
+ *
+ * TJsonRpcProtocol is a class that implements JSON-Rpc protocol in {@link TRpcService}.
+ * Both version 1.0 and 2.0 of the specification are implemented, and the server will try
+ * to answer using the same version of the protocol used by the requesting client.
+ *
+ * @author Robin J. Rogge <rrogge@bigpoint.net>
+ * @author Fabio Bas <ctrlaltca@gmail.com>
+ * @version $Id$
+ * @package System.Web.Services
+ * @since 3.2
+ */
+class TJsonRpcProtocol extends TRpcProtocol
+{
+ protected $_id=null;
+ protected $_specificationVersion=1.0;
+
+ /**
+ * Handles the RPC request
+ * @param string $requestPayload
+ * @return string JSON RPC response
+ */
+ public function callMethod($requestPayload)
+ {
+ try
+ {
+ $_request = $this->decode($requestPayload);
+
+ if(isset($_request['jsonrpc']))
+ {
+ $this->_specificationVersion=$_request['jsonrpc'];
+ if($this->_specificationVersion > 2.0)
+ throw new TRpcException('Unsupported specification version', '-32600');
+ }
+
+ if(isset($_request['id']))
+ $this->_id=$_request['id'];
+
+ if(!isset($_request['method']))
+ throw new TRpcException('Missing request method', '-32600');
+
+ if(!isset($_request['params']))
+ $parameters = array();
+ else
+ $parameters = $_request['params'];
+
+ if(!is_array($parameters))
+ $parameters = array($parameters);
+
+ // a request without an id is a notification that doesn't need a response
+ if($this->_id !== null)
+ {
+ if($this->_specificationVersion==2.0)
+ {
+ return $this->encode(array(
+ 'jsonrpc' => '2.0',
+ 'id' => $this->_id,
+ 'result' => $this->callApiMethod($_request['method'], $parameters),
+ ));
+ } else {
+ return $this->encode(array(
+ 'id' => $this->_id,
+ 'result' => $this->callApiMethod($_request['method'], $_request['params']),
+ 'error' => null
+ ));
+ }
+ }
+ }
+ catch(TRpcException $e)
+ {
+ return $this->createErrorResponse($e);
+ }
+ catch(THttpException $e)
+ {
+ throw $e;
+ }
+ catch(Exception $e)
+ {
+ return $this->createErrorResponse(new TRpcException('An internal error occured', '-32603'));
+ }
+ }
+
+ /**
+ * Turns the given exception into an JSON RPC fault
+ * @param TRpcException $exception
+ * @return string JSON RPC fault
+ */
+ public function createErrorResponse(TRpcException $exception)
+ {
+ if($this->_specificationVersion==2.0)
+ {
+ return $this->encode(array(
+ 'id' => $this->_id,
+ 'result' => null,
+ 'error'=> array(
+ 'code' => $exception->getCode(),
+ 'message'=> $exception->getMessage(),
+ 'data' => null,
+ )
+ ));
+ } else {
+ return $this->encode(array(
+ 'id' => $this->_id,
+ 'error'=> array(
+ 'code' => $exception->getCode(),
+ 'message'=> $exception->getMessage(),
+ 'data' => null,
+ )
+ ));
+ }
+ }
+
+ /**
+ * Sets the correct response headers
+ * @param THttpResponse $response
+ */
+ public function createResponseHeaders($response)
+ {
+ $response->setContentType('application/json');
+ $response->setCharset('UTF-8');
+ }
+
+ /**
+ * Decodes JSON encoded data into PHP data
+ * @param string $data in JSON format
+ * @return array PHP data
+ */
+ public function decode($data)
+ {
+ $s = json_decode($data, true);
+ self::checkJsonError();
+ return $s;
+ }
+
+ /**
+ * Encodes PHP data into JSON data
+ * @param mixed PHP data
+ * @return string JSON encoded PHP data
+ */
+ public function encode($data)
+ {
+ $s = json_encode($data);
+ self::checkJsonError();
+ return $s;
+ }
+
+ private static function checkJsonError()
+ {
+ $errnum = json_last_error();
+ if($errnum != JSON_ERROR_NONE)
+ throw new Exception("JSON error: $msg", $err);
+ }
+
+ /**
+ * Calls the callback handler for the given method
+ * Overrides parent implementation to correctly handle error codes
+ * @param string $methodName of the RPC
+ * @param array $parameters for the callback handler as provided by the client
+ * @return mixed whatever the callback handler returns
+ */
+ public function callApiMethod($methodName, $parameters)
+ {
+ if(!isset($this->rpcMethods[$methodName]))
+ throw new TRpcException('Method "'.$methodName.'" not found', '-32601');
+
+ return call_user_func_array($this->rpcMethods[$methodName]['method'], $parameters);
+ }
+}
+
+/**
+ * TXmlRpcProtocol class
+ *
+ * TXmlRpcProtocol is a class that implements XML-Rpc protocol in {@link TRpcService}.
+ * It's basically a wrapper to the xmlrpc_server_* family of php methods.
+ *
+ * @author Robin J. Rogge <rrogge@bigpoint.net>
+ * @version $Id$
+ * @package System.Web.Services
+ * @since 3.2
+ */
+class TXmlRpcProtocol extends TRpcProtocol
+{
+ /**
+ * @var XML RPC server resource
+ */
+ private $_xmlrpcServer;
+
+ // magics
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->_xmlrpcServer = xmlrpc_server_create();
+ }
+
+ /**
+ * Destructor
+ */
+ public function __destruct()
+ {
+ xmlrpc_server_destroy($this->_xmlrpcServer);
+ }
+
+ // methods
+
+ /**
+ * Registers a new RPC method and handler details
+ * @param string $methodName
+ * @param array $handlerDetails containing the callback handler
+ */
+ public function addMethod($methodName, $methodDetails)
+ {
+ parent::addMethod($methodName, $methodDetails);
+
+ xmlrpc_server_register_method($this->_xmlrpcServer, $methodName, array($this, 'callApiMethod'));
+ }
+
+ // methods
+
+ /**
+ * Handles the RPC request
+ * @param string $requestPayload
+ * @return string XML RPC response
+ */
+ public function callMethod($requestPayload)
+ {
+ try
+ {
+ return xmlrpc_server_call_method($this->_xmlrpcServer, $requestPayload, null);
+ }
+ catch(TRpcException $e)
+ {
+ return $this->createErrorResponse($e);
+ }
+ catch(THttpException $e)
+ {
+ throw $e;
+ }
+ catch(Exception $e)
+ {
+ return $this->createErrorResponse(new TRpcException('An internal error occured'));
+ }
+ }
+
+ /**
+ * Turns the given exception into an XML RPC fault
+ * @param TRpcException $exception
+ * @return string XML RPC fault
+ */
+ public function createErrorResponse(TRpcException $exception)
+ {
+ return $this->encode(array(
+ 'faultCode' => $exception->getCode(),
+ 'faultString' => $exception->getMessage()
+ ));
+ }
+
+ /**
+ * Sets the correct response headers
+ * @param THttpResponse $response
+ */
+ public function createResponseHeaders($response)
+ {
+ $response->setContentType('text/xml');
+ $response->setCharset('UTF-8');
+ }
+
+ /**
+ * Decodes XML encoded data into PHP data
+ * @param string $data in XML format
+ * @return array PHP data
+ */
+ public function decode($data)
+ {
+ return xmlrpc_decode($data);
+ }
+
+ /**
+ * Encodes PHP data into XML data
+ * @param mixed PHP data
+ * @return string XML encoded PHP data
+ */
+ public function encode($data)
+ {
+ return xmlrpc_encode($data);
+ }
+}
diff --git a/lib/prado/framework/Web/Services/TSoapService.php b/lib/prado/framework/Web/Services/TSoapService.php
new file mode 100644
index 0000000..9554af2
--- /dev/null
+++ b/lib/prado/framework/Web/Services/TSoapService.php
@@ -0,0 +1,622 @@
+<?php
+/**
+ * TSoapService and TSoapServer class file
+ *
+ * @author Knut Urdalen <knut.urdalen@gmail.com>
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link https://github.com/pradosoft/prado
+ * @copyright Copyright &copy; 2005-2015 The PRADO Group
+ * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
+ * @package System.Web.Services
+ */
+
+/**
+ * TSoapService class
+ *
+ * TSoapService processes SOAP requests for a PRADO application.
+ * TSoapService requires PHP SOAP extension to be loaded.
+ *
+ * TSoapService manages a set of SOAP providers. Each SOAP provider
+ * is a class that implements a set of SOAP methods which are exposed
+ * to SOAP clients for remote invocation. TSoapService generates WSDL
+ * automatically for the SOAP providers by default.
+ *
+ * To use TSoapService, configure it in the application specification like following:
+ * <code>
+ * <services>
+ * <service id="soap" class="System.Web.Services.TSoapService">
+ * <soap id="stockquote" provider="MyStockQuote" />
+ * </service>
+ * </services>
+ * </code>
+ * PHP configuration style:
+ * <code>
+ * 'services' => array(
+ * 'soap' => array(
+ * 'class' => 'System.Web.Services.TSoapService'
+ * 'properties' => array(
+ * 'provider' => 'MyStockQuote'
+ * )
+ * )
+ * )
+ * </code>
+ *
+ * The WSDL for the provider class "MyStockQuote" is generated based on special
+ * comment tags in the class. In particular, if a class method's comment
+ * contains the keyword "@soapmethod", it is considered to be a SOAP method
+ * and will be exposed to SOAP clients. For example,
+ * <code>
+ * class MyStockQuote {
+ * / **
+ * * @param string $symbol the stock symbol
+ * * @return float the stock price
+ * * @soapmethod
+ * * /
+ * public function getQuote($symbol) {...}
+ * }
+ * </code>
+ *
+ * With the above SOAP provider, a typical SOAP client may call the method "getQuote"
+ * remotely like the following:
+ * <code>
+ * $client=new SoapClient("http://hostname/path/to/index.php?soap=stockquote.wsdl");
+ * echo $client->getQuote("ibm");
+ * </code>
+ *
+ * Each <soap> element in the application specification actually configures
+ * the properties of a SOAP server which defaults to {@link TSoapServer}.
+ * Therefore, any writable property of {@link TSoapServer} may appear as an attribute
+ * in the <soap> element. For example, the "provider" attribute refers to
+ * the {@link TSoapServer::setProvider Provider} property of {@link TSoapServer}.
+ * The following configuration specifies that the SOAP server is persistent within
+ * the user session (that means a MyStockQuote object will be stored in session)
+ * <code>
+ * <services>
+ * <service id="soap" class="System.Web.Services.TSoapService">
+ * <soap id="stockquote" provider="MyStockQuote" SessionPersistent="true" />
+ * </service>
+ * </services>
+ * </code>
+ *
+ * You may also use your own SOAP server class by specifying the "class" attribute of <soap>.
+ *
+ * @author Knut Urdalen <knut.urdalen@gmail.com>
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @author Carl G. Mathisen <carlgmathisen@gmail.com>
+ * @package System.Web.Services
+ * @since 3.1
+ */
+class TSoapService extends TService
+{
+ const DEFAULT_SOAP_SERVER='TSoapServer';
+ private $_servers=array();
+ private $_configFile=null;
+ private $_wsdlRequest=false;
+ private $_serverID=null;
+
+ /**
+ * Constructor.
+ * Sets default service ID to 'soap'.
+ */
+ public function __construct()
+ {
+ $this->setID('soap');
+ }
+
+ /**
+ * Initializes this module.
+ * This method is required by the IModule interface.
+ * @param TXmlElement configuration for this module, can be null
+ * @throws TConfigurationException if {@link getConfigFile ConfigFile} is invalid.
+ */
+ public function init($config)
+ {
+ if($this->_configFile!==null)
+ {
+ if(is_file($this->_configFile))
+ {
+ $dom=new TXmlDocument;
+ $dom->loadFromFile($this->_configFile);
+ $this->loadConfig($dom);
+ }
+ else
+ throw new TConfigurationException('soapservice_configfile_invalid',$this->_configFile);
+ }
+ $this->loadConfig($config);
+
+ $this->resolveRequest();
+ }
+
+ /**
+ * Resolves the request parameter.
+ * It identifies the server ID and whether the request is for WSDL.
+ * @throws THttpException if the server ID cannot be found
+ * @see getServerID
+ * @see getIsWsdlRequest
+ */
+ protected function resolveRequest()
+ {
+ $serverID=$this->getRequest()->getServiceParameter();
+ if(($pos=strrpos($serverID,'.wsdl'))===strlen($serverID)-5)
+ {
+ $serverID=substr($serverID,0,$pos);
+ $this->_wsdlRequest=true;
+ }
+ else
+ $this->_wsdlRequest=false;
+ $this->_serverID=$serverID;
+ if(!isset($this->_servers[$serverID]))
+ throw new THttpException(400,'soapservice_request_invalid',$serverID);
+ }
+
+ /**
+ * Loads configuration from an XML element
+ * @param mixed configuration node
+ * @throws TConfigurationException if soap server id is not specified or duplicated
+ */
+ private function loadConfig($config)
+ {
+ if($this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP)
+ {
+ if(is_array($config))
+ {
+ foreach($config['soap'] as $id => $server)
+ {
+ $properties = isset($server['properties'])?$server['properties']:array();
+ if(isset($this->_servers[$id]))
+ throw new TConfigurationException('soapservice_serverid_duplicated',$id);
+ $this->_servers[$id]=$properties;
+ }
+ }
+ }
+ else
+ {
+ foreach($config->getElementsByTagName('soap') as $serverXML)
+ {
+ $properties=$serverXML->getAttributes();
+ if(($id=$properties->remove('id'))===null)
+ throw new TConfigurationException('soapservice_serverid_required');
+ if(isset($this->_servers[$id]))
+ throw new TConfigurationException('soapservice_serverid_duplicated',$id);
+ $this->_servers[$id]=$properties;
+ }
+ }
+ }
+
+ /**
+ * @return string external configuration file. Defaults to null.
+ */
+ public function getConfigFile()
+ {
+ return $this->_configFile;
+ }
+
+ /**
+ * @param string external configuration file in namespace format. The file
+ * must be suffixed with '.xml'.
+ * @throws TInvalidDataValueException if the file is invalid.
+ */
+ public function setConfigFile($value)
+ {
+ if(($this->_configFile=Prado::getPathOfNamespace($value,Prado::getApplication()->getConfigurationFileExt()))===null)
+ throw new TConfigurationException('soapservice_configfile_invalid',$value);
+ }
+
+ /**
+ * Constructs a URL with specified page path and GET parameters.
+ * @param string soap server ID
+ * @param array list of GET parameters, null if no GET parameters required
+ * @param boolean whether to encode the ampersand in URL, defaults to true.
+ * @param boolean whether to encode the GET parameters (their names and values), defaults to true.
+ * @return string URL for the page and GET parameters
+ */
+ public function constructUrl($serverID,$getParams=null,$encodeAmpersand=true,$encodeGetItems=true)
+ {
+ return $this->getRequest()->constructUrl($this->getID(),$serverID,$getParams,$encodeAmpersand,$encodeGetItems);
+ }
+
+ /**
+ * @return boolean whether this is a request for WSDL
+ */
+ public function getIsWsdlRequest()
+ {
+ return $this->_wsdlRequest;
+ }
+
+ /**
+ * @return string the SOAP server ID
+ */
+ public function getServerID()
+ {
+ return $this->_serverID;
+ }
+
+ /**
+ * Creates the requested SOAP server.
+ * The SOAP server is initialized with the property values specified
+ * in the configuration.
+ * @return TSoapServer the SOAP server instance
+ */
+ protected function createServer()
+ {
+ $properties=$this->_servers[$this->_serverID];
+ $serverClass=null;
+ if($this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP && isset($config['class']))
+ $serverClass=$config['class'];
+ else if($this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_XML)
+ $serverClass=$properties->remove('class');
+ if($serverClass===null)
+ $serverClass=self::DEFAULT_SOAP_SERVER;
+ Prado::using($serverClass);
+ $className=($pos=strrpos($serverClass,'.'))!==false?substr($serverClass,$pos+1):$serverClass;
+ if($className!==self::DEFAULT_SOAP_SERVER && !is_subclass_of($className,self::DEFAULT_SOAP_SERVER))
+ throw new TConfigurationException('soapservice_server_invalid',$serverClass);
+ $server=new $className;
+ $server->setID($this->_serverID);
+ foreach($properties as $name=>$value)
+ $server->setSubproperty($name,$value);
+ return $server;
+ }
+
+ /**
+ * Runs the service.
+ * If the service parameter ends with '.wsdl', it will serve a WSDL file for
+ * the specified soap server.
+ * Otherwise, it will handle the soap request using the specified server.
+ */
+ public function run()
+ {
+ Prado::trace("Running SOAP service",'System.Web.Services.TSoapService');
+ $server=$this->createServer();
+ $this->getResponse()->setContentType('text/xml');
+ $this->getResponse()->setCharset($server->getEncoding());
+ if($this->getIsWsdlRequest())
+ {
+ // server WSDL file
+ Prado::trace("Generating WSDL",'System.Web.Services.TSoapService');
+ $this->getResponse()->clear();
+ $this->getResponse()->write($server->getWsdl());
+ }
+ else
+ {
+ // provide SOAP service
+ Prado::trace("Handling SOAP request",'System.Web.Services.TSoapService');
+ $server->run();
+ }
+ }
+}
+
+
+/**
+ * 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;
+ }
+}
+