Skip to content
Snippets Groups Projects
Commit e5d9563b authored by hahn's avatar hahn
Browse files

- added: simple webserice

parent e8c0c4bf
No related branches found
No related tags found
No related merge requests found
......@@ -16,3 +16,4 @@ nbproject
/database/logs.db
/public_html/deployment/dummy.db
/public_html/deployment/classes/ramdb.class.php
/public_html/~cache/
\ No newline at end of file
......@@ -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();
......
<?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&nbsp;(beta)";
/**
* title
* @var string
*/
private $_sTitle = "SWS :: simple&nbsp;web&nbsp;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 . '&amp;action=' . $sAction . '&amp;type=' . $sType . '"'
. ' class="button try"'
. '>' . $sType . '</a> ';
}
$sReturn .= '<br>'
. '</div>'
. '</li>';
} else {
$sReturn .= '<li class="actionname error">'
. '' . $sAction . ' &lt;&lt;&lt; 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());
}
}
}
// ----------------------------------------------------------------------
<?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
{
"classes": {
"Actionlog": {
"file": "actionlog.class.php",
"actions": {
"getLogs": {}
}
}
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment