Skip to content
Snippets Groups Projects
Select Git revision
  • 47860870a333e83a38fb0591e187db2287162ace
  • master default protected
  • Legacy_Php7
3 results

sws.class.php

Blame
  • sws.class.php 42.16 KiB
    <?php
    
    /*
     * SIMPLE WEBSERVICE
     * <br>
     * THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE <br>
     * LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR <br>
     * OTHER PARTIES PROVIDE THE PROGRAM ?AS IS? WITHOUT WARRANTY OF ANY KIND, <br>
     * EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED <br>
     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE <br>
     * ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. <br>
     * SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY <br>
     * SERVICING, REPAIR OR CORRECTION.<br>
     * <br>
     * 
     * --- HISTORY:<br>
     * 2014-04-30  0.5  gui enhancements: input field for each param of all methods
     * 2014-04-29  0.4  abstract all data before displaying them
     * 2014-04-06  0.3  enable/ disable gui; detect class parameters
     * 2014-04-05  0.2  
     * 2014-04-05  0.1  first public beta
     * 
     * url params
     * - class  - classname
     * - action - method to call [classname] -> [method]
     * - args   - arguments  (as json)
     * - type   - outputtype (default: json)
     * 
     * TODOs:
     * - add param for arguments to initialize the class (detect params of __construct())
     * - show an error: detect if a configured method is not public 
     * - parse parameters from phpdoc - detect required and optional params
     * - parse parameters from phpdoc - show input fields for each parameters
     * - configuration: option area
     * - configuration: examples of a class + of actions
     * 
     * @version 0.06
     * @author Axel Hahn
     * @link http://www.axel-hahn.de/php_sws
     * @license GPL
     * @license http://www.gnu.org/licenses/gpl-3.0.html GPL 3.0
     * @package Axels simple web service
     */
    
    class sws {
    
        /**
         * config array with known classnames and its supported methods
         * @var string
         */
        private $_aKnownClasses = array();
    
        /**
         * array of incoming parameters (GET, POST)
         * @var array
         */
        private $_aParams = array();
    
        /**
         * name of the detected classname in the http request
         * @var string
         */
        private $_sClass = false;
        
        /**
         * name of the detected init arguments for the class in the http request
         * @var string
         */
        private $_aInit = array();
    
        /**
         * name of the detected method in the http request
         * @var string
         */
        private $_sAction = false;
    
        /**
         * name of the detected arguments in the http request
         * @var string
         */
        private $_aArgs = array();
    
        /**
         * name of the found class file (by found class it is fetched from config)
         * @var string
         */
        private $_sClassfile = false;
    
        /**
         * relative default dir from sws.class.php to class files in the config
         * @var string
         */
        private $_sClassDir = "";
        //private $_sClassDir = "../php/classes/";
    
        /**
         * output type for the respone to the client
         * @var string
         */
        private $_sOutputType = "json";
    
        /**
         * version
         * @var string 
         */
        private $_sVersion = "0.07&nbsp;(beta)";
    
        /**
         * title
         * @var string
         */
        private $_sTitle = "SWS :: simple&nbsp;web&nbsp;service";
    
        private $_aOptions = array(
            'enableGui'=>true,
            'enableDump'=>false
            );
    
        /**
         * url for documentation
         * @var string
         */
        private $_sUrlDoc = "";
    
        /**
         * url of project home
         * @var string
         */
        private $_sUrlHome = "http://www.axel-hahn.de/php_sws";
    
        // ----------------------------------------------------------------------
        // general functions
        // ----------------------------------------------------------------------
    
        /**
         * constructor
         * @param array $aKnownClasses  array with known classes and supported methods
         * @return boolean
         */
        public function __construct($aKnownClasses = false) {
            if (is_array($aKnownClasses)) {
                $this->setConfig($aKnownClasses);
            }
            return true;
        }
    
        // ----------------------------------------------------------------------
        // private functions
        // ----------------------------------------------------------------------
    
        /**
         * apply a given config
         * @param array $aKnownClasses  array with known classes and supported methods
         * @return bool
         */
        public function setConfig($aKnownClasses) {
            $this->_aKnownClasses = array();
            if (is_array($aKnownClasses)) {
                $this->_aKnownClasses = $aKnownClasses;
                
                if (array_key_exists("options", $aKnownClasses)){
                    $this->_aOptions=array_merge($this->_aOptions, $aKnownClasses["options"]);
                }
                return true;
            }
            return false;
        }
    
        /**
         * helper for _parseParams
         * check if parameter args contain safe chars
         * @param type $sParamValue
         * @return boolean
         */
        private function _verifyParamValue($sParamValue){
            $sOKChars='a-z0-9\"\{\}\[\]\.\,\ \:\-\+\_';
            /*
            $sOKChars='a-z0-9\"\`\'\{\}\[\]\.\,\ \:\-\+'
            .'\<\>\='
            ;
             */
            if(isset($this->_aParams[$sParamValue])){
                $sVal=urldecode($this->_aParams[$sParamValue]);
                $sBadchars=preg_replace('/['.$sOKChars. ']/i', '',$sVal);
                if($sBadchars){
                    $this->_quit("ERROR: parameter $sParamValue=.. contains unsupported character(s): [". $sBadchars."]");
                }
                
            }
            return true;
        }
        /**
         * parse parameters (given GET/ POST is in by _aParams @see setParams)
         *     class  - class to initialize
         *     init   - parameters for constructor
         *     action - public method to call
         *     args   - arguments for the method
         * @return bool
         */
        private function _parseParams() {
            $aMinParams = array("class", "action");
            $aMaxParams = array_merge($aMinParams, array("init", "args", "type"));
            $sErrors = '';
    
            // set defaults
            $this->_sClass = false;
            $this->_aInit = array();
            $this->_sAction = false;
            $this->_aArgs = array();
    
            if (!$this->_aKnownClasses || !is_array($this->_aKnownClasses)) {
                $this->_quit("ERROR: no configuration was found."
                        . "You can set it with initialisation <br>\n"
                        . "<code>\$o = new " . __CLASS__ . " ( \$aOptions );</code><br>\n"
                        . "or<br>\n"
                        . "<code>\$o = new " . __CLASS__ . " ();<br>\n"
                        . "\$o -> setConfig ( \$aOptions );</code><br>\n"
                );
            }
    
            // check minimal params
            foreach ($aMinParams as $sKey) {
                $bFound = array_key_exists($sKey, $this->_aParams);
                if (!$bFound) {
                    $sErrors.="- <em>" . $sKey . "</em><br>";
                }
            }
            // check max Params
            foreach(array_keys($this->_aParams) as $sKey){
                if(!in_array($sKey, $aMaxParams)){
                    $this->_quit("ERROR: parameter ". htmlentities($sKey)." is unknown.");
                }
            }
            
            // check if classname and action exist in configuration
            if (array_key_exists("class", $this->_aParams)) {
                if (!array_key_exists($this->_aParams["class"], $this->_aKnownClasses["classes"])) {
                    $this->_quit('ERROR: parameter <em>class = ' . $this->_aParams["class"] . '</em> is invalid. I cannot handle this class.', $this->_showClasshelp());
                } else {
                    // set internal vars: classname
                    $this->_sClass = $this->_aParams["class"];
                    $this->_sClassfile = $this->_aKnownClasses["classes"][$this->_aParams["class"]]["file"];
    
                    // get arguments for the method
                    $this->_verifyParamValue("init");
                    $this->_verifyParamValue("args");
                    
                    if (array_key_exists("init", $this->_aParams)) {
                        try {
                            $aTmp = json_decode($this->_aParams["init"], 1);
                        } catch (Exception $e) {
                            // nop
                        }
                        if (!is_array($aTmp)) {
                            $this->_quit(
                                    'ERROR: wrong request - init value must be a json string (in url encoded form)<br>'
                                    . 'examples:<br>'
                                    . '- one arg <code>(...)&init=["my string"]</code><br>'
                                    . '- two args <code>(...)&init=["my string", 123]</code> '
                                    , $this->_showClasshelp());
                        }
                        $this->_aInit = $aTmp;
                    }
                }
                // get method name to call
                if (array_key_exists("action", $this->_aParams)) {
                    if (!array_key_exists($this->_aParams["action"], $this->_aKnownClasses["classes"][$this->_aParams["class"]]["actions"])) {
                        $this->_quit('ERROR: class ' . $this->_aParams["class"] . ' exists but it has no <em>action = ' . $this->_aParams["action"] . '</em>.', $this->_showClasshelp());
                    } else {
                        // set internal vars
                        $this->_sAction = $this->_aParams["action"];
                    }
                }
                // get arguments for the method
                if (array_key_exists("args", $this->_aParams)) {
                    try {
                        $aTmp = json_decode($this->_aParams["args"], 1);
                    } catch (Exception $e) {
                        // nop
                    }
                    if (!is_array($aTmp)) {
                        $this->_quit(
                                'ERROR: wrong request - args value must be a json string (in url encoded form)<br>'
                                . 'examples:<br>'
                                . '- one arg <code>(...)&args=["my string"]</code><br>'
                                . '- two args <code>(...)&args=["my string", 123]</code> '
                                , $this->_showClasshelp());
                    }
                    $this->_aArgs = $aTmp;
                }
            }
    
    
            if (array_key_exists("type", $this->_aParams)) {
                $this->setOutputType($this->_aParams["type"]);
            }
            if ($sErrors) {
                $this->_quit('ERROR: wrong request -  a required parameter was not found:<br>' . $sErrors, $this->_showClasshelp());
            }
            return true;
        }
    
        /**
         * helper function: parse php doc comment block
         * @param string $sComment
         * @return string
         */
        private function _parseComment($sComment) {
            $sReturn = $sComment;
            if (!$sReturn) {
                return '<div class="warning">WARNING: this object has no PHPDOC comment.</div>';
            }
    
            // all @-Tags
            $aTags = array(
                "abstract", "access", "author", "category", "copyright", "deprecated", "example",
                "final", "filesource", "global", "ignore", "internal", "license",
                "link", "method", "name", "package", "param", "property", "return",
                "see", "since", "static", "staticvar", "subpackage", "todo", "tutorial",
                "uses", "var", "version"
            );
    
            $sReturn = preg_replace('@[\ \t][\ \t]*@', ' ', $sReturn); // remove multiple spaces
    
            $sReturn = preg_replace('@^\/\*\*.*@', '', $sReturn); // remove first comment line
            $sReturn = preg_replace('@.*\*\/@', '', $sReturn);  // remove last comment line
            $sReturn = preg_replace('@ \* @', '', $sReturn);    // remove " * " 
            $sReturn = preg_replace('@ \*@', '', $sReturn);     // remove " *" 
            $sReturn = preg_replace('@(<br>)*$@', '', $sReturn);
            $sReturn = preg_replace('@^\<br\>@', '', $sReturn);
    
    
            /*
              foreach(array("param", "return", "var") as $sKey){
              $sReturn=preg_replace('/\@'.$sKey.' ([a-zA-Z\|]*)\ (.*)\<br\>/U', '@'.$sKey.' <span style="font-weight: bold; color:#000;">$1</span> <span style="font-weight: bold;">$2</span><br>', $sReturn);
              }
             */
            $sReturn = preg_replace('/(\@.*)$/s', '<span class="phpdoc">$1</span>', $sReturn);
            foreach ($aTags as $sKey) {
                $sReturn = preg_replace('/\@(' . $sKey . ')/U', '<span class="doctag">@' . $sKey . '</span>', $sReturn);
            }
            if ($sReturn) {
                $sReturn = '<pre>' . $sReturn . '</pre><span class="phpdoctitle">PHPDOC</span>';
            }
            return $sReturn;
        }
    
        /**
         * quit with error: returns 404 and the given errormessage in the body
         * @param type $sError
         */
        private function _quit($sError, $sMore = "") {
            header("HTTP1.0 400 Bad Request");
            echo $this->_wrapAsWebpage(
                    $sMore, '<div class="error">' . $sError . '</div>'
            );
            die();
        }
    
        
    
        /**
         * read a class and fetch information about its methods and parameters
         */
        private function _collectClassData(){
            // loop classes of the given config
            foreach (array_keys($this->_aKnownClasses["classes"]) as $sMyClass) {
    
                $RefClass=&$this->_aKnownClasses["classes"][$sMyClass];
                
                require_once($this->_sClassDir . $RefClass["file"]);
                $oRefClass = new ReflectionClass($sMyClass);
                
                $RefClass=&$this->_aKnownClasses["classes"][$sMyClass];
                $RefClass['phpdoc-class']=$this->_parseComment($oRefClass->getDocComment());
                $RefClass['active']=(array_key_exists("class", $this->_aParams) && $sMyClass===$this->_aParams["class"])?true:false;
    
                // loop configured methods of a class
                // foreach (array_keys($this->_aKnownClasses["classes"][$sMyClass]["actions"]) as $sAction) {
                foreach (array_merge(array("__construct"), array_keys($this->_aKnownClasses["classes"][$sMyClass]["actions"])) as $sAction) {
                // foreach (array_keys($this->_aKnownClasses["classes"][$sMyClass]["actions"]) as $sAction) {
                    $oMethod = false;
                    try {
                        $oMethod = $oRefClass->getMethod($sAction);
                    } catch (Exception $e) {
                        //   nop
                        echo $e->getMessage();
                    }
                    if ($oMethod) {
                        $bActive=(array_key_exists("action", $this->_aParams) && $sAction===$this->_aParams["action"])?true:false;
                        $sComment = $this->_parseComment($oRefClass->getMethod($sAction)->getDocComment());
    
                        $sActionKey=($sAction==="__construct")?"init":"args";
                        $aGivenParams=false;
                        if (array_key_exists($sActionKey, $this->_aParams)){
                            $aGivenParams=json_decode($this->_aParams[$sActionKey]);
                        }
                        
                        if (!$aGivenParams){
                            $aGivenParams=array();
                        }
    
                        $aParams=array();
                        $iRequired=$oMethod->getNumberOfRequiredParameters();
                        
                        // loop params of the method
                        $iCount=0;
                        foreach($oMethod->getParameters() as $oParam){
                            
                            preg_match('@Parameter\ \#.*\[\ (.*)\ \]@', $oParam->__toString(), $aTmp);
                            preg_match('@(.*)=\ (.*)@', $aTmp[1], $aTmp1);
                            $aParams[$iCount]["orig"]=$oParam->__toString();
                            $sValue=false;
                            if (count($aTmp1)){
                                $aParams[$iCount]["varname"]=trim($aTmp1[1]);
                                $sType=$aTmp1[2];
                                $sValue=$aTmp1[2];
                                
                                $sValue=str_replace(array("'", '"'), array('', ''), $sValue);
                                if ($sValue=="Array"){ 
                                    $sValue='{ }';
                                }
                                if ($sValue=="false"){ 
                                    $sValue='';
                                }
                                
                                $aParams[$iCount]["defaultvalue"]=$aTmp1[2];
                            }
                            /*
                            $aParams[$iCount]["required"]=false;
                            } else {
                                $aParams[$iCount]["required"]=true;
                            }
                             */
                            $sValue=(array_key_exists($iCount, $aGivenParams) && $aGivenParams[$iCount])?json_encode($aGivenParams[$iCount]):$sValue;
                            $aParams[$iCount]["value"]=$sValue;
                            
                            $aParams[$iCount]["required"]=$iCount<$iRequired ? true:false;
                            $iCount++;
                        }
                        $RefClass["actions"][$sAction]['active']=$bActive;
                        $RefClass["actions"][$sAction]['phpdoc']=$sComment;
                        $RefClass["actions"][$sAction]['params']=$aParams;
                        $RefClass["actions"][$sAction]['value']=($bActive && array_key_exists("args", $this->_aParams))?$this->_aParams["args"]:'';
                        
                        if($sAction==="__construct"){
                            $RefClass["actions"][$sAction]['value']=(array_key_exists("init", $this->_aParams))?$this->_aParams["init"]:'';
                        }
                    } else {
                        // '' . $sAction . ' &lt;&lt;&lt; ERROR in configuration: this method does not exist'
                    }
                }
                
            }        
        }
        
        /**
         * get html code for input fields of the parameters of a class
         * @param string $sMyClass  name of the class
         * @param string $sAction   name of the method to render
         * @return string
         */
        private function _showMethodInputForm($sMyClass, $sAction, $sIdPrefixBase){
            $sHtml='';
            $iCount=0;
            $aParams=array();
    
            $aRefMethod=$this->_aKnownClasses['classes'][$sMyClass]['actions'][$sAction];
    
            $sHtml.=$aRefMethod["phpdoc"];
            $iCount=count($aRefMethod["params"]);
            if ($iCount) {
                
                $sHtml.='<fieldset><legend>Enter each parameter params to generate json: </legend>';
                foreach($aRefMethod["params"] as $sKey=>$aParam){
                    $sIdInput=$sIdPrefixBase.$sKey;
                    $sFkt='updateMethodVar(\''.$sIdPrefixBase.'\', '.$iCount.')';
                    $sChanges='onchange="'.$sFkt.'" '
                            . 'onkeypress="'.$sFkt.'" '
                            . 'onkeyup="'.$sFkt.'" '
                            ;
                        $sHtml.='<label for="'.$sIdInput.'">'.$aParam["varname"].' = </label>'
                        .'<input type="text" id="'.$sIdInput.'" size="30" '.$sChanges.' value="'.str_replace('"', '&quot;', $aParam["value"]).'"/>'
                        ;
                    if ($aParam["required"]){
                        $sHtml.=' (*)';
                    } else {
                        $sHtml.=' &laquo; optional; default is <span class="defaultvalue">'.$aParam["defaultvalue"].'</span><br>';
                    }
                }
                $sHtml.='</fieldset><br>';
            } else {
                $sHtml.='(no parameters)<br>';
            }
            
            return $sHtml;
        }
        
        
        /**
         * show help - return html code for gui of the given method
         * called in _showClasshelp()
         * @param string $sMyClass  name of the class
         * @param string $sAction   name of the method to render
         * @return string
         */
        private function _showActionHelp($sMyClass, $sAction){
            $sReturn = '';
            $RefClass=&$this->_aKnownClasses["classes"][$sMyClass];
            
            $sStyle = ' style="display: none;"';
            $sBtnValue = '+';
            $sCssClass = '';
            if ($RefClass["actions"][$sAction]["active"]) {
                $sCssClass = 'active';
                $sStyle = '';
                $sBtnValue = '-';
            }
            $sValInit=array_key_exists('value', $RefClass['actions']['__construct'])?$RefClass['actions']['__construct']['value']:'';
            $sValInit=str_replace('"', '&quot;', $sValInit);
            if (array_key_exists("params", $RefClass["actions"][$sAction])) {
                $sIdDescription = "help-" . $sMyClass . "-" . $sAction;
                $sValArgs=array_key_exists('value', $RefClass['actions'][$sAction])?$RefClass['actions'][$sAction]['value']:'';
                $sValArgs=str_replace('"', '&quot;', $sValArgs);
                $sIdPrefix=md5($sMyClass.$sAction);
                $sChanges='onchange="update(\''.$sIdPrefix.'\')" onkeypress="update(\''.$sIdPrefix.'\')" onkeyup="update(\''.$sIdPrefix.'\')" ';
                $sReturn .= '<li class="actionname ' . $sCssClass . '">'
                        . '<h4>'
                        . '<button onclick="toggleDesciption(\'' . $sIdDescription . '\', this)">' . $sBtnValue . '</button> '
                        . $sAction
                        . '</h4>'
                        . '<div id="' . $sIdDescription . '"' . $sStyle . '>'
                        
                        . '<input type="hidden" id="'.$sIdPrefix.'class" value="'.$sMyClass.'" />'
                        . '<input type="hidden" id="'.$sIdPrefix.'action" value="'.$sAction.'" />'
                        
                        . '<blockquote>'
                        
                            . '<h5>init parameters of class &quot;'.$sMyClass.'&quot;</h5>'
                            . '<blockquote>'
                                . '<code>$o = new '.$sMyClass.' ( ... )</code><br>'
                                . $this->_showMethodInputForm($sMyClass, "__construct", $sIdPrefix."init")
                        ;
                        if (count($RefClass['actions']["__construct"]['params'])){
                            $sReturn .='OR<br><fieldset><legend>json</legend>'
                                . '<label for="'.$sIdPrefix.'init">init=</label>'
                                . '<input type="text" id="'.$sIdPrefix.'init" size="80" class="einit" name="init" value="'.$sValInit.'" '.$sChanges.'/><br>'
                                . '</fieldset>';
                        }
                        $sReturn .= '</blockquote>'
    
                            . '<h5>parameters of method &quot;'.$sAction.'&quot;</h5>'
                            . '<blockquote>'
                                . '<code>$o ->' . $sAction . ' ( ... )</code><br>'
                                . $this->_showMethodInputForm($sMyClass, $sAction, $sIdPrefix."args");
                        if (count($RefClass['actions'][$sAction]['params'])){
                            $sReturn .='OR<br><fieldset><legend>json</legend>'
                                . '<label for="'.$sIdPrefix.'args">args=</label>'
                                . '<input type="text" id="'.$sIdPrefix.'args" size="80" name="args" value="'.$sValArgs.'" '.$sChanges.'/><br>'
                                . '</fieldset>';
                        }
                        $sReturn .='</blockquote>'
                        
                        . '<h5>select output type</h5>'
                        . '<blockquote>'
                        . '<select id="'.$sIdPrefix.'type" '.$sChanges.'>';
                            foreach (array("raw", "json") as $sType) {
                                $sReturn .= '<option value="'.$sType.'"';
                                if (array_key_exists("type", $this->_aParams) && $sType === $this->_aParams["type"]){ 
                                    $sReturn .= ' selected="selected"'; 
                                }
                                $sReturn .= '>'.$sType.'</option>';   
                            }                    
                $sReturn .= '</select>'
                        . '</blockquote>'
                        
                        . '<h5>preview:</h5>'
                        . '<blockquote>'
                        . '<textarea id="'.$sIdPrefix.'url" cols="120" rows="2" disabled="disabled"></textarea><br><br>'
                        . '<div id="'.$sIdPrefix.'code"></div>'
                        . '</blockquote>'
                        . '<button onclick="run(\''.$sIdPrefix.'\');">go</button>'
                        . '</blockquote>'
                        . '</div>'
                        . '</li>'
                        ;
            } else {
                $sReturn .= '<li class="actionname error">'
                        . '' . $sAction . ' &lt;&lt;&lt; ERROR in configuration: this method does not exist'
                        . '</li>';
            }
            return $sReturn;
        }
        
        
        /**
         * show help - return html gui with list of allowed classes
         * @return string
         */
        private function _showClasshelp() {
            $sReturn = '';
    
            $this->_collectClassData();
            $sReturn .= '<h2>Explore</h2>'
                    . '<p>allowed classes are:</p>'
                    . '<ul class="classes">';
            foreach (array_keys($this->_aKnownClasses["classes"]) as $sMyClass) {
                $RefClass=&$this->_aKnownClasses["classes"][$sMyClass];
    
                $sIdDescription = "help-" . $sMyClass . "";
                $sClass = '';
                $sStyle = ' style="display: none;"';
                $sBtnValue = '+';
                if ($RefClass["active"]) {
                    $sClass = 'active';
                    $sStyle = '';
                    $sBtnValue = '-';
                }
                $sReturn .= '<li class="classname ' . $sClass . ' ">'
                        . '<h3>'
                        . '<button onclick="toggleDesciption(\'' . $sIdDescription . '\', this)">' . $sBtnValue . '</button> '
                        . '<a href="?class=' . $sMyClass . '">' . $sMyClass . '</a>'
                        . '</h3>'
                        . '<div id="' . $sIdDescription . '"' . $sStyle . '>'
                        . $RefClass["phpdoc-class"]
                        . '<p>allowed methods:</p><ul>';
                    foreach (array_keys($RefClass["actions"]) as $sAction) {
                        if ($sAction=="__construct"){
                            continue;
                        }
                        $sReturn.=$this->_showActionHelp($sMyClass, $sAction);
                    }            
                foreach($RefClass["actions"] as $sAction){
                    // nop
                }
                $sReturn .= '</div>'
                        . '</li>';
            }
            $sReturn .= '</ul>';
            return $sReturn;
        }
    
    
    
        /**
         * render output as html page
         * @param string $sBody   html body
         * @param string $sError  error message
         * @return string
         */
        private function _wrapAsWebpage($sBody = "", $sError = "") {
    
            $sClassSelect = '<span class="urlvalue">[class]</span>';
            $sClassInit = '<span class="urlvalue">[initparams]</span>';
            $sActionSelect = '<span class="urlvalue">[action]</span>';
            $sParamSelect = '<span class="urlvalue">[parameters]</span>';
            $sTypeSelect = '<span class="urlvalue">[raw|json]</span>';
    
            $sSyntax = sprintf(
                    '<pre>?'
                    . '<span class="urlvar">class</span>=%s'
                    . '&<span class="urlvar">init</span>=%s'
                    . '&<span class="urlvar">action</span>=%s'
                    . '&<span class="urlvar">args</span>=%s'
                    . '&<span class="urlvar">type</span>=%s'
                    . '</pre>', $sClassSelect, $sClassInit, $sActionSelect, $sParamSelect, $sTypeSelect
            );
    
            /*
              if ($this->_sClass){
              $sClassSelect='<strong>'.$this->_sClass.'</strong>';
              }
              if ($this->_sAction){
              $sActionSelect='<strong>'.$this->_sAction.'</strong>';
              }
              if ($this->_aArgs){
              $sActionSelect='<strong>'.$this->_aParams["args"].'</strong>';
              }
    
              $sSyntax2=sprintf(
              '<pre>?<span class="urlvar">class</span>=%s&<span class="urlvar">action</span>=%s&<span class="urlvar">args</span>=%s&<span class="urlvar">type</span>=%s</pre>',
              $sClassSelect,
              $sActionSelect,
              $sParamSelect,
              $sTypeSelect
              );
              if ($sSyntax!=$sSyntax2){
              $sSyntax.='<br><br>change it here:<br>'.$sSyntax2;
              }
              $sSyntax='general syntax:<br>'.$sSyntax;
             */
            $sReturn = '<!DOCTYPE html>' . "\n"
                    . '<html>'
                    . '<head>'
                    . '<title>' . $this->_sTitle . '</title>'
                    . '<meta http-equiv="content-type" content="text/html; charset=UTF-8" />'
                    . '<style>'
                        . 'body{background:#aaa; color: #ddd; font-family: verdana,arial; margin: 0;}'
                        . '#content{padding: 2em; color: #444; background:#f8f8f8;}'
                        . '#footer{padding: 0 2em; border-top: 0.5em solid #999;}'
                        . 'a,a:visited{text-decoration: none; color: #000;}'
                        . 'a.button{border: 2px solid #888; background:#aaa; padding: 0.2em; color:#eee; border-radius: 0.3em;}'
                        . 'a.try{background:#080; border-color: #0c0;}'
                        . 'a.try:hover{background:#0b0; border-color: #0c0;}'
                        . 'button{ width: 2em; height: 2em; background: #44a; border-radius: 0.3em; color:#eee; border: 3px solid #338; font-size: 110%;}'
                        . 'h1{background:#222; color:#ccc; padding: 0.3em; margin: 0; border-bottom: 0.2em solid #999;}'
                        . 'h1 a, h1 a:visited{color:#888;}'
                        . 'h1 a span{color:#666; font-size: 45%;}'
                        . 'h2{margin: 3em 0 1em;  }'
                        . 'h3,h4{margin: 0;}'
                        . 'fieldset{border: 1px solid rgba(0,0,0,0.2); background: rgba(0,0,0,0.05); }'
                        . 'input {border: 0px solid #ccc; padding: 0.1em; border-radius: 0.2em; color: #348; font-size: 100%;}' 
                        . 'label{width: 10em; float: left; text-align: right;}'
                        . 'ul{padding-left: 0; margin: 0; border-radius: 0.5em;}'
                        . 'ul ul{margin: 1em;}'
                        . '.classes li{list-style: none; border-radius: 0.3em; padding: 0.3em; margin-bottom: 0.3em;}'
                        . 'li{border-left: 5px solid #aaa; }'
                        . 'li.active{border-left: 5px solid #aaa; }'
                        . 'li li.active{border-left: 5px solid #c00; background: #fcc;}'
                        . 'pre{margin: 0; border: #ddd solid 1px; border-radius: 0.5em; padding: 0.5em; background: #fff; opacity: 0.7;}'
                        . 'pre .urlvar{color: #480; font-weight: bold;}'
                        . 'pre .urlvalue{color: #8c0;}'
                        . 'pre pre{display: inline; padding: 0; border: 0;}'
                        . 'select {border: 2px solid #ddd; padding: 0.3em; border-radius: 0.5em; color: #348; font-size: 100%;}' 
                        . 'td{color:#444;}'
                        . '.classname{ background:#eee; }'
                        . '.actionname{background:#ddd; background: rgba(0,0,0,0.1);}'
                        . '.phpdoctitle{display: none; background:#ccc; color:#fff;float: right; margin-right: 0.5em; padding: 0 1em; border-radius: 0.5em; border-top-left-radius: 0;border-top-right-radius: 0;}'
                        . '.phpdoc{background:#f0f4f8;}'
                        . '.doctag{ color:#080; }'
                        . '.error{ color:#800; background:#fcc; padding: 0.5em; margin-bottom: 2em; border-left: 4px solid;}'
                        . '.warning{ color:#a96; background:#fc8; padding: 0.5em; margin-bottom: 2em; border-left: 4px solid;}'
                        . '.defaultvalue{color: #33c;}'
                    . '</style>'
                    ;
            if($this->_aOptions["enableGui"]){
                $sReturn.= ''
                    . '<script>'
                    . 'function toggleDesciption(sId, a){'
                    . 'var o=document.getElementById(sId);'
                    . 'if (o) {'
                    . 'o.style.display=(o.style.display=="")?"none":"";'
                    . '}'
                    . 'console.log(a);'
                    . 'if (a) {'
                    . 'a.innerHTML=(a.innerHTML=="+")?"-":"+";'
                    . '}'
                    . '}'
                    .'
                        function updateMethodVar(sIdPrefix, iCount){
                            var sOut=\'\';
                            var sVal=false;
                            var aParams=[];
                            if (!document.getElementById(sIdPrefix)){
                                return false;
                            }
                            for (var i=0; i<iCount; i++) {
                                sVal=document.getElementById(sIdPrefix+""+i).value;
                                if(!sVal){
                                    sVal="";
                                } else {
                                    if (sVal % 1 == 0){
                                        sVal=sVal/1;
                                    } else {
                                        if( sVal === "true" || sVal === "false" ){
                                            sVal=sVal || sVal;
                                        } else {
                                            if (
                                                sVal.indexOf(\'"\')<0
                                                && sVal.indexOf("\'")<0
                                                && sVal.indexOf("\[")<0
                                                && sVal.indexOf("\]")<0
                                                && sVal.indexOf("\{")<0
                                                && sVal.indexOf("\}")<0
                                            ){
                                                sVal=\'"\'+sVal+\'"\';
                                            }
                                        }
                                    }
                                }
                                if (sOut){
                                    sOut+=", ";
                                }
                                sOut+=sVal;
                                aParams[i]=sVal;
                            }
                            document.getElementById(sIdPrefix).value="[" + sOut + "]";
                            // document.getElementById(sIdPrefix).value=JSON.stringify(aParams);
                            document.getElementById(sIdPrefix).onchange();
                        }
                        
                        function update(sIdPrefix){
                            if (!document.getElementById(sIdPrefix+"class")){
                                return false;
                            }
                            var sUrl="?";
                            var arguments=["class", "init", "action", "args", "type"];
                            for (var i=0; i<arguments.length; i++){
                                if (document.getElementById(sIdPrefix+arguments[i])
                                && document.getElementById(sIdPrefix+arguments[i]).value
                                ){
                                    sUrl+="&"+arguments[i]+"="+document.getElementById(sIdPrefix+arguments[i]).value;
                                }
                            }
                            document.getElementById(sIdPrefix+"url").value=sUrl;
                            return true;
                        }
                        
                        function run(sIdPrefix){
                            if (update(sIdPrefix)) {
                                location.href=document.getElementById(sIdPrefix+"url").value;
                            }
                            return true;
                        }
                        
                      '
                    . '</script>'
                    ;
            }
                $sReturn.= ''
                    . '</head>'
                    . '<body>';
    
            if($this->_aOptions["enableGui"]){
                $sReturn.= 
                      '<h1><a href="?">' . $this->_sTitle . ' <span>'.$this->_sVersion.'</span></a></h1>'
                    . '<div id="content">'
                    . $sError;
                if($this->_aOptions["enableDump"]){
                    $sReturn.= '<h2>Debug</h2>'
                            . '_aParams:<pre>'.print_r($this->_aParams, true).'</pre>'
                            . '_aKnownClasses:<pre>'.print_r($this->_aKnownClasses, true).'</pre>'
                        ;
                }
                $sReturn.= $sBody
                    . '<h2>Syntax</h2>'
                    . $sSyntax
                    . '</div><div id="footer">'
                    . '<h2>About</h2>'
                    . '<p>'
                    . 'SWS is a free wrapper for other php classes and makes their public '
                    . 'functions (or a subset of them) available as a webservice.<br>'
                    . 'It offers a webinterface to explore available classes and methods. '
                    . 'The shown information is parsed by reading phpdoc.'
                    . '</p>'
                    . '<p>'
                    . 'GNU GPL 3.0;<br>';
                if ($this->_sUrlDoc) {
                    $sReturn.='<a href="' . $this->_sUrlDoc . '">' . $this->_sUrlDoc . '</a> ';
                }
                if ($this->_sUrlHome) {
                    $sReturn.='<a href="' . $this->_sUrlHome . '">' . $this->_sUrlHome . '</a> ';
                }
                $sReturn.='</p></div>';
            } else {
                      
                $sReturn.=
                        '<div id="content" style="height: 1000px; overflow: hidden;">'
                        .$sError .''. $this->_sTitle
                        .'</div>'
                        ;
            }
            $sReturn.='</body></html>';
            return $sReturn;
        }
    
        // ----------------------------------------------------------------------
        // setter
        // ----------------------------------------------------------------------
    
        /**
         * set ouput type of response
         * @param string $sOutputType  one of json|raw
         * @return bool
         */
        public function setOutputType($sOutputType) {
            $this->_sOutputType = $sOutputType;
            return true;
        }
    
        /**
         * put query parameter
         * @param array $aParams  parameters; default false and params will be taken from $_GET and $_POST
         * @return array
         */
        public function setParams($aParams = false) {
    
            $this->_aParams = array();
            if (is_array($aParams)) {
                $this->_aParams = $aParams;
            } else {
                if (isset($_GET) && count($_GET)) {
                    foreach ($_GET as $key => $value)
                        $this->_aParams[$key] = $value;
                }
                if (isset($_POST) && count($_POST)) {
                    foreach ($_POST as $key => $value)
                        $this->_aParams[$key] = $value;
                }
            }
            $this->_parseParams();
            return $this->_aParams;
        }
    
        // ----------------------------------------------------------------------
        // actions
        // ----------------------------------------------------------------------
    
        /**
         * execute function
         * @param type $sOutputType
         */
        public function run($sOutputType = false) {
            if (!count($this->_aParams)){
                $this->setParams();
            }
            $this->_parseParams();
            require_once($this->_sClassDir . $this->_sClassfile);
    
            if (!class_exists($this->_sClass)) {
                $this->_quit("ERROR: class does not exist: " . $this->_sClass);
            }
            
            // ------------------------------------------------------------
            // init object
            // ------------------------------------------------------------
            switch (count($this->_aInit)) {
                case 0: $o = new $this->_sClass;
                    break;
                case 1: $o = new $this->_sClass($this->_aInit[0]);
                    break;
                case 2: $o = new $this->_sClass($this->_aInit[0], $this->_aInit[1]);
                    break;
                case 3: $o = new $this->_sClass($this->_aInit[0], $this->_aInit[1], $this->_aInit[2]);
                    break;
                case 4: $o = new $this->_sClass($this->_aInit[0], $this->_aInit[1], $this->_aInit[2], $this->_aInit[3]);
                    break;
                default: die("internal ERROR in run(): need to set a new case with up to " . count($this->_aInit) . " arguments in " . __FILE__);
            }   
            
            if (!method_exists($o, $this->_sAction)) {
                $this->_quit("ERROR: method does not exist: " . $this->_sClass . " -> " . $this->_sAction . "()");
            }
    
            // ------------------------------------------------------------
            // call function
            // ------------------------------------------------------------
            switch (count($this->_aArgs)) {
                case 0: $return = call_user_func(array($o, $this->_sAction));
                    break;
                case 1: $return = call_user_func(array($o, $this->_sAction), $this->_aArgs[0]);
                    break;
                case 2: $return = call_user_func(array($o, $this->_sAction), $this->_aArgs[0], $this->_aArgs[1]);
                    break;
                case 3: $return = call_user_func(array($o, $this->_sAction), $this->_aArgs[0], $this->_aArgs[1], $this->_aArgs[2]);
                    break;
                case 4: $return = call_user_func(array($o, $this->_sAction), $this->_aArgs[0], $this->_aArgs[1], $this->_aArgs[2], $this->_aArgs[3]);
                    break;
                default: die("internal ERROR in run(): need to set a new case with up to " . count($this->_aArgs) . " arguments in " . __FILE__);
            }
            /*
            if (!$return) {
                $this->_quit("ERROR: no output");
            }
             * 
             */
    
            // ------------------------------------------------------------
            // output
            // ------------------------------------------------------------
            switch ($this->_sOutputType) {
                case "json":
                    header('Content-Type: application/json');
                    echo json_encode($return);
                    break;
                case "raw":
                    if (is_array($return)) {
                        $this->_quit("ERROR: Wrong output type for that method. Return of " . $this->_sClass . '->' . $this->_sAction . "() is an array", $this->_showClasshelp());
                    }
                    echo $return;
                    break;
                default:
                    $this->_quit("ERROR in " . __FUNCTION__ . ": outputtype </em>" . $this->_sOutputType . "</em> is unknown", $this->_showClasshelp());
            }
        }
    
    }
    
    // ----------------------------------------------------------------------