Commit bca8c51e authored by Carsten  Rose's avatar Carsten Rose
Browse files

Start refactor to extend HTTP_STATUS Code in exception.

parent 10d77d42
...@@ -9,6 +9,9 @@ ...@@ -9,6 +9,9 @@
.. ..
.. -------------------------------------------------- .. --------------------------------------------------
.. Best Practice T3 reST https://docs.typo3.org/typo3cms/drafts/github/xperseguers/RstPrimer/ .. Best Practice T3 reST https://docs.typo3.org/typo3cms/drafts/github/xperseguers/RstPrimer/
.. Italic *italic*
.. Bold **bold**
.. Code ``text``
.. External Links: `Bootstrap <http://getbootstrap.com/>`_ .. External Links: `Bootstrap <http://getbootstrap.com/>`_
.. Add Images: https://wiki.typo3.org/ReST_Syntax#Images ... .. Add Images: https://wiki.typo3.org/ReST_Syntax#Images ...
.. ..
...@@ -7459,7 +7462,7 @@ To define a QFQ form becomes a REST form by enable one or more of: ...@@ -7459,7 +7462,7 @@ To define a QFQ form becomes a REST form by enable one or more of:
Form: Access > Permit REST: get / insert / update / delete Form: Access > Permit REST: get / insert / update / delete
Endpoint Endpoint
^^^^^^^^ --------
.. tip:: .. tip::
...@@ -7498,7 +7501,7 @@ Only the last <level> of an URI will be processed. The former ones are just to f ...@@ -7498,7 +7501,7 @@ Only the last <level> of an URI will be processed. The former ones are just to f
GET - Read GET - Read
^^^^^^^^^^ ----------
A REST (GET) form has two modes: :: A REST (GET) form has two modes: ::
...@@ -7524,39 +7527,69 @@ Form: ...@@ -7524,39 +7527,69 @@ Form:
+-------------------+------------------------------------------------------------------------------+ +-------------------+------------------------------------------------------------------------------+
| Attribute | Description | | Attribute | Description |
+===================+==============================================================================+ +===================+==============================================================================+
| name=<level> | Level name in URI | | name | *<level>* Mandatory. Level name (Endpoint) in URI. |
+-------------------+------------------------------------------------------------------------------+ +-------------------+------------------------------------------------------------------------------+
| permitNew=rest | The form can be loaded in REST mode with mising parameter 'id' or 'id=0' | | table | Mandatory. Name of the primary table |
+-------------------+------------------------------------------------------------------------------+ +-------------------+------------------------------------------------------------------------------+
| permitEdit=rest | The form can be loaded in REST mode with parameter 'id' > 0 | | Permit REST | *get* Mandatory. The form can be loaded in REST mode. |
+-------------------+------------------------------------------------------------------------------+ +-------------------+------------------------------------------------------------------------------+
Form.parameter: Form.parameter:
+-------------------+----------------------------------------------------------------------------------+
| Attribute | Description |
+===================+==================================================================================+
| restSqlData | Mandatory. SQL query selects content shown in data mode. |
| | | ``restSqlData={{!SELECT id, name, gender FROM Person WHERE id='{{r:T0}}'' }}`` |
+-------------------+----------------------------------------------------------------------------------+
| restSqlList | Mandatory. SQL query selects content shown in data mode. |
| | | ``restSqlData={{!SELECT id, name FROM Person }}`` |
+-------------------+----------------------------------------------------------------------------------+
| restParam | Optional. CSV list of variable names. E.g.: ``restParam=pId,adrId`` |
+-------------------+----------------------------------------------------------------------------------+
| restToken | Optional. User defined string or dynamic token (see below). |
+-------------------+----------------------------------------------------------------------------------+
.. note:
There are no `special-column-names`_ available in `restSqlData` or `restSqlList`. Also there are no
SIPs possible, cause REST typically does not offer sessions/cookies (which are necessary for SIPs).
POST - Insert
-------------
Form:
+-------------------+------------------------------------------------------------------------------+ +-------------------+------------------------------------------------------------------------------+
| Attribute | Description | | Attribute | Description |
+===================+==============================================================================+ +===================+==============================================================================+
| restSqlData | SQL query selects content shown in data mode. | | name | *<level>* Mandatory. Level name (Endpoint) in URI. |
| | `restSqlData={{!SELECT id, name, gender FROM Person WHERE id='{{r:T0}}'' }}` |
+-------------------+------------------------------------------------------------------------------+
| restSqlList | SQL query selects content shown in data mode. |
| | `restSqlData={{!SELECT id, name FROM Person }}` |
+-------------------+------------------------------------------------------------------------------+ +-------------------+------------------------------------------------------------------------------+
| restParam | Optional. CSV list of variable names. E.g.: `restParam=pId,adrId` | | table | Mandatory. Name of the primary table |
+-------------------+------------------------------------------------------------------------------+ +-------------------+------------------------------------------------------------------------------+
| restToken | Optional. User defined string. For dynamic token see below. | | Permit REST | *insert* Mandatory. The form can be loaded in REST mode. |
+-------------------+------------------------------------------------------------------------------+ +-------------------+------------------------------------------------------------------------------+
| id | Missing or '0'.
.. note: PUT - Update
------------
DELETE - Delete
---------------
There are no `special-column-names`_ available in `restSqlData` or `restSqlList`. Also there are no
SIPs possible, cause REST typically does not offer sessions/cookies (which are necessary for SIPs).
Authorization Authorization
------------- -------------
By default, the REST API is public accessible. A QFQ form is only acessible via REST API, if ``Form.permitREST`` enables the HTTP Method (get, post, put, delete)
``Permit New`` or ``Permit Edit`` don't apply to QFQ forms called via REST.
.. important::
By default, the REST API is public accessible.
If this is not wished, HTTP AUTH might be used (configured via webserver) or the If this is not wished, HTTP AUTH might be used (configured via webserver) or the
QFQ internal 'HTTP header token based authorization'. QFQ internal 'HTTP header token based authorization'.
...@@ -7564,10 +7597,9 @@ QFQ internal 'HTTP header token based authorization'. ...@@ -7564,10 +7597,9 @@ QFQ internal 'HTTP header token based authorization'.
Token based authorization Token based authorization
^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^
A form will require a 'token based authorization', as soon as there is a `form.parameter.restToken` defined. A form will require a 'token based authorization', as soon as there is a ``form.parameter.restToken`` defined.
Therefore the HTTP Header 'Authorization' has to be set with `token=<secret token>`. The 'secret token' will Therefore the HTTP Header 'Authorization' has to be set with ``token=<secret token>``. The 'secret token' will
be checked against the server. Using HTTPS, such token can't be sniffed and will typically not be logged in be checked against the server.
any server logs.
Example: :: Example: ::
...@@ -7578,9 +7610,11 @@ Example: :: ...@@ -7578,9 +7610,11 @@ Example: ::
The static setup with `form.parameter.restToken=myCrypticString0123456789 is fine, as long as only one token The static setup with `form.parameter.restToken=myCrypticString0123456789 is fine, as long as only one token
exist. In case of multiple tokens, replace the static string against a SQL query. exist. In case of multiple tokens, replace the static string against a SQL query.
General: The HTML Header Authorization token is available in STORE_CLIENT via '`{{Authorization:C:alnumx}}`. .. tip::
The HTML Header Authorization token is available in STORE_CLIENT via '`{{Authorization:C:alnumx}}`.
For example all created tokens are saved in a table 'Auth' with a column 'token'. Define: :: Best Practice: For example all created tokens are saved in a table 'Auth' with a column 'token'. Define::
form.parameter.restToken={{SELECT a.token FROM Auth AS a WHERE a.token='{{Authorization:C:alnumx}}' }} form.parameter.restToken={{SELECT a.token FROM Auth AS a WHERE a.token='{{Authorization:C:alnumx}}' }}
......
...@@ -19,6 +19,7 @@ $restId = array(); ...@@ -19,6 +19,7 @@ $restId = array();
$restForm = array(); $restForm = array();
$status = 'HTTP/1.0 409 Bad Request'; $status = 'HTTP/1.0 409 Bad Request';
$data = array();
try { try {
try { try {
...@@ -39,13 +40,13 @@ try { ...@@ -39,13 +40,13 @@ try {
if ($id != 0) { if ($id != 0) {
throw new UserFormException('Method POST needs no id or id=0', ERROR_REST_INVALID_ID); throw new UserFormException('Method POST needs no id or id=0', ERROR_REST_INVALID_ID);
} }
$_POST = json_decode(file_get_contents('php://input'), true); $data = json_decode(file_get_contents('php://input'), true);
break; break;
case REQUEST_METHOD_PUT: case REQUEST_METHOD_PUT:
if ($id == 0) { if ($id == 0) {
throw new UserFormException('Method PUT needs an id>0', ERROR_REST_INVALID_ID); throw new UserFormException('Method PUT needs an id>0', ERROR_REST_INVALID_ID);
} }
$_POST = json_decode(file_get_contents('php://input'), true); $data = json_decode(file_get_contents('php://input'), true);
break; break;
case REQUEST_METHOD_DELETE: case REQUEST_METHOD_DELETE:
if ($id == 0) { if ($id == 0) {
...@@ -56,20 +57,33 @@ try { ...@@ -56,20 +57,33 @@ try {
break; break;
} }
if ($data === null) {
throw new NotAcceptableResponseException(
json_encode([ERROR_MESSAGE_TO_USER => 'Invalid JSON',
ERROR_MESSAGE_SUPPORT => json_last_error_msg()]), ERROR_INVALID_VALUE);
}
if(!empty($data)){
$_POST = $data;
}
$qfq = new QuickFormQuery(['bodytext' => $bodytext]); $qfq = new QuickFormQuery(['bodytext' => $bodytext]);
$answer = $qfq->rest($restId, $restForm); $answer = $qfq->rest($restId, $restForm);
$status = 'HTTP/1.0 200 OK'; $status = 'HTTP/1.0 200 OK';
} catch (qfq\CodeException $e) { } catch (qfq\CodeException $e) {
$answer[API_MESSAGE] = $e->formatMessage(); $answer[API_MESSAGE] = $e->formatMessage();
$status=$e->getHttpStatus();
} catch (qfq\UserFormException $e) { } catch (qfq\UserFormException $e) {
$answer[API_MESSAGE] = $e->formatMessage(); $answer[API_MESSAGE] = $e->formatMessage();
$status=$e->getHttpStatus();
} catch (qfq\DbException $e) { } catch (qfq\DbException $e) {
$answer[API_MESSAGE] = $e->formatMessage(); $answer[API_MESSAGE] = $e->formatMessage();
$status=$e->getHttpStatus();
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$answer[API_MESSAGE] = "Generic Exception: " . $e->getMessage(); $answer[API_MESSAGE] = "Generic Exception: " . $e->getMessage();
} }
......
...@@ -141,6 +141,7 @@ const KVP_VALUE_GIVEN = 'value_given'; ...@@ -141,6 +141,7 @@ const KVP_VALUE_GIVEN = 'value_given';
const ERROR_MESSAGE_TO_USER = 'toUser'; // always shown to the user. const ERROR_MESSAGE_TO_USER = 'toUser'; // always shown to the user.
const ERROR_MESSAGE_SUPPORT = 'support'; // Message to help the developer to understand the problem. const ERROR_MESSAGE_SUPPORT = 'support'; // Message to help the developer to understand the problem.
const ERROR_MESSAGE_OS = 'os'; // Error message from the OS - like 'file not found' or specific SQL problem const ERROR_MESSAGE_OS = 'os'; // Error message from the OS - like 'file not found' or specific SQL problem
const ERROR_MESSAGE_HTTP_STATUS = 'httpStatus'; // HTTP Status Code to report
// QFQ Error Codes // QFQ Error Codes
const ERROR_UNKNOW_SANITIZE_CLASS = 1001; const ERROR_UNKNOW_SANITIZE_CLASS = 1001;
...@@ -1762,3 +1763,5 @@ const ATTRIBUTE_DATA_REFERENCE = 'data-reference'; ...@@ -1762,3 +1763,5 @@ const ATTRIBUTE_DATA_REFERENCE = 'data-reference';
// REST // REST
const HTTP_HEADER_AUTHORIZATION = 'Authorization'; const HTTP_HEADER_AUTHORIZATION = 'Authorization';
const HTTP_401 = '401 Unauthorized';
const HTTP_403 = '403 Forbidden';
\ No newline at end of file
...@@ -546,7 +546,7 @@ class QuickFormQuery { ...@@ -546,7 +546,7 @@ class QuickFormQuery {
if ($formMode == FORM_REST) { if ($formMode == FORM_REST) {
$data = ['id' => $rc]; $data = ['id' => $rc];
$flagApiStructureReGroup=false; $flagApiStructureReGroup = false;
break; break;
} }
...@@ -588,7 +588,7 @@ class QuickFormQuery { ...@@ -588,7 +588,7 @@ class QuickFormQuery {
break; break;
case FORM_REST: case FORM_REST:
$flagApiStructureReGroup=false; $flagApiStructureReGroup = false;
$data = $this->doRestGet(); $data = $this->doRestGet();
break; break;
...@@ -596,7 +596,7 @@ class QuickFormQuery { ...@@ -596,7 +596,7 @@ class QuickFormQuery {
throw new CodeException("This statement should never be reached", ERROR_CODE_SHOULD_NOT_HAPPEN); throw new CodeException("This statement should never be reached", ERROR_CODE_SHOULD_NOT_HAPPEN);
} }
if ($flagApiStructureReGroup && is_array($data) ) { if ($flagApiStructureReGroup && is_array($data)) {
// $data['element-update']=... // $data['element-update']=...
$data = $this->groupElementUpdateEntries($data); $data = $this->groupElementUpdateEntries($data);
} }
...@@ -632,6 +632,7 @@ class QuickFormQuery { ...@@ -632,6 +632,7 @@ class QuickFormQuery {
* If not: throw an exception. * If not: throw an exception.
* *
* @param string|array $serverToken * @param string|array $serverToken
* @throws RestException
* @throws CodeException * @throws CodeException
* @throws UserFormException * @throws UserFormException
*/ */
...@@ -642,7 +643,8 @@ class QuickFormQuery { ...@@ -642,7 +643,8 @@ class QuickFormQuery {
return; return;
} }
if ($serverToken === $this->store::getVar(HTTP_HEADER_AUTHORIZATION, STORE_CLIENT . STORE_EMPTY, SANITIZE_ALLOW_ALL)) { $clientToken = $this->store::getVar(HTTP_HEADER_AUTHORIZATION, STORE_CLIENT, SANITIZE_ALLOW_ALL);
if ($serverToken === $clientToken) {
return; return;
} }
...@@ -650,7 +652,17 @@ class QuickFormQuery { ...@@ -650,7 +652,17 @@ class QuickFormQuery {
$seconds = $this->store::getVar(SYSTEM_SECURITY_FAILED_AUTH_DELAY, STORE_SYSTEM); $seconds = $this->store::getVar(SYSTEM_SECURITY_FAILED_AUTH_DELAY, STORE_SYSTEM);
sleep($seconds); sleep($seconds);
throw new UserFormException('Missing or wrong authorization token', ERROR_REST_AUTHORIZATION); if ($clientToken == false) {
throw new RestException(json_encode([ERROR_MESSAGE_TO_USER => 'Missing authorization token',
ERROR_MESSAGE_SUPPORT => "Missing HTTP Header: " . HTTP_HEADER_AUTHORIZATION,
ERROR_MESSAGE_HTTP_STATUS => HTTP_401
]), ERROR_REST_AUTHORIZATION);
}
throw new RestException(json_encode([ERROR_MESSAGE_TO_USER => 'Authorization token not accepted',
ERROR_MESSAGE_SUPPORT => "Missing HTTP Header: " . HTTP_HEADER_AUTHORIZATION,
ERROR_MESSAGE_HTTP_STATUS => HTTP_401
]), ERROR_REST_AUTHORIZATION);
} }
/** /**
...@@ -1423,9 +1435,9 @@ class QuickFormQuery { ...@@ -1423,9 +1435,9 @@ class QuickFormQuery {
* @param string $formMode * @param string $formMode
* *
* @return bool 'true' if SIP exists, else 'false' * @return bool 'true' if SIP exists, else 'false'
* @throws \qfq\CodeException * @throws RestException
* @throws \qfq\UserFormException * @throws CodeException
* @internal param $foundInStore * @throws UserFormException
*/ */
private function validateForm($formNameFoundInStore, $formMode, &$formModeNew) { private function validateForm($formNameFoundInStore, $formMode, &$formModeNew) {
...@@ -1458,7 +1470,7 @@ class QuickFormQuery { ...@@ -1458,7 +1470,7 @@ class QuickFormQuery {
$method = $this->store::getVar(CLIENT_REQUEST_METHOD, STORE_CLIENT); $method = $this->store::getVar(CLIENT_REQUEST_METHOD, STORE_CLIENT);
if (false === Support::findInSet(strtolower($method), $this->formSpec[F_REST_METHOD])) { if (false === Support::findInSet(strtolower($method), $this->formSpec[F_REST_METHOD])) {
throw new UserFormException("Form '" . $this->formSpec[F_NAME] . "' is not allowed with method '$method'", ERROR_FORM_REST); throw new RestException("Endpoint '" . $this->formSpec[F_NAME] . "' is not allowed with method '$method'", ERROR_FORM_REST);
} }
$this->restCheckAuthToken($this->formSpec[F_REST_TOKEN] ?? ''); $this->restCheckAuthToken($this->formSpec[F_REST_TOKEN] ?? '');
...@@ -1467,6 +1479,11 @@ class QuickFormQuery { ...@@ -1467,6 +1479,11 @@ class QuickFormQuery {
case REQUEST_METHOD_GET: case REQUEST_METHOD_GET:
break; break;
case REQUEST_METHOD_POST: case REQUEST_METHOD_POST:
if ($r != 0) {
throw new RestException('Mode GET with id>0 is forbidden', ERROR_UNKNOWN_MODE);
}
$formModeNew = FORM_SAVE;
break;
case REQUEST_METHOD_PUT: case REQUEST_METHOD_PUT:
$formModeNew = FORM_SAVE; $formModeNew = FORM_SAVE;
break; break;
...@@ -1474,7 +1491,7 @@ class QuickFormQuery { ...@@ -1474,7 +1491,7 @@ class QuickFormQuery {
$formModeNew = FORM_DELETE; $formModeNew = FORM_DELETE;
break; break;
default: default:
throw new CodeException('Unknown Request Method: ' . $method, ERROR_UNKNOWN_MODE); throw new RestException('Unknown Request Method: ' . $method, ERROR_UNKNOWN_MODE);
} }
} else { } else {
......
...@@ -29,10 +29,11 @@ require_once(__DIR__ . '/../helper/Support.php'); ...@@ -29,10 +29,11 @@ require_once(__DIR__ . '/../helper/Support.php');
* *
* Throw with message for User and message for Support. * Throw with message for User and message for Support.
* *
* throw new UserFormException( json_encode( throw new UserFormException( json_encode(
* [ERROR_MESSAGE_TO_USER => 'Failed: chmod', [ERROR_MESSAGE_TO_USER => 'Failed: chmod',
* ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'"]), ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'",
* ERROR_IO_CHMOD); ERROR_MESSAGE_HTTP_STATUS => 'HTTP/1.0 409 Bad Request' ]),
ERROR_IO_CHMOD);
* *
* @package qfq * @package qfq
*/ */
...@@ -46,6 +47,8 @@ class AbstractException extends \Exception { ...@@ -46,6 +47,8 @@ class AbstractException extends \Exception {
protected $file = ''; protected $file = '';
protected $line = ''; protected $line = '';
protected $httpStatusCode = '400 Bad Request';
/** /**
* $this->getMessage() might give * $this->getMessage() might give
* a) a simple string, or * a) a simple string, or
...@@ -92,10 +95,17 @@ class AbstractException extends \Exception { ...@@ -92,10 +95,17 @@ class AbstractException extends \Exception {
$msg = $this->getMessage(); $msg = $this->getMessage();
$arrMsg = json_decode($msg, true); $arrMsg = json_decode($msg, true);
if ($arrMsg === null) { if ($arrMsg === null) {
$arrShow[EXCEPTION_MESSAGE] = $msg; $arrShow[EXCEPTION_MESSAGE] = $msg;
$arrMsg[ERROR_MESSAGE_TO_USER] = $msg; $arrMsg[ERROR_MESSAGE_TO_USER] = $msg;
} else { } else {
$arrShow[EXCEPTION_MESSAGE] = $arrMsg[ERROR_MESSAGE_TO_USER]; $arrShow[EXCEPTION_MESSAGE] = $arrMsg[ERROR_MESSAGE_TO_USER];
if (isset($arrMsg[ERROR_MESSAGE_HTTP_STATUS])) {
$this->httpStatusCode = $arrMsg[ERROR_MESSAGE_HTTP_STATUS];
}
} }
$arrDebugHidden[EXCEPTION_FILE] = $this->getFile(); $arrDebugHidden[EXCEPTION_FILE] = $this->getFile();
...@@ -137,7 +147,7 @@ class AbstractException extends \Exception { ...@@ -137,7 +147,7 @@ class AbstractException extends \Exception {
if (!empty($os = $arrMerged[ERROR_MESSAGE_OS] ?? '')) { if (!empty($os = $arrMerged[ERROR_MESSAGE_OS] ?? '')) {
// [ mysqli: 1146 ] Table 'qfq_db.UNKNOWN_TABLE' doesn't exist // [ mysqli: 1146 ] Table 'qfq_db.UNKNOWN_TABLE' doesn't exist
$before=$this->getTableToken( html_entity_decode($arrMerged[ERROR_MESSAGE_OS],ENT_QUOTES)); $before = $this->getTableToken(html_entity_decode($arrMerged[ERROR_MESSAGE_OS], ENT_QUOTES));
$arrMerged[EXCEPTION_SQL_FINAL] = $this->sqlHighlightError($arrMerged[ERROR_MESSAGE_OS], 'mysqli: 1146', $arrMerged[EXCEPTION_SQL_FINAL], $before, "' doesn't exist"); $arrMerged[EXCEPTION_SQL_FINAL] = $this->sqlHighlightError($arrMerged[ERROR_MESSAGE_OS], 'mysqli: 1146', $arrMerged[EXCEPTION_SQL_FINAL], $before, "' doesn't exist");
$arrMerged[EXCEPTION_SQL_FINAL] = $this->sqlHighlightError($arrMerged[ERROR_MESSAGE_OS], 'mysqli: 1064', $arrMerged[EXCEPTION_SQL_FINAL], "the right syntax to use near '", "' at line [0-9]*$"); $arrMerged[EXCEPTION_SQL_FINAL] = $this->sqlHighlightError($arrMerged[ERROR_MESSAGE_OS], 'mysqli: 1064', $arrMerged[EXCEPTION_SQL_FINAL], "the right syntax to use near '", "' at line [0-9]*$");
// [ mysqli: 1054 ] Unknown column "noPsp.pspElement' in 'field list" | "... in 'order clause'" // [ mysqli: 1054 ] Unknown column "noPsp.pspElement' in 'field list" | "... in 'order clause'"
...@@ -173,6 +183,13 @@ class AbstractException extends \Exception { ...@@ -173,6 +183,13 @@ class AbstractException extends \Exception {
} }
/**
* @return string
*/
public function getHttpStatus() {
return $this->$this->httpStatusCode;
}
/** /**
* Extract 'beforeMatch', incl. dynamic db name as token to do underlining later. * Extract 'beforeMatch', incl. dynamic db name as token to do underlining later.
* E.g.: "[ mysqli: 1146 ] Table 'qfq_db.UNKNOWN_TABLE' doesn't exist" * E.g.: "[ mysqli: 1146 ] Table 'qfq_db.UNKNOWN_TABLE' doesn't exist"
...@@ -184,8 +201,8 @@ class AbstractException extends \Exception { ...@@ -184,8 +201,8 @@ class AbstractException extends \Exception {
private function getTableToken($os) { private function getTableToken($os) {
$subject = "Table '.*' "; $subject = "Table '.*' ";
$arr = preg_match("/$subject/", $os, $matches); $arr = preg_match("/$subject/", $os, $matches);
$arr= explode('.', $matches[0]??''); $arr = explode('.', $matches[0] ?? '');
return ($arr[0]??'') . '.'; return ($arr[0] ?? '') . '.';
} }
/** /**
......
...@@ -21,10 +21,10 @@ require_once(__DIR__ . '/AbstractException.php'); ...@@ -21,10 +21,10 @@ require_once(__DIR__ . '/AbstractException.php');
* *
* Throw with message for User and message for Support. * Throw with message for User and message for Support.
* *
* throw new UserFormException( json_encode( throw new UserFormException( json_encode(
* [ERROR_MESSAGE_TO_USER => 'Failed: chmod', [ERROR_MESSAGE_TO_USER => 'Failed: chmod',
* ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'"]), ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'"]),
* ERROR_IO_CHMOD); ERROR_IO_CHMOD);
* *
* @package qfq\exceptions * @package qfq\exceptions
*/ */
......
...@@ -38,6 +38,13 @@ class DbException extends AbstractException { ...@@ -38,6 +38,13 @@ class DbException extends AbstractException {
* [ERROR_MESSAGE_TO_USER] 'toUser' - shown in the client to the user - no details here!!! * [ERROR_MESSAGE_TO_USER] 'toUser' - shown in the client to the user - no details here!!!
* [ERROR_MESSAGE_SUPPORT] 'support' - help for the developer * [ERROR_MESSAGE_SUPPORT] 'support' - help for the developer
* [ERROR_MESSAGE_OS] 'os' - message from the OS, like 'file not found' * [ERROR_MESSAGE_OS] 'os' - message from the OS, like 'file not found'
*
throw new UserFormException( json_encode(
[ERROR_MESSAGE_TO_USER => 'Failed: chmod',
ERROR_MESSAGE_SUPPORT => "Failed: chmod $mode '$pathFileName'",
ERROR_MESSAGE_OS => 'os' - message from the OS, like 'file not found'
ERROR_MESSAGE_HTTP_STATUS => 'HTTP/1.0 409 Bad Request' ]),
ERROR_IO_CHMOD);
* *
* @return string HTML formatted error string * @return string HTML formatted error string
* @return string * @return string
......
...@@ -21,10 +21,11 @@ require_once(__DIR__ . '/AbstractException.php'); ...@@ -21,10 +21,11 @@ require_once(__DIR__ . '/AbstractException.php');
* *
* Throw with message for User and message for Support. * Throw with message for User and message for Support.
* *
* throw new UserFormException( json_encode( throw new UserFormException( json_encode(
* [ERROR_MESSAGE_TO_USER => 'Failed: chmod', [ERROR_MESSAGE_TO_USER => 'Failed: chmod',