diff --git a/.gitignore b/.gitignore index 665bf5a0587255bd35773957066b8c6189be2742..ca089f967a73f425cf203e4d8d4ea5339ebf6e4b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ nbproject /config/projects/ci.json /database/logs.db /public_html/deployment/dummy.db -/public_html/deployment/classes/ramdb.class.php \ No newline at end of file +/public_html/deployment/classes/ramdb.class.php +/public_html/~cache/ \ No newline at end of file diff --git a/public_html/deployment/classes/actionlog.class.php b/public_html/deployment/classes/actionlog.class.php index 75f09df0aa4cd4a29d58fbc6be556088b8c07882..6dff441fbbe1178d77c8311672d9106599d0ef1c 100644 --- a/public_html/deployment/classes/actionlog.class.php +++ b/public_html/deployment/classes/actionlog.class.php @@ -31,7 +31,9 @@ class Actionlog { */ public function __construct($sProject = false) { global $aConfig; - + if (!is_array($aConfig) || !array_key_exists("appRootDir", $aConfig)) { + die(__CLASS__ . "::".__FUNCTION__." ERROR: configuration with \$aConfig was not loaded."); + } $this->_dbfile = $aConfig['appRootDir'] . '/database/logs.db'; if (!file_exists($this->_dbfile)) { $this->_createDb(); diff --git a/public_html/deployment/classes/sws.class.php b/public_html/deployment/classes/sws.class.php new file mode 100644 index 0000000000000000000000000000000000000000..6637546675c731c038671852c1381a180b5d3225 --- /dev/null +++ b/public_html/deployment/classes/sws.class.php @@ -0,0 +1,618 @@ +<?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-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.02 + * @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 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.02 (beta)"; + + /** + * title + * @var string + */ + private $_sTitle = "SWS :: simple web service"; + + /** + * 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; + return true; + } + return false; + } + + /** + * parse parameters (given by setParams) + * @return bool + */ + private function _parseParams() { + $aMinParams = array("class", "action"); + $aMaxParams = array_merge($aMinParams, array("args")); + $sErrors = ''; + + // set defaults + $this->_sClass = false; + $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>"; + } + } + // TODO: checkMaxParams + // 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 + $this->_sClass = $this->_aParams["class"]; + $this->_sClassfile = $this->_aKnownClasses["classes"][$this->_aParams["class"]]["file"]; + } + 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"]; + } + } + 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<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" + ); + /* + $aTagsFollowedByKeyword=array( + "access", "global", + "method", "param", "property", "return", + "see", "since", "static", "staticvar", "subpackage", "todo", "tutorial", + "uses", "var" + ); + * + */ + + $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("Status: 404 Not Found"); + echo $this->_wrapAsWebpage( + $sMore, '<div class="error">' . $sError . '</div>' + ); + die(); + } + + /** + * show help - return html with list of allowed classes + * @return string + */ + private function showClasshelp() { + $sReturn = ''; + + '<a href="help">help</a>'; + $sReturn .= '<h2>Explore</h2>' + . '<p>allowed classes are:</p><ul class="classes">'; + foreach (array_keys($this->_aKnownClasses["classes"]) as $sMyClass) { + + require_once($this->_sClassDir . $this->_aKnownClasses["classes"][$sMyClass]["file"]); + $oRefClass = new ReflectionClass($sMyClass); + $sComment = $this->_parseComment($oRefClass->getDocComment()); + + $sIdDescription = "help-" . $sMyClass . ""; + $sStyle = ' style="display: none;"'; + $sClass = ''; + $sBtnValue = '+'; + if (array_key_exists("class", $this->_aParams) && $this->_aParams["class"] == $sMyClass) { + $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 . '>' + . $sComment + . $this->showActionhelp($sMyClass) + . '</div>' + . '</li>'; + } + $sReturn .= '</ul>'; + return $sReturn; + } + + /** + * show help - return html with list allowed methods of a given class + * @param string $sClass classname you get the methods from + * @return string + */ + private function showActionhelp($sClass) { + $sReturn = '<p>For the class <em>' . $sClass . '</em> these methods are configured:</p><ul>'; + require_once($this->_sClassDir . $this->_aKnownClasses["classes"][$sClass]["file"]); + $oRefClass = new ReflectionClass($sClass); + + foreach (array_keys($this->_aKnownClasses["classes"][$sClass]["actions"]) as $sAction) { + $sStyle = ' style="display: none;"'; + $sBtnValue = '+'; + $sCssClass = ''; + if ( + array_key_exists("class", $this->_aParams) && $this->_aParams["class"] == $sClass && array_key_exists("action", $this->_aParams) && $this->_aParams["action"] == $sAction) { + $sCssClass = 'active'; + $sStyle = ''; + $sBtnValue = '-'; + } + try { + $oMethod = false; + $oMethod = $oRefClass->getMethod($sAction); + } catch (Exception $e) { + // nop + } + if ($oMethod) { + $sComment = $this->_parseComment($oRefClass->getMethod($sAction)->getDocComment()); + $sIdDescription = "help-" . $sClass . "-" . $sAction; + $sReturn .= '<li class="actionname ' . $sCssClass . '">' + . '<h4>' + . '<button onclick="toggleDesciption(\'' . $sIdDescription . '\', this)">' . $sBtnValue . '</button> ' + . $sAction + . '</h4>' + . '<div id="' . $sIdDescription . '"' . $sStyle . '>' + . $sComment + . '<br>try it ... ' + . '' . $sClass . '->' . $sAction . '() ... <br><br>as '; + foreach (array("raw", "json") as $sType) { + $sReturn .= '<a href="?class=' . $sClass . '&action=' . $sAction . '&type=' . $sType . '"' + . ' class="button try"' + . '>' . $sType . '</a> '; + } + $sReturn .= '<br>' + . '</div>' + . '</li>'; + } else { + $sReturn .= '<li class="actionname error">' + . '' . $sAction . ' <<< ERROR in configuration: this method does not exist' + . '</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>'; + $sActionSelect = '<span class="urlvalue">[action]</span>'; + $sParamSelect = '<span class="urlvalue">[parameters]</span>'; + $sTypeSelect = '<span class="urlvalue">[type: raw|json]</span>'; + + $sSyntax = 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 ($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: #ccc; 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; }' + . 'h1 a, h1 a:visited{color:#888;}' + // . 'h1:before{content: "░▒▓█ "}' + . 'h1 a span{color:#666; font-size: 45%;}' + . 'h2{margin: 3em 0 1em; }' + //. 'h2:before{content: "░▒ "}' + //. 'h2:after{content: " ▒░"}' + . 'h3,h4{margin: 0;}' + . '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;}' + . 'pre .urlvar{color: #480; font-weight: bold;}' + . 'pre .urlvalue{color: #8c0;}' + . '.classname{ background:#eee; }' + . '.actionname{background:#ddd; background: rgba(0,0,0,0.1);}' + . '.phpdoctitle{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;}' + . '</style>' + . '<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=="+")?"-":"+";' + . '}' + . '}' + . '</script>' + . '</head>' + . '<body>' + . '<h1><a href="?">' . $this->_sTitle . ' <span>'.$this->_sVersion.'</span></a></h1>' + . '<div id="content">' + . $sError + . $sBody + . '<h2>Syntax</h2>' + . '<p>' + . $sSyntax + . '</p>' + . '</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 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></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); + } + $o = new $this->_sClass; + 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: need to set a new case with up to " . count($this->_aArgs) . " arguments in " . __FILE__); + } + if (!$return) { + 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()); + } + } + +} + +// ---------------------------------------------------------------------- diff --git a/public_html/webservice/index.php b/public_html/webservice/index.php new file mode 100644 index 0000000000000000000000000000000000000000..4f67bc42bafb21f64e8a9ffb7a37a39ec32a6575 --- /dev/null +++ b/public_html/webservice/index.php @@ -0,0 +1,8 @@ +<?php + +require_once("../../config/inc_projects_config.php"); +require_once("../deployment/classes/sws.class.php"); + +$aSwsConfig=json_decode(file_get_contents("sws-config.json"), 1); +$oSws=new sws($aSwsConfig); +$oSws->run(); \ No newline at end of file diff --git a/public_html/webservice/sws-config.json b/public_html/webservice/sws-config.json new file mode 100644 index 0000000000000000000000000000000000000000..dfd5b7596faa92e85baa7d5f0609c1d621be570b --- /dev/null +++ b/public_html/webservice/sws-config.json @@ -0,0 +1,10 @@ +{ + "classes": { + "Actionlog": { + "file": "actionlog.class.php", + "actions": { + "getLogs": {} + } + } + } +} \ No newline at end of file