FHCAPI Controller

This commit is contained in:
cgfhtw
2024-01-25 16:41:05 +01:00
parent afc995b4bd
commit 1fec3543cd
6 changed files with 404 additions and 0 deletions
+212
View File
@@ -0,0 +1,212 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* Controller using JSON
*/
class FHCAPI_Controller extends FHC_Controller
{
/**
* Response status
* @see https://github.com/omniti-labs/jsend
*/
const STATUS_SUCCESS = 'success';
const STATUS_FAIL = 'fail';
const STATUS_ERROR = 'error';
/**
* Error types
*/
const ERROR_TYPE_PHP = 'php'; // TODO(chris): php types from severity?
const ERROR_TYPE_EXCEPTION = 'exception';
const ERROR_TYPE_GENERAL = 'general';
const ERROR_TYPE_404 = '404';
const ERROR_TYPE_DB = 'db';
const ERROR_TYPE_VALIDATION = 'validation';
/**
* Return Object
*
* @var array
*/
private $returnObj = [];
/**
* Constructor
*
* @param array $requiredPermissions
* @return void
*/
public function __construct($requiredPermissions = [])
{
if (is_cli())
show_404();
parent::__construct();
$this->config->set_item('error_views_path', VIEWPATH.'errors'.DIRECTORY_SEPARATOR.'json'.DIRECTORY_SEPARATOR);
global $g_result;
$g_result = $this;
ob_start(function ($content) {
$http_response_code = http_response_code();
// NOTE(chris): For security reasons 404 will be displayed the same everywhere
if ($http_response_code == REST_Controller::HTTP_NOT_FOUND)
return $content;
header('Content-Type: application/json; charset=utf-8');
if (!isset($this->returnObj['meta']) || !isset($this->returnObj['meta']['status'])) {
switch ($http_response_code) {
case 200:
$this->setStatus(self::STATUS_SUCCESS);
break;
case 400:
$this->setStatus(self::STATUS_FAIL);
break;
default:
$this->setStatus(self::STATUS_ERROR);
break;
}
}
#$this->returnObj['test'] = implode('/n', headers_list());
return json_encode($this->returnObj);
});
// Load libraries
$this->load->library('AuthLib');
$this->load->library('PermissionLib');
// Checks if the caller is allowed to access to this content
$this->_isAllowed($requiredPermissions);
// For JSON Requests (as opposed to multipart/form-data) get the $_POST variable from the input stream instead
if ($this->input->get_request_header('Content-Type', true) == 'application/json')
$_POST = json_decode($this->security->xss_clean($this->input->raw_input_stream));
}
// ---------------------------------------------------------------
// Handle Output object
// ---------------------------------------------------------------
/**
* @param array $data
* @param string (optional) $type
* @return void
*/
public function addError($data, $type = null)
{
if (!isset($this->returnObj['errors']))
$this->returnObj['errors'] = [];
$error = [];
if (is_array($data)) {
if ($type == self::ERROR_TYPE_VALIDATION)
$error['messages'] = $data;
else
$error = $data;
} else {
$error['message'] = $data;
}
if ($type)
$error['type'] = $type;
$this->returnObj['errors'][] = $error;
}
/**
* @param mixed $data
* @return void
*/
public function setData($data)
{
$this->returnObj['data'] = $data;
}
/**
* @param string $status
* @return void
*/
public function setStatus($status)
{
if (!isset($this->returnObj['meta']))
$this->returnObj['meta'] = [];
$this->returnObj['meta']['status'] = $status;
}
// ---------------------------------------------------------------
// Handle Output object - Shortcut functions
// ---------------------------------------------------------------
/**
* @param array $errors
* @return void
*/
protected function terminateWithValidationErrors($errors)
{
$this->output->set_status_header(REST_Controller::HTTP_BAD_REQUEST);
$this->addError($errors, self::ERROR_TYPE_VALIDATION);
$this->setStatus(self::STATUS_FAILED);
exit(EXIT_ERROR);
}
// TODO(chris): complete list
// ---------------------------------------------------------------
// Security
// ---------------------------------------------------------------
/**
* Checks if the caller is allowed to access to this content with the given permissions
* If it is not allowed will set the HTTP header with code 401
* Wrapper for permissionlib->isEntitled
*
* @param array $requiredPermissions
* @return void
*/
protected function _isAllowed($requiredPermissions)
{
// Checks if this user is entitled to access to this content
if (!$this->permissionlib->isEntitled($requiredPermissions, $this->router->method))
{
$this->output->set_status_header(isLogged() ? REST_Controller::HTTP_FORBIDDEN : REST_Controller::HTTP_UNAUTHORIZED);
$this->addError([
'message' => 'You are not allowed to access to this content',
'controller' => $this->router->class,
'method' => $this->router->method,
'required_permissions' => $this->_rpsToString($requiredPermissions, $this->router->method)
]);
exit; // immediately terminate the execution
}
}
/**
* Converts an array of permissions to a string that contains them as a comma separated list
* Ex: "<permission 1>, <permission 2>, <permission 3>"
*
* @param array $requiredPermissions
* @param string $method
* @return void
*/
protected function _rpsToString($requiredPermissions, $method)
{
if (!isset($requiredPermissions[$method]))
return '';
if (!is_array($requiredPermissions[$method]))
return $requiredPermissions[$method];
return implode(', ', $requiredPermissions[$method]);
}
}
@@ -0,0 +1,65 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
// NOTE(chris): For security reasons 404 will be displayed the same everywhere
?><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>404 Page Not Found</title>
<style type="text/css">
::selection { background-color: #E13300; color: white; }
::-moz-selection { background-color: #E13300; color: white; }
body {
background-color: #fff;
margin: 40px;
font: 13px/20px normal Helvetica, Arial, sans-serif;
color: #4F5155;
}
a {
color: #003399;
background-color: transparent;
font-weight: normal;
}
h1 {
color: #444;
background-color: transparent;
border-bottom: 1px solid #D0D0D0;
font-size: 19px;
font-weight: normal;
margin: 0 0 14px 0;
padding: 14px 15px 10px 15px;
}
code {
font-family: Consolas, Monaco, Courier New, Courier, monospace;
font-size: 12px;
background-color: #f9f9f9;
border: 1px solid #D0D0D0;
color: #002166;
display: block;
margin: 14px 0 14px 0;
padding: 12px 10px 12px 10px;
}
#container {
margin: 10px;
border: 1px solid #D0D0D0;
box-shadow: 0 0 8px #D0D0D0;
}
p {
margin: 12px 15px 12px 15px;
}
</style>
</head>
<body>
<div id="container">
<h1><?php echo $heading; ?></h1>
<?php echo $message; ?>
</div>
</body>
</html>
@@ -0,0 +1,49 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
global $g_result;
// NOTE(chris): remove p tags from CI_Exceptions::show_error() function
$msg = substr($message, 3);
$msg = substr($msg, 0, -4);
$msg = explode('</p><p>', $msg);
$msgs = [];
$error = [
'heading' => $heading
];
/** NOTE(chris): extract Error Number and SQL
* @see: DB_driver.php:692
*/
if (substr(current($msg), 0, 14) == 'Error Number: ') {
$code = substr(array_shift($msg), 14);
if ($code)
$error['code'] = (int)$code;
$msgs[] = array_shift($msg);
$error['sql'] = array_shift($msg);
}
/** NOTE(chris): extract Line Number and Filename
* @see: DB_driver.php:1782
* @see: DB_driver.php:1783
*/
if (count($msg) >= 2) {
if (substr(end($msg), 0, 13) == 'Line Number: ' && substr(prev($msg), 0, 10) == 'Filename: ') {
$error['line'] = (int)substr(array_pop($msg), 13);
$error['filename'] = substr(array_pop($msg), 10);
}
}
foreach ($msg as $m)
$msgs[] = $m;
if (count($msgs) == 1)
$error['message'] = current($msgs);
else
$error['messages'] = $msgs;
$g_result->addError($error, FHCAPI_Controller::ERROR_TYPE_DB);
$g_result->setStatus(FHCAPI_Controller::STATUS_ERROR);
@@ -0,0 +1,27 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
global $g_result;
$error = [
'message' => $message,
'class' => get_class($exception),
'filename' => $exception->getFile(),
'line' => $exception->getLine()
];
if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE === true) {
$error['backtrace'] = [];
foreach (debug_backtrace() as $err) {
if (isset($err['file']) && strpos($err['file'], realpath(BASEPATH)) !== 0) {
$error['backtrace'][] = [
'file' => $err['file'],
'line' => $err['line'],
'function' => $err['function']
];
}
}
}
$g_result->addError($error, FHCAPI_Controller::ERROR_TYPE_EXCEPTION);
$g_result->setStatus(FHCAPI_Controller::STATUS_ERROR);
@@ -0,0 +1,20 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
global $g_result;
// NOTE(chris): remove p tags from CI_Exceptions::show_error() function
$msg = substr($message, 3);
$msg = substr($msg, 0, -4);
$msg = explode('</p><p>', $msg);
$error = [
'heading' => $heading
];
if (count($msg) == 1)
$error['message'] = current($msg);
else
$error['messages'] = $msg;
$g_result->addError($error, FHCAPI_Controller::ERROR_TYPE_GENERAL);
$g_result->setStatus(FHCAPI_Controller::STATUS_ERROR);
@@ -0,0 +1,31 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
global $g_result;
$error = [
'message' => $message,
'severity' => $severity,
'filename' => $filepath,
'line' => $line
];
if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE === true) {
$error['backtrace'] = [];
foreach (debug_backtrace() as $err) {
if (isset($err['file']) && strpos($err['file'], realpath(BASEPATH)) !== 0) {
$error['backtrace'][] = [
'file' => $err['file'],
'line' => $err['line'],
'function' => $err['function']
];
}
}
}
// TODO(chris): change type with severity
$g_result->addError($error, 'php');
if (((E_ERROR | E_PARSE | E_COMPILE_ERROR | E_CORE_ERROR | E_USER_ERROR) & $severity) === $severity) {
$g_result->setStatus('error');
}