openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit ba0ddd7f3dd1489ff1d257cc0483c97abaf6d5ce
parent 9571fe349057d63c16d6f7a5c51da4f7d960acdb
Author: dankert <openrat@jandankert.de>
Date:   Sun, 30 Jan 2022 23:38:42 +0100

Refactoring: Only 1 http-endpoint for both the UI and the API. Path "/api" is not available any more, all API data is served under "/".

Diffstat:
Dapi/index.php | 27---------------------------
Mapi/web/index.php | 4++--
Mindex.php | 76++++++----------------------------------------------------------------------
Mmodules/cms/action/object/ObjectDelaclAction.class.php | 3++-
Dmodules/cms/api/API.class.php | 252-------------------------------------------------------------------------------
Mmodules/cms/base/Startup.class.php | 6++++++
Amodules/cms/output/APIOutput.class.php | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amodules/cms/output/BaseOutput.class.php | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amodules/cms/output/HtmlOutput.class.php | 167+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amodules/cms/output/HtmlPlainOutput.class.php | 29+++++++++++++++++++++++++++++
Amodules/cms/output/JsonOutput.class.php | 25+++++++++++++++++++++++++
Amodules/cms/output/Output.class.php | 33+++++++++++++++++++++++++++++++++
Amodules/cms/output/OutputFactory.class.php | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amodules/cms/output/PHPArrayOutput.class.php | 26++++++++++++++++++++++++++
Amodules/cms/output/PHPSerializeOutput.class.php | 26++++++++++++++++++++++++++
Amodules/cms/output/PlainOutput.class.php | 27+++++++++++++++++++++++++++
Amodules/cms/output/XmlOutput.class.php | 29+++++++++++++++++++++++++++++
Amodules/cms/output/YamlOutput.class.php | 26++++++++++++++++++++++++++
Dmodules/cms/ui/UI.class.php | 130-------------------------------------------------------------------------------
Mmodules/cms/ui/themes/default/script/openrat/api.js | 2+-
Mmodules/cms/ui/themes/default/script/openrat/view.js | 7+------
Mmodules/cms/ui/themes/default/script/plugin/jquery-plugin-orSearch.js | 2+-
Mmodules/util/Http.class.php | 46++++++++++++++++------------------------------
Mopenapi.yaml | 2+-
24 files changed, 711 insertions(+), 521 deletions(-)

diff --git a/api/index.php b/api/index.php @@ -1,26 +0,0 @@ -<?php -// Excecuting the CMS application programming interface (API) - -require('../modules/autoload.php'); - -use cms\api\API; -use cms\base\Startup; - -Startup::initialize(); - -try { - // Cookie-Path: Actual path without '/api'. - define('COOKIE_PATH',substr(dirname($_SERVER['SCRIPT_NAME']),0,-3)); - - API::execute(); - -} catch (Exception $e) { - - if (!headers_sent()) - header('HTTP/1.0 500 Internal Server Error'); - - echo $e->__toString(); -} - - -?> -\ No newline at end of file diff --git a/api/web/index.php b/api/web/index.php @@ -10,7 +10,7 @@ <p>A web interface for communicating with the CMS API.</p> <hr> -<form action="../../api/"> +<form action="../../"> <table> <tr> <th>Parameter</th><th>Value</th> @@ -50,7 +50,7 @@ </select> <select name="output"> - <?php foreach( array('HTML','JSON','XML','YAML') as $type ) { ?> + <?php foreach( array('JSON','XML','YAML','HTML','PLAIN',) as $type ) { ?> <option value="<?php echo strtolower($type) ?>"><?php echo $type ?></option> <?php } ?> </select> diff --git a/index.php b/index.php @@ -3,77 +3,12 @@ require('modules/autoload.php'); use cms\base\Startup; +use cms\output\OutputFactory; use cms\ui\UI; Startup::initialize(); -try { - UI::execute(); - -} catch (Exception $e) { - -if (!headers_sent()) -{ - header('HTTP/1.0 500 Internal Server Error'); - header('Content-Type: text/html; charset=UTF-8'); - header('Content-Security-Policy: style-src: inline; default: self'); -} - -?><!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <meta name="viewport" content="width=device-width, initial-scale=1"/> - <title>Service currently unavailable</title> - <style type="text/css"> - - header, main { - display: block - } - - body { - width: 100%; - height: 100%; - background-color: rgba(13,8,5,0.58); - color: white; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; - line-height: 1.4; - font-size: 1.5em; - text-align: center; - } - - pre { - margin:10%; - width: 80%; - overflow: visible; - height: 40%; - color: silver; - text-align: left; - font-size: 0.6rem; - } - - h1 { - font-size: 2em; - } -</style> -</head> -<body> - -<header> - <h1>Sorry, our service is currently unavailable</h1> -</header> - -<main> - - <p>Something went terribly wrong &#x1F61E;</p> - - <pre><?php // Display exceptions only in development mode, because they may contain sensitive internal information like passwords. - if (!defined('DEVELOPMENT') || DEVELOPMENT ) { - echo $e->__toString(); } - ?></pre> -</main> - -</body> -</html><?php - -} +$output = OutputFactory::createOutput(); +header('X-CMS-Output-Type: ' . get_class($output ) ); +header('Content-Type: ' . $output->getContentType() . '; charset=' . Startup::CHARSET); +$output->execute(); +\ No newline at end of file diff --git a/modules/cms/action/object/ObjectDelaclAction.class.php b/modules/cms/action/object/ObjectDelaclAction.class.php @@ -6,6 +6,7 @@ use cms\action\ObjectAction; use cms\model\Permission; use cms\model\BaseObject; use language\Messages; +use util\exception\SecurityException; use util\Http; class ObjectDelaclAction extends ObjectAction implements Method { @@ -27,7 +28,7 @@ class ObjectDelaclAction extends ObjectAction implements Method { $o->load(); if ( !$o->hasRight( Permission::ACL_GRANT ) ) - Http::notAuthorized('no grant rights'); // Da wollte uns wohl einer vereimern. + throw new SecurityException( 'no grant rights' ); // Da wollte uns wohl einer vereimern. $permission->delete(); // Weg mit der ACL diff --git a/modules/cms/api/API.class.php b/modules/cms/api/API.class.php @@ -1,252 +0,0 @@ -<?php - -namespace cms\api; - -use BadMethodCallException; -use cms\action\RequestParams; -use cms\base\Startup; -use cms\Dispatcher; -use Exception; -use util\Http; -use logger\Logger; -use \util\exception\ObjectNotFoundException; -use util\exception\UIException; -use util\exception\SecurityException; -use util\json\JSON; -use util\Session; -use util\XML; -use util\YAML; - -/** - * Entrypoint for all API requests. - */ -class API -{ - const OUTPUT_PHPARRAY = 1; - const OUTPUT_PHPSERIALIZE = 2; - const OUTPUT_JSON = 3; - const OUTPUT_XML = 4; - const OUTPUT_YAML = 5; - const OUTPUT_HTML = 6; - const OUTPUT_PLAIN = 7; - - - /** - * Führt einen API-Request durch. - */ - public static function execute() - { - $createDataWithError = function( $status, $message, $cause ) { - - Logger::warn($cause); - API::sendHTTPStatus($status, $message); - - $data = [ - 'status' => $status, - 'message' => $message - ]; - - // Traces only in DEVELOPMENT mode - // for security reasons, because traces may contain sensitive information. - if (!defined('DEVELOPMENT') || DEVELOPMENT) - $data['cause'] = API::exceptionToArray($cause); - - return $data; - }; - - try { - $request = new RequestParams(); - - $dispatcher = new Dispatcher(); - - $dispatcher->request = $request; - - $data = $dispatcher->doAction(); - - } catch (BadMethodCallException $e) { - $data = $createDataWithError( 204, 'Method not found' , $e ); - } catch (ObjectNotFoundException $e) { - $data = $createDataWithError( 204, 'Object not found' , $e ); - } catch (UIException $e) { - $data = $createDataWithError( 500, 'Internal CMS Error', $e ); - } catch (SecurityException $e) { - $data = $createDataWithError( 403, 'Forbidden' , $e ); - } catch (Exception $e) { - $data = $createDataWithError( 500, 'Internal Server Error', $e ); - } - - - if ( Logger::isTraceEnabled() ) - Logger::trace('Output' . "\n" . print_r($data, true)); - - // Weitere Variablen anreichern. - $data['session'] = ['name' => session_name(), 'id' => session_id(), 'token' => Session::token()]; - $data['version'] = Startup::VERSION; - $data['api' ] = Startup::API_LEVEL; - - - switch (API::discoverOutputType()) { - - case self::OUTPUT_PHPARRAY: - header('Content-Type: application/php-array; charset=UTF-8'); - $output = print_r($data, true); - break; - - case self::OUTPUT_PLAIN: - header('Content-Type: text/plain; charset=UTF-8'); - $output = print_r($data, true); - break; - - case self::OUTPUT_PHPSERIALIZE: - header('Content-Type: application/php-serialized; charset=UTF-8'); - $output = serialize($data); - break; - - case self::OUTPUT_JSON: - header('Content-Type: application/json; charset=UTF-8'); - $output = JSON::encode($data); - break; - - case self::OUTPUT_XML: - $xml = new XML(); - $xml->root = 'server'; // Name des XML-root-Elementes - header('Content-Type: application/xml; charset=UTF-8'); - $output = $xml->encode($data); - break; - - case self::OUTPUT_HTML: - header('Content-Type: text/html; charset=UTF-8'); - $output = '<html><body><h1>API response:</h1><hr /><pre>'; - $output .= print_r($data,true); - $output .= '</pre></body></html>'; - break; - - case self::OUTPUT_YAML: - header('Content-Type: application/yaml; charset=UTF-8'); - $output = YAML::dump($data); - break; - } - - - if (!headers_sent()) - // HTTP Spec: - // "Applications SHOULD use this field to indicate the transfer-length of the - // message-body, unless this is prohibited by the rules in section 4.4." - // - // And the overhead of 'Transfer-Encoding: chunked' is eliminated... - header('Content-Length: ' . strlen($output)); - - echo $output; - } - - /** - * Discovering the output-type for this API-request - * - * @return int constant of self::CMS_API_OUTPUT_* - */ - private static function discoverOutputType() - { - $types = Http::getAccept(); - - $reqOutput = strtolower(@$_REQUEST['output']); - - // First check: The output parameter has precedence over HTTP headers - if ( $reqOutput == 'php-array') - return self::OUTPUT_PHPARRAY; - - if ( $reqOutput == 'php') - return self::OUTPUT_PHPSERIALIZE; - - if ( $reqOutput == 'json') - return self::OUTPUT_JSON; - - if ( $reqOutput == 'xml') - return self::OUTPUT_XML; - - if ( $reqOutput == 'yaml') - return self::OUTPUT_YAML; - - // Lets check the HTTP request headers - if (in_array('application/php-array', $types) ) - return self::OUTPUT_PHPARRAY; - - if (in_array('application/php-serialized', $types) ) - return self::OUTPUT_PHPSERIALIZE; - - if (in_array('application/json', $types) ) - return self::OUTPUT_JSON; - - if (in_array('application/xml', $types) ) - return self::OUTPUT_XML; - - if (in_array('application/yaml', $types) ) - return self::OUTPUT_YAML; - - if (in_array('text/html', $types)) - return self::OUTPUT_HTML; // normally an ordinary browser. - - return self::OUTPUT_PLAIN; // Fallback - } - - /** - * @param $status int HTTP-Status - * @param $text string Statustext - */ - private static function sendHTTPStatus($status, $text) - { - if (headers_sent()) { - //echo "$status $text"; - ; // There is nothing we can do. Every output would destroy the JSON, XML, whatever. - } else { - header('HTTP/1.0 ' . intval($status) . ' ' . $text); - } - } - - /** - * Converting an exception to an array. - * - * This will contain all exceptions out of the exception chain. - * - * @param $e Exception - */ - private static function exceptionToArray($e) - { - $data = array( - 'error'=>get_class($e), - 'description'=>$e->getMessage(), - 'code'=>$e->getCode(), - - 'trace'=>array_merge( array( array( - 'file'=>$e->getFile(), - 'line'=>$e->getLine(), - 'function'=>'', - 'class' => '' - )), API::removeArgsFromTrace($e->getTrace())) - ); - - // the cause of the exception is another exception. - if ( $e->getPrevious() ) - $data['cause'] = API::exceptionToArray($e->getPrevious() ); - - return $data; - } - - - /** - * Removing the call argument from the trace. - * - * This is because of security reasons. The arguments could be an information leak. - * - * @param $trace array - * @return array - */ - private static function removeArgsFromTrace($trace) - { - foreach( $trace as &$t ) - { - unset($t['args']); - } - - return $trace; - } -} diff --git a/modules/cms/base/Startup.class.php b/modules/cms/base/Startup.class.php @@ -27,6 +27,7 @@ class Startup { const MIN_VERSION = '5.6'; // minimum required PHP version. const API_LEVEL = '2'; // public API version. + const CHARSET = 'UTF-8'; // Everything is UTF-8. const IMG_EXT = '.gif'; const IMG_ICON_EXT = '.png'; @@ -36,6 +37,8 @@ class Startup { const THEMES_DIR = './modules/cms/ui/themes/'; const CSS_PREFIX = 'or-'; const DEFAULT_CONFIG_FILE = __DIR__ . '/../../../config/config.yml'; + const APP_DIR = __DIR__ . '/../../../'; + const MODULE_DIR = self::APP_DIR .'modules/'; /** * This is the application name. @@ -67,6 +70,9 @@ class Startup { // in some situations we want to know, if the CMS is really started up. define('APP_STARTED','1'); + + // Cookie path + define('COOKIE_PATH',dirname($_SERVER['SCRIPT_NAME']).'/'); } protected static function checkPHPVersion() diff --git a/modules/cms/output/APIOutput.class.php b/modules/cms/output/APIOutput.class.php @@ -0,0 +1,113 @@ +<?php + +namespace cms\output; + +use BadMethodCallException; +use cms\action\RequestParams; +use cms\api\API; +use cms\base\Startup; +use cms\Dispatcher; +use Exception; +use util\Http; +use logger\Logger; +use \util\exception\ObjectNotFoundException; +use util\exception\UIException; +use util\exception\SecurityException; +use util\json\JSON; +use util\Session; +use util\XML; +use util\YAML; + +/** + * Entrypoint for all API requests. + */ +abstract class APIOutput extends BaseOutput +{ + /** + * Converting an exception to an array. + * + * This will contain all exceptions out of the exception chain. + * + * @param $e Exception + */ + private static function exceptionToArray($e) + { + $data = array( + 'error'=>get_class($e), + 'description'=>$e->getMessage(), + 'code'=>$e->getCode(), + + 'trace'=>array_merge( array( array( + 'file'=>$e->getFile(), + 'line'=>$e->getLine(), + 'function'=>'', + 'class' => '' + )), self::removeArgsFromTrace($e->getTrace())) + ); + + // the cause of the exception is another exception. + if ( $e->getPrevious() ) + $data['cause'] = self::exceptionToArray($e->getPrevious() ); + + return $data; + } + + /** + * Removing the call argument from the trace. + * + * This is because of security reasons. The arguments could be an information leak. + * + * @param $trace array + * @return array + */ + private static function removeArgsFromTrace($trace) + { + foreach( $trace as &$t ) + { + unset($t['args']); + } + + return $trace; + } + + protected function outputData($request, $data) + { + $data += [ + 'output' => $data, + 'session' => [ + 'name' => session_name(), + 'id' => session_id(), + 'token' => Session::token() + ], + 'version' => Startup::VERSION, + 'api' => Startup::API_LEVEL, + ]; + + $output = $this->renderOutput( $data ); + + // HTTP Spec: + // "Applications SHOULD use this field to indicate the transfer-length of the + // message-body, unless this is prohibited by the rules in section 4.4." + // + // And the overhead of 'Transfer-Encoding: chunked' is eliminated... + header('Content-Length: ' . strlen($output)); + echo $output; + } + + abstract protected function renderOutput( $data ); + + protected function setError($text, $cause) + { + $data = [ + 'message' => $text + ]; + + // Traces only in DEVELOPMENT mode + // for security reasons, because traces may contain sensitive information. + if (!defined('DEVELOPMENT') || DEVELOPMENT) + $data['cause'] = $this->exceptionToArray($cause); + + $this->outputData($data); + } + +} diff --git a/modules/cms/output/BaseOutput.class.php b/modules/cms/output/BaseOutput.class.php @@ -0,0 +1,83 @@ +<?php + +namespace cms\output; + +use BadMethodCallException; +use cms\action\RequestParams; +use cms\base\Language as L; +use cms\base\Startup; +use cms\Dispatcher; +use Exception; +use template_engine\engine\TemplateRunner; +use util\Http; +use logger\Logger; +use LogicException; +use \util\exception\ObjectNotFoundException; +use util\exception\UIException; +use util\exception\SecurityException; +use template_engine\engine\TemplateEngine; +use util\Session; +use util\text\TextMessage; + + +/** + * base class for output. + */ +abstract class BaseOutput implements Output +{ + abstract protected function outputData($request, $data); + + /** + * Calling the dispatcher. + */ + public function execute() + { + $request = new RequestParams(); + + $this->beforeAction( $request ); + + $dispatcher = new Dispatcher(); + $dispatcher->request = $request; + + try { + $data = $dispatcher->doAction(); + + $this->outputData( $request,$data ); + } catch (BadMethodCallException $e) { + // Action-Method does not exist. + Logger::debug( $e ); + Http::noContent(); + $this->setError("Method not found",$e); + } catch (ObjectNotFoundException $e) { + Logger::debug( $e ); // only debug, because this may happen on links to deleted objects. + Http::noContent(); + $this->setError("No content",$e); + } catch (UIException $e) { + Logger::warn( $e ); + $this->setError(L::lang($e->key,$e->params),$e); + } catch (SecurityException $e) { + Logger::info($e); + Http::notAuthorized(); + $this->setError("You are not allowed to execute this action.",$e); + } catch (Exception $e) { + Logger::warn( $e ); + // Sorry, our service is currently unavailable + Http::serverError(); + $this->setError("Internal CMS error",$e); + } + } + + protected function beforeAction( $request ) + { + } + + + abstract protected function setError($text, $cause); + + + protected function setStatus( $status, $text ) + { + header('HTTP/1.0 ' . intval($status) . ' ' . $text); + } + +} +\ No newline at end of file diff --git a/modules/cms/output/HtmlOutput.class.php b/modules/cms/output/HtmlOutput.class.php @@ -0,0 +1,167 @@ +<?php + +namespace cms\output; + +use BadMethodCallException; +use cms\action\RequestParams; +use cms\base\Language as L; +use cms\base\Startup;use cms\Dispatcher; +use cms\output\BaseOutput; +use Exception; +use template_engine\engine\TemplateRunner; +use util\Http; +use logger\Logger; +use LogicException; +use \util\exception\ObjectNotFoundException; +use util\exception\UIException; +use util\exception\SecurityException; +use template_engine\engine\TemplateEngine; +use util\text\TextMessage; + + +/** + * Executing the Openrat CMS User Interface. + */ +class HtmlOutput extends BaseOutput +{ + /** + * Shows the complete UI. + */ + protected function beforeAction($request) + { + // Sending the Content-Security-Policy. + self::setContentSecurityPolicy(); + + if ( @$_REQUEST['scope']=='openid' ) { + $request->redirectActionAndMethod('login','oidc'); + } + elseif (empty($request->action)) { + $request->redirectActionAndMethod('index','show' ); + } + + if ( $request->isAction ) + throw new \RuntimeException('The UI does not accept POST requests'); + + if ( in_array( $request->action,['index','tree','title','usergroup']) ) + $request->isUIAction = true; + + } + + + + protected function outputData($request, $data) + { + // The action is able to change its method and action name. + $subaction = $request->method; + $action = $request->action; + + + $this::outputTemplate($request,$action, $subaction, $data['output'] ); + } + + + /** + * Executes and outputs a HTML template. + * + * @param $request RequestParams + * @param $action string action + * @param $subaction string method + * @param $outputData array Output data + */ + private static function outputTemplate($request, $action, $subaction, $outputData) + { + $templateFile = Startup::MODULE_DIR . 'cms/ui/themes/default/html/views/' . $action.'/'.$subaction . '.php'; + + if ( DEVELOPMENT ) + header('X-OR-Template: '.$templateFile); + + $engine = new TemplateRunner(); + //$engine->request = $request; + $engine->executeTemplate( $templateFile, $outputData ); + } + + + /** + * Content-Security-Policy. + */ + private static function setContentSecurityPolicy() + { + // config is not loaded yet. Allow nothing... + header('Content-Security-Policy: default-src \'none\'' ); + + // This will be overwritten by the index action + } + + + protected function setError($text, $cause) + { +if (!headers_sent()) +{ + header('HTTP/1.0 500 Internal Server Error'); + header('Content-Security-Policy: style-src: inline; default: self'); +} + +?><!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1"/> + <title><?php echo $text ?></title> + <style type="text/css"> + + header, main { + display: block + } + + body { + width: 100%; + height: 100%; + background-color: rgba(13,8,5,0.58); + color: white; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + line-height: 1.4; + font-size: 1.5em; + text-align: center; + } + + pre { + margin:10%; + width: 80%; + overflow: visible; + height: 40%; + color: silver; + text-align: left; + font-size: 0.6rem; + } + + h1 { + font-size: 2em; + } +</style> +</head> +<body> + +<header> + <h1><?php echo $text ?></h1> +</header> + +<main> + <p>Something went terribly wrong &#x1F61E;</p> + + <pre><?php // Display exceptions only in development mode, because they may contain sensitive internal information like passwords. + if (!defined('DEVELOPMENT') || DEVELOPMENT ) { + echo $cause->__toString(); + } + ?></pre> +</main> + +</body> +</html><?php + + } + + public function getContentType() + { + return 'text/html'; + } +} diff --git a/modules/cms/output/HtmlPlainOutput.class.php b/modules/cms/output/HtmlPlainOutput.class.php @@ -0,0 +1,29 @@ +<?php + +namespace cms\output; + +use cms\output\APIOutput; +use util\json\JSON; + +/** + * JSON Rendering. + */ +class HtmlPlainOutput extends APIOutput +{ + /** + * Renders the output in JSON Format. + */ + protected function renderOutput( $data ) + { + $output = '<html><body><h1>API response:</h1><hr /><pre>'; + $output .= print_r($data,true); + $output .= '</pre></body></html>'; + + return $output; + } + + public function getContentType() + { + return 'text/html'; + } +} diff --git a/modules/cms/output/JsonOutput.class.php b/modules/cms/output/JsonOutput.class.php @@ -0,0 +1,25 @@ +<?php + +namespace cms\output; + +use cms\output\APIOutput; +use util\json\JSON; + +/** + * JSON Rendering. + */ +class JsonOutput extends APIOutput +{ + /** + * Renders the output in JSON Format. + */ + protected function renderOutput( $data ) + { + return JSON::encode($data); + } + + public function getContentType() + { + return 'application/json'; + } +} diff --git a/modules/cms/output/Output.class.php b/modules/cms/output/Output.class.php @@ -0,0 +1,33 @@ +<?php + +namespace cms\output; + +use BadMethodCallException; +use cms\action\RequestParams; +use cms\base\Language as L; +use cms\Dispatcher; +use Exception; +use template_engine\engine\TemplateRunner; +use util\Http; +use logger\Logger; +use LogicException; +use \util\exception\ObjectNotFoundException; +use util\exception\UIException; +use util\exception\SecurityException; +use template_engine\engine\TemplateEngine; +use util\text\TextMessage; + + +/** + * Executing the Openrat CMS User Interface. + * The request is executed by a dispatcher and the output is displayed with a template. + */ +interface Output +{ + /** + * Rendering output. + */ + public function execute(); + + public function getContentType(); +} diff --git a/modules/cms/output/OutputFactory.class.php b/modules/cms/output/OutputFactory.class.php @@ -0,0 +1,89 @@ +<?php + +namespace cms\output; + +use util\Http; + +class OutputFactory { + + const OUTPUT_PHPARRAY = 1; + const OUTPUT_PHPSERIALIZE = 2; + const OUTPUT_JSON = 3; + const OUTPUT_XML = 4; + const OUTPUT_YAML = 5; + const OUTPUT_HTML = 6; + const OUTPUT_PLAIN = 7; + + + /** + * Map 'output' request to output type. + */ + const MAP_OUTPUT = [ + 'php-array' => self::OUTPUT_PHPARRAY, + 'php' => self::OUTPUT_PHPSERIALIZE, + 'json' => self::OUTPUT_JSON, + 'xml' => self::OUTPUT_XML, + 'yaml' => self::OUTPUT_YAML, + 'plain' => self::OUTPUT_PLAIN + ]; + + /** + * Map Accept-Header to Output type. + */ + const MAP_ACCEPT = [ + 'application/php-array' => self::OUTPUT_PHPARRAY, + 'application/php-serialized' => self::OUTPUT_PHPSERIALIZE, + 'application/json' => self::OUTPUT_JSON, + 'application/xml' => self::OUTPUT_XML, + 'application/yaml' => self::OUTPUT_YAML, + 'text/html' => self::OUTPUT_HTML, + ]; + + public static function createOutput() { + + switch ( self::discoverOutputType() ) { + case self::OUTPUT_PHPARRAY: + return new PHPArrayOutput(); + case self::OUTPUT_PHPSERIALIZE: + return new PHPSerializeOutput(); + case self::OUTPUT_JSON: + return new JsonOutput(); + // case self:: + // return new HtmlPlainOutput(); + case self::OUTPUT_XML: + return new XmlOutput(); + case self::OUTPUT_YAML: + return new YamlOutput(); + case self::OUTPUT_PLAIN: + return new PlainOutput(); + case self::OUTPUT_HTML: + default: + return new HtmlOutput(); + } + } + + + + /** + * Discovering the output-type for this request + * + * @return int constant of self::OUTPUT_* + */ + private static function discoverOutputType() + { + $reqOutput = strtolower(@$_REQUEST['output']); + + // Try 1: Checking the 'output' request parameter. + if ( $reqOutput && array_key_exists( $reqOutput, self::MAP_OUTPUT ) ) + return self::MAP_OUTPUT[ $reqOutput ]; + + // Try 2: Lets check the HTTP request headers + foreach( Http::getAccept() as $acceptType ) + if ( array_key_exists( $acceptType, self::MAP_ACCEPT ) ) + return self::MAP_ACCEPT[ $acceptType ]; + + // Fallback + return self::OUTPUT_HTML; + } + +} +\ No newline at end of file diff --git a/modules/cms/output/PHPArrayOutput.class.php b/modules/cms/output/PHPArrayOutput.class.php @@ -0,0 +1,26 @@ +<?php + +namespace cms\output; + +use cms\output\APIOutput; +use util\json\JSON; + +/** + * JSON Rendering. + */ +class PHPArrayOutput extends APIOutput +{ + /** + * Renders the output in JSON Format. + */ + protected function renderOutput( $data ) + { + header('Content-Type: application/json; charset=UTF-8'); + return JSON::encode($data); + } + + public function getContentType() + { + return 'application/php-array'; + } +} diff --git a/modules/cms/output/PHPSerializeOutput.class.php b/modules/cms/output/PHPSerializeOutput.class.php @@ -0,0 +1,26 @@ +<?php + +namespace cms\output; + +use cms\output\APIOutput; +use util\json\JSON; + +/** + * JSON Rendering. + */ +class PHPSerializeOutput extends APIOutput +{ + /** + * Renders the output in JSON Format. + */ + protected function renderOutput( $data ) + { + header('Content-Type: application/json; charset=UTF-8'); + return serialize($data); + } + + public function getContentType() + { + return 'application/php-serialized'; + } +} diff --git a/modules/cms/output/PlainOutput.class.php b/modules/cms/output/PlainOutput.class.php @@ -0,0 +1,27 @@ +<?php + +namespace cms\output; + +use cms\output\APIOutput; +use util\json\JSON; +use util\YAML; + +/** + * Plain text rendering. + */ +class PlainOutput extends APIOutput +{ + /** + * Renders the output as plain text. + */ + protected function renderOutput( $data ) + { + //return YAML::dump($data); + return print_r($data); + } + + public function getContentType() + { + return 'text/plain'; + } +} diff --git a/modules/cms/output/XmlOutput.class.php b/modules/cms/output/XmlOutput.class.php @@ -0,0 +1,29 @@ +<?php + +namespace cms\output; + +use cms\output\APIOutput; +use util\json\JSON; +use util\XML; + +/** + * JSON Rendering. + */ +class XmlOutput extends APIOutput +{ + /** + * Renders the output in JSON Format. + */ + protected function renderOutput( $data ) + { + $xml = new XML(); + $xml->root = 'server'; // Name des XML-root-Elementes + return $xml->encode($data); + + } + + public function getContentType() + { + return 'application/xml'; + } +} diff --git a/modules/cms/output/YamlOutput.class.php b/modules/cms/output/YamlOutput.class.php @@ -0,0 +1,26 @@ +<?php + +namespace cms\output; + +use cms\output\APIOutput; +use util\json\JSON; +use util\YAML; + +/** + * YAML Rendering. + */ +class YamlOutput extends APIOutput +{ + /** + * Renders the output in YAML Format. + */ + protected function renderOutput( $data ) + { + return YAML::dump($data); + } + + public function getContentType() + { + return 'application/yaml'; + } +} diff --git a/modules/cms/ui/UI.class.php b/modules/cms/ui/UI.class.php @@ -1,130 +0,0 @@ -<?php - -namespace cms\ui; - -use BadMethodCallException; -use cms\action\RequestParams; -use cms\base\Language as L; -use cms\Dispatcher; -use Exception; -use template_engine\engine\TemplateRunner; -use util\Http; -use logger\Logger; -use LogicException; -use \util\exception\ObjectNotFoundException; -use util\exception\UIException; -use util\exception\SecurityException; -use template_engine\engine\TemplateEngine; -use util\text\TextMessage; - - -/** - * Executing the Openrat CMS User Interface. - * The request is executed by a dispatcher and the output is displayed with a template. - * - * @package cms\ui - */ -class UI -{ - /** - * Shows the complete UI. - */ - public static function execute() - { - $request = new RequestParams(); - - try - { - define('COOKIE_PATH',dirname($_SERVER['SCRIPT_NAME']).'/'); - - // Everything is UTF-8. - header('Content-Type: text/html; charset=UTF-8'); - - // Sending the Content-Security-Policy. - self::setContentSecurityPolicy(); - - if ( @$_REQUEST['scope']=='openid' ) { - $request->redirectActionAndMethod('login','oidc'); - } - elseif (empty($request->action)) { - $request->redirectActionAndMethod('index','show' ); - } - - if ( $request->isAction ) - throw new \RuntimeException('The UI does not accept POST requests'); - - if ( in_array( $request->action,['index','tree','title','usergroup']) ) - $request->isUIAction = true; - - UI::executeAction($request); - - } catch (BadMethodCallException $e) { - // Action-Method does not exist. - Logger::debug( $e ); - Http::noContent(); - } catch (ObjectNotFoundException $e) { - Logger::debug( $e ); // only debug, because this may happen on links to deleted objects. - Http::noContent(); - } catch (UIException $e) { - Logger::warn( $e ); - throw new LogicException(L::lang($e->key,$e->params),0, $e); - } catch (SecurityException $e) { - Logger::info($e); - Http::notAuthorized("You are not allowed to execute this action."); - } catch (Exception $e) { - Logger::warn( $e ); - throw new LogicException("Internal CMS error",0, $e); - } - } - - - private static function executeAction($request) - { - $dispatcher = new Dispatcher(); - $dispatcher->request = $request; - - $data = $dispatcher->doAction(); - - - // The action is able to change its method and action name. - $subaction = $dispatcher->request->method; - $action = $dispatcher->request->action; - - UI::outputTemplate($request,$action, $subaction, $data['output']); - } - - - /** - * Executes and outputs a HTML template. - * - * @param $request RequestParams - * @param $action string action - * @param $subaction string method - * @param $outputData array Output data - */ - private static function outputTemplate($request, $action, $subaction, $outputData) - { - $templateFile = __DIR__ . '/themes/default/html/views/' . $action.'/'.$subaction . '.php'; - - if ( DEVELOPMENT ) - header('X-OR-Template: '.$templateFile); - - $engine = new TemplateRunner(); - //$engine->request = $request; - $engine->executeTemplate( $templateFile, $outputData ); - } - - - /** - * Content-Security-Policy. - */ - private static function setContentSecurityPolicy() - { - // config is not loaded yet. Allow nothing... - header('Content-Security-Policy: default-src \'none\'' ); - - // This will be overwritten by the index action - } - - -} diff --git a/modules/cms/ui/themes/default/script/openrat/api.js b/modules/cms/ui/themes/default/script/openrat/api.js @@ -30,7 +30,7 @@ export default class Api { let api = this; return new Promise( (resolve, reject) => { - let load = fetch( './api/', { 'method':'POST', body:formData } ); + let load = fetch( './', { 'method':'POST', body:formData } ); load.then( response => { if ( ! response.ok ) diff --git a/modules/cms/ui/themes/default/script/openrat/view.js b/modules/cms/ui/themes/default/script/openrat/view.js @@ -150,12 +150,7 @@ export default class View { */ static createUrl(action,subaction,id,extraid={},api=false ) { - let url = './'; - - if ( api ) - url += 'api/'; - - url += '?'; + let url = './?'; if(action) url += '&action='+action; diff --git a/modules/cms/ui/themes/default/script/plugin/jquery-plugin-orSearch.js b/modules/cms/ui/themes/default/script/plugin/jquery-plugin-orSearch.js @@ -50,7 +50,7 @@ export default function( options ) $('.or-search').addClass('search--is-active'); dropdownEl.addClass('search-result--is-active'); - let url = './api/?action='+settings.action+'&subaction='+settings.method+'&output=json&search='+searchArgument; + let url = './?action='+settings.action+'&subaction='+settings.method+'&output=json&search='+searchArgument; let response = await fetch( url, { method: 'GET', headers: { diff --git a/modules/util/Http.class.php b/modules/util/Http.class.php @@ -390,8 +390,11 @@ class Http * * @param String $message Eigener Hinweistext */ - public static function serverError($message, $reason = '') + public static function serverError($message = '', $reason = '') { + /* + try { + if (class_exists('util\Session')) { $db = DB::get(); if (is_object($db)) @@ -400,8 +403,12 @@ class Http if (class_exists('logger\Logger')) Logger::warn($message . "\n" . $reason); + } + catch( \Exception $e ) { + //error_log( $e->__toString() ); + }*/ - Http::sendStatus(501, 'Internal Server Error', $message, $reason); + self::sendStatus(501, 'Internal Server Error'); } @@ -414,10 +421,9 @@ class Http * @param String $text Text * @param String $message Eigener Hinweistext */ - public static function notAuthorized($message = '') + public static function notAuthorized() { - Logger::warn("Security warning: $message"); - Http::sendStatus(403, 'Not authorized', $message); + Http::sendStatus(403, 'Not authorized'); } @@ -427,12 +433,10 @@ class Http * Diese Funktion erzeugt einen "HTTP 404 Not found" und das * Skript wird beendet. * - * @param String $text Text - * @param String $message Eigener Hinweistext */ - public static function notFound($text, $message) + public static function notFound() { - Http::sendStatus(404, 'Not found', $message); + Http::sendStatus(404, 'Not found'); } @@ -443,8 +447,7 @@ class Http */ public static function noContent() { - header('HTTP/1.0 204 No Content'); - exit; + self::sendStatus(204,'No Content'); } @@ -453,32 +456,15 @@ class Http * * @param Integer $status HTTP-Status (ganzzahlig) (Default: 501) * @param String $text HTTP-Meldung (Default: 'Internal Server Error') - * @param String $message Eigener Hinweistext (Default: leer) - * @param String $reason Technischer Grund (Default: leer) */ - private static function sendStatus($status = 501, $text = 'Internal Server Error', $message = '', $reason = '') + private static function sendStatus($status = 501, $text = 'Internal Server Error') { if (headers_sent()) { - echo "$status $text\n$message"; + echo "$status $text"; exit; } header('HTTP/1.0 ' . intval($status) . ' ' . $text); - - - $types = Http::getAccept(); - - header('Content-Type: text/html'); - $message = htmlentities($message); - $reason = htmlentities($reason); - echo <<<HTML -<h1>$text</h1> -<p>$message</p> -<pre><?php echo $reason; ?></pre> -<?php - -HTML; - exit; } diff --git a/openapi.yaml b/openapi.yaml @@ -12,7 +12,7 @@ schemes: - https - http paths: - /api/: + /: get: tags: - pet