Commit 2e7b662e authored by Carsten  Rose's avatar Carsten Rose
Browse files

Merge branch 'F7919RestAPIExportPrimaryTableIndex' into 'master'

Rest Implementation with GET,PUT,POST,DELETE and authorization token

See merge request !127
parents f23f0c7d b0d50398
Pipeline #1559 passed with stage
in 2 minutes and 9 seconds
This diff is collapsed.
......@@ -15,34 +15,101 @@ require_once(__DIR__ . '/../core/exceptions/UserFormException.php');
require_once(__DIR__ . '/../core/exceptions/CodeException.php');
require_once(__DIR__ . '/../core/exceptions/DbException.php');
$restId=array();
$restForm=array();
$restId = array();
$restForm = array();
$status = HTTP_400_BAD_REQUEST;
$data = array();
try {
try {
$form = OnString::splitPathInfoToIdForm($_SERVER['PATH_INFO'], $restId, $restForm);
$id=end($restId);
// Fake Bodytext setup
// get latest `ìd`
$id = end($restId);
// Fake Bodytext setup
$bodytext = TYPO3_RECORD_ID . '=' . $id . PHP_EOL;
$bodytext .= TYPO3_FORM . '=' . $form . PHP_EOL;
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case REQUEST_METHOD_GET:
$status = HTTP_200_OK;
break;
case REQUEST_METHOD_POST:
if ($id != 0) {
throw new UserFormException(json_encode([ERROR_MESSAGE_TO_USER => "Forbidden: id>0 with HTTP method $method",
ERROR_MESSAGE_SUPPORT => '',
ERROR_MESSAGE_HTTP_STATUS => HTTP_400_BAD_REQUEST
]), ERROR_REST_INVALID_ID);
}
$data = json_decode(file_get_contents('php://input'), true);
$status = HTTP_201_CREATED;
break;
case REQUEST_METHOD_PUT:
if ($id == 0) {
throw new UserFormException(json_encode([ERROR_MESSAGE_TO_USER => "Forbidden: id==0 with HTTP method $method",
ERROR_MESSAGE_SUPPORT => '',
ERROR_MESSAGE_HTTP_STATUS => HTTP_400_BAD_REQUEST
]), ERROR_REST_INVALID_ID);
}
$data = json_decode(file_get_contents('php://input'), true);
$status = HTTP_200_OK;
break;
case REQUEST_METHOD_DELETE:
if ($id == 0) {
throw new UserFormException(json_encode([ERROR_MESSAGE_TO_USER => "Forbidden: id==0 with HTTP method $method",
ERROR_MESSAGE_SUPPORT => '',
ERROR_MESSAGE_HTTP_STATUS => HTTP_400_BAD_REQUEST
]), ERROR_REST_INVALID_ID);
}
$status = HTTP_200_OK;
break;
default:
throw new UserFormException(json_encode([ERROR_MESSAGE_TO_USER => 'Unsupported/unknown HTTP request method',
ERROR_MESSAGE_SUPPORT => 'HTTP Code: ' . $method,
ERROR_MESSAGE_HTTP_STATUS => HTTP_403_METHOD_NOT_ALLOWED
]), ERROR_UNKNOWN_MODE);
break;
}
if ($data === null) {
throw new UserFormException(json_encode([ERROR_MESSAGE_TO_USER => "Missing or broken JSON",
ERROR_MESSAGE_SUPPORT => json_last_error_msg(),
ERROR_MESSAGE_HTTP_STATUS => HTTP_400_BAD_REQUEST
]), ERROR_BROKEN_PARAMETER);
}
if (!empty($data)) {
$_POST = $data;
}
$qfq = new QuickFormQuery(['bodytext' => $bodytext]);
$answer = $qfq->rest($restId, $restForm);
} catch (qfq\CodeException $e) {
$answer[API_MESSAGE] = $e->formatMessage();
$status = $e->getHttpStatus();
} catch (qfq\UserFormException $e) {
$answer[API_MESSAGE] = $e->formatMessage();
$status = $e->getHttpStatus();
} catch (qfq\DbException $e) {
$answer[API_MESSAGE] = $e->formatMessage();
$status = $e->getHttpStatus();
}
} catch (\Exception $e) {
$answer[API_MESSAGE] = "Generic Exception: " . $e->getMessage();
}
header('HTTP/1.0 ' . $status);
header("Content-Type: application/json");
echo json_encode($answer);
......@@ -67,7 +67,7 @@ const SQL_FORM_ELEMENT_BY_ID = "SELECT * FROM FormElement AS fe WHERE fe.id = ?"
const SQL_FORM_ELEMENT_RAW = "SELECT * FROM FormElement AS fe WHERE fe.formId = ? AND fe.deleted = 'no' AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
const SQL_FORM_ELEMENT_SPECIFIC_CONTAINER = "SELECT *, ? AS 'nestedInFieldSet' FROM FormElement AS fe WHERE fe.formId = ? AND fe.deleted = 'no' AND FIND_IN_SET(fe.class, ? ) AND fe.feIdContainer = ? AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
const SQL_FORM_ELEMENT_ALL_CONTAINER = "SELECT *, ? AS 'nestedInFieldSet' FROM FormElement AS fe WHERE fe.formId = ? AND fe.deleted = 'no' AND FIND_IN_SET(fe.class, ? ) AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
const SQL_FORM_ELEMENT_SIMPLE_ALL_CONTAINER = "SELECT fe.id, fe.feIdContainer, fe.name, fe.label, fe.type, fe.encode, fe.checkType, fe.checkPattern, fe.mode, fe.modeSql, fe.parameter, fe.dynamicUpdate FROM FormElement AS fe, Form AS f WHERE f.name = ? AND f.id = fe.formId AND fe.deleted = 'no' AND fe.class = 'native' AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
const SQL_FORM_ELEMENT_SIMPLE_ALL_CONTAINER = "SELECT fe.id, fe.feIdContainer, fe.name, fe.value, fe.label, fe.type, fe.encode, fe.checkType, fe.checkPattern, fe.mode, fe.modeSql, fe.parameter, fe.dynamicUpdate FROM FormElement AS fe, Form AS f WHERE f.name = ? AND f.id = fe.formId AND fe.deleted = 'no' AND fe.class = 'native' AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
const SQL_FORM_ELEMENT_CONTAINER_TEMPLATE_GROUP = "SELECT fe.id, fe.name, fe.label, fe.maxLength, fe.parameter FROM FormElement AS fe, Form AS f WHERE f.name = ? AND f.id = fe.formId AND fe.deleted = 'no' AND fe.class = 'container' AND fe.type='templateGroup' AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
const SQL_FORM_ELEMENT_TEMPLATE_GROUP_FE_ID = "SELECT * FROM FormElement AS fe WHERE fe.id = ? AND fe.deleted = 'no' AND fe.class = 'container' AND fe.type='templateGroup' AND fe.enabled='yes' ";
//const SQL_FORM_ELEMENT_NATIVE_TG_COUNT = "SELECT fe.*, IFNULL(feTg.maxLength,0) AS _tgCopies FROM FormElement AS fe LEFT JOIN FormElement AS feTg ON fe.feIdContainer=feTg.id AND feTg.deleted = 'no' AND feTg.class = 'container' AND feTg.type='templateGroup' AND feTg.enabled='yes' WHERE fe.formId = ? AND fe.deleted = 'no' AND fe.class = 'native' AND fe.enabled='yes'";
......@@ -141,6 +141,7 @@ const KVP_VALUE_GIVEN = 'value_given';
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_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
const ERROR_UNKNOW_SANITIZE_CLASS = 1001;
......@@ -227,7 +228,6 @@ const ERROR_SMALLER_THAN_MIN = 1083;
const ERROR_LARGER_THAN_MAX = 1084;
const ERROR_INVALID_DECIMAL_FORMAT = 1085;
const ERROR_INVALID_DATE = 1086;
const ERROR_FORM_REST = 1087;
// Subrecord
const ERROR_SUBRECORD_MISSING_COLUMN_ID = 1100;
......@@ -363,6 +363,10 @@ const ERROR_FORM_RESERVED_NAME = 2800;
const ERROR_IMPORT_MISSING_EXPLICIT_TYPE = 2900;
const ERROR_IMPORT_LIST_SHEET_NAMES = 2901;
// REST
const ERROR_FORM_REST = 3000;
const ERROR_REST_AUTHORIZATION = 3001;
const ERROR_REST_INVALID_ID = 3002;
//
// Store Names: Identifier
//
......@@ -418,12 +422,18 @@ const CLIENT_SERVER_ADDRESS = 'SERVER_ADDR';
const CLIENT_SERVER_PORT = 'SERVER_PORT';
const CLIENT_REMOTE_ADDRESS = 'REMOTE_ADDR';
const CLIENT_REQUEST_SCHEME = 'REQUEST_SCHEME';
const CLIENT_REQUEST_METHOD = 'REQUEST_METHOD';
const CLIENT_SCRIPT_FILENAME = 'SCRIPT_FILENAME';
const CLIENT_QUERY_STRING = 'QUERY_STRING';
const CLIENT_REQUEST_URI = 'REQUEST_URI';
const CLIENT_SCRIPT_NAME = 'SCRIPT_NAME';
const CLIENT_PHP_SELF = 'PHP_SELF';
const REQUEST_METHOD_GET = 'GET';
const REQUEST_METHOD_POST = 'POST';
const REQUEST_METHOD_PUT = 'PUT';
const REQUEST_METHOD_DELETE = 'DELETE';
// _COOKIE
const CLIENT_COOKIE_QFQ = 'cookieQfq';
......@@ -541,6 +551,7 @@ const SYSTEM_SECURITY_ATTACK_DELAY_DEFAULT = 5; // Detected attack causes x seco
const SYSTEM_SECURITY_SHOW_MESSAGE = 'securityShowMessage'; // Detected attack shows an error message
const SYSTEM_SECURITY_GET_MAX_LENGTH = 'securityGetMaxLength'; // Trim every character (before conversion) to SECURITY_GET_MAX_LENGTH chars;
const SYSTEM_SECURITY_GET_MAX_LENGTH_DEFAULT = 50; // Default max length for get variables
const SYSTEM_SECURITY_FAILED_AUTH_DELAY = 'securityFailedAuthDelay'; // Failed auth causes x seconds delay
const GET_EXTRA_LENGTH_TOKEN = '_';
......@@ -876,6 +887,7 @@ const F_TITLE = 'title';
const F_TABLE_NAME = 'tableName';
const F_PRIMARY_KEY = 'primaryKey';
const F_PRIMARY_KEY_DEFAULT = 'id';
const F_REST_METHOD = 'restMethod';
const F_REQUIRED_PARAMETER_NEW = 'requiredParameterNew';
const F_REQUIRED_PARAMETER_EDIT = 'requiredParameterEdit';
const F_EXTRA_DELETE_FORM = 'extraDeleteForm';
......@@ -991,6 +1003,7 @@ const F_SHOW_ID_IN_FORM_TITLE = SYSTEM_SHOW_ID_IN_FORM_TITLE;
const F_REST_SQL_LIST = 'restSqlList';
const F_REST_SQL_DATA = 'restSqlData';
const F_REST_PARAM = 'restParam';
const F_REST_TOKEN = 'restToken';
const CLIENT_REST_ID = '_id';
const CLIENT_REST_FORM = '_form';
......@@ -1745,4 +1758,17 @@ const DND_SUBRECORD_FORM_ID = 'dnd-subrecord-form-id';
const DND_ORD_HTML_ID_PREFIX = 'qfq-dnd-ord-id-';
// Application Test: SELENIUM
const ATTRIBUTE_DATA_REFERENCE = 'data-reference';
\ No newline at end of file
const ATTRIBUTE_DATA_REFERENCE = 'data-reference';
// REST
const HTTP_HEADER_AUTHORIZATION = 'Authorization';
const HTTP_200_OK = '200 OK';
const HTTP_201_CREATED = '201 Created';
const HTTP_400_BAD_REQUEST = '400 Bad Request';
const HTTP_401_UNAUTHORIZED = '401 Unauthorized';
const HTTP_403_FORBIDDEN = '403 Forbidden';
const HTTP_403_METHOD_NOT_ALLOWED = '405 Method Not Allowed';
const HTTP_404_NOT_FOUND = '404 Not Found';
const HTTP_409_CONFLICT = '409 Conflict';
\ No newline at end of file
......@@ -168,8 +168,11 @@ class QuickFormQuery {
$this->store = Store::getInstance($bodytext, $phpUnit);
$timeout = $this->store::getVar(SYSTEM_SESSION_TIMEOUT_SECONDS, STORE_SYSTEM);
Session::checkSessionExpired($timeout);
// If an FE user logs out and a different user logs in (same browser session) - the old values has to be destroyed!
if (Session::getAndDestroyFlagFeUserHasChanged() || Session::checkSessionExpired($timeout)) {
if (Session::getAndDestroyFlagFeUserHasChanged()) {
$this->store->unsetStore(STORE_USER);
}
......@@ -341,9 +344,13 @@ class QuickFormQuery {
$flagApiStructureReGroup = true;
// Fill STORE_FORM
if ($formMode === FORM_UPDATE || $formMode === FORM_SAVE) {
$fillStoreForm = new FillStoreForm();
$fillStoreForm->process($formMode);
switch ($formMode) {
case FORM_UPDATE:
case FORM_SAVE:
case FORM_REST:
$fillStoreForm = new FillStoreForm();
$fillStoreForm->process($formMode);
break;
}
$recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_TYPO3 . STORE_CLIENT . STORE_ZERO);
......@@ -365,12 +372,13 @@ class QuickFormQuery {
}
}
// Session Expire happens quite late, cause it can be configured per form.
// Check 'session expire' happens quite late, cause it can be configured per form.
Session::checkSessionExpired($this->formSpec[F_SESSION_TIMEOUT_SECONDS]);
if ($formName !== false) {
// Validate only if there is a 'real' form (not a FORM_DELETE with only a tablename).
$sipFound = $this->validateForm($foundInStore, $formMode);
// Validate (only if there is a 'real' form, not a FORM_DELETE with only a tablename).
// Attention: $formModeNew will be set
$sipFound = $this->validateForm($foundInStore, $formMode, $formModeNew);
} else {
// FORM_DELETE without a form definition: Fake the form with only a tableName.
......@@ -411,10 +419,10 @@ class QuickFormQuery {
}
// Check (and release) dirtyRecord.
if ($formMode === FORM_DELETE || $formMode === FORM_SAVE) {
if ($formModeNew === FORM_DELETE || $formModeNew === FORM_SAVE) {
$dirty = new Dirty(false, $this->dbIndexData, $this->dbIndexQfq);
$answer = $dirty->checkDirtyAndRelease($formMode, $this->formSpec[F_RECORD_LOCK_TIMEOUT_SECONDS],
$answer = $dirty->checkDirtyAndRelease($formModeNew, $this->formSpec[F_RECORD_LOCK_TIMEOUT_SECONDS],
$this->formSpec[F_DIRTY_MODE], $this->formSpec[F_TABLE_NAME], $this->formSpec[F_PRIMARY_KEY], $recordId, true);
// In case of a conflict, return immediately
......@@ -426,7 +434,7 @@ class QuickFormQuery {
}
// FORM_LOAD: if there is a foreign exclusive record lock - show form in F_MODE_READONLY mode.
if ($formMode === FORM_LOAD) {
if ($formModeNew === FORM_LOAD) {
$dirty = new Dirty(false, $this->dbIndexData, $this->dbIndexQfq);
$recordDirty = array();
$rcLockFound = $dirty->getCheckDirty($this->formSpec[F_TABLE_NAME], $recordId, $recordDirty, $msg);
......@@ -435,7 +443,7 @@ class QuickFormQuery {
}
}
switch ($formMode) {
switch ($formModeNew) {
case FORM_DELETE:
$build = new Delete($this->dbIndexData);
break;
......@@ -468,10 +476,10 @@ class QuickFormQuery {
}
$formAction = new FormAction($this->formSpec, $this->dbArray[$this->dbIndexData], $this->phpUnit);
switch ($formMode) {
switch ($formModeNew) {
case FORM_LOAD:
$formAction->elements($recordId, $this->feSpecAction, FE_TYPE_BEFORE_LOAD);
$data = $build->process($formMode);
$data = $build->process($formModeNew);
$tmpClass = is_numeric($this->formSpec[F_BS_COLUMNS]) ? ('col-md-' . $this->formSpec[F_BS_COLUMNS]) : $this->formSpec[F_BS_COLUMNS];
// $data = Support::wrapTag("<div class='" . 'col-md-' . $this->formSpec[F_BS_COLUMNS] . "'>", $data);
$data = Support::wrapTag('<div class="' . $tmpClass . '">', $data);
......@@ -482,7 +490,7 @@ class QuickFormQuery {
case FORM_UPDATE:
$formAction->elements($recordId, $this->feSpecAction, FE_TYPE_BEFORE_LOAD);
// data['form-update']=....
$data = $build->process($formMode);
$data = $build->process($formModeNew);
$formAction->elements($recordId, $this->feSpecAction, FE_TYPE_AFTER_LOAD);
break;
......@@ -497,7 +505,7 @@ class QuickFormQuery {
case FORM_SAVE:
$this->logFormSubmitRequest();
$recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP);
$recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_TYPO3);
// Action: Before
$feTypeList = FE_TYPE_BEFORE_SAVE . ',' . ($recordId == 0 ? FE_TYPE_BEFORE_INSERT : FE_TYPE_BEFORE_UPDATE);
......@@ -536,6 +544,11 @@ class QuickFormQuery {
// Action: Sendmail
$formAction->elements($rc, $this->feSpecAction, FE_TYPE_SENDMAIL);
if ($formMode == FORM_REST) {
$data = ['id' => $rc];
$flagApiStructureReGroup = false;
break;
}
$customForward = $this->setForwardModePage();
......@@ -560,10 +573,7 @@ class QuickFormQuery {
$feSpecNative = HelperFormElement::setLanguage($feSpecNative, $parameterLanguageFieldName);
$this->feSpecNative = HelperFormElement::setFeContainerFormElementId($feSpecNative, $this->formSpec[F_ID], $recordId);
// Retrieve FE Values as JSON
// $data['form-update']=...
// $data = $build->process($formMode, $htmlElementNameIdZero);
$data = $build->process($formMode, false, $this->feSpecNative);
$data = $build->process($formModeNew, false, $this->feSpecNative);
}
break;
......@@ -578,8 +588,8 @@ class QuickFormQuery {
break;
case FORM_REST:
$data = $this->doRestGet();
$flagApiStructureReGroup = false;
$data = $this->doRestGet();
break;
default:
......@@ -604,7 +614,7 @@ class QuickFormQuery {
*/
private function doRestGet() {
$this->copyGenericRestParamToNamed();
$this->nameGenericRestParam();
$r = $this->store::getVar(TYPO3_RECORD_ID, STORE_TYPO3);
$key = empty($r) ? F_REST_SQL_LIST : F_REST_SQL_DATA;
......@@ -617,6 +627,43 @@ class QuickFormQuery {
}
/**
* Checks if $serverToken matches HTTP_HEADER_AUTHORIZATION,
* If not: throw an exception.
*
* @param string|array $serverToken
* @throws CodeException
* @throws UserFormException
*/
private function restCheckAuthToken($serverToken) {
// No serverToken: no check necessary
if ($serverToken === '') {
return;
}
$clientToken = $this->store::getVar(HTTP_HEADER_AUTHORIZATION, STORE_CLIENT, SANITIZE_ALLOW_ALL);
if ($serverToken === $clientToken) {
return;
}
// Delay before answering.
$seconds = $this->store::getVar(SYSTEM_SECURITY_FAILED_AUTH_DELAY, STORE_SYSTEM);
sleep($seconds);
if ($clientToken == false) {
throw new UserFormException(json_encode([ERROR_MESSAGE_TO_USER => 'Missing authorization token',
ERROR_MESSAGE_SUPPORT => "Missing HTTP Header: " . HTTP_HEADER_AUTHORIZATION,
ERROR_MESSAGE_HTTP_STATUS => HTTP_401_UNAUTHORIZED
]), ERROR_REST_AUTHORIZATION);
}
throw new UserFormException(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_UNAUTHORIZED
]), ERROR_REST_AUTHORIZATION);
}
/**
* STORE_CLIENT: copy parameter _id1,_id2,...,_idN to named variables, specified via $this->formSpec[F_REST_PARAM] (CSV list)
*
......@@ -624,7 +671,7 @@ class QuickFormQuery {
* @throws UserFormException
* @throws UserReportException
*/
private function copyGenericRestParamToNamed() {
private function nameGenericRestParam() {
$paramNames = explode(',', $this->formSpec[F_REST_PARAM] ?? '');
......@@ -954,13 +1001,10 @@ class QuickFormQuery {
HelperFormElement::explodeParameter($form, F_PARAMETER);
unset($form[F_PARAMETER]);
if (isset($form[FE_FILL_STORE_VAR])) {
$fillStoreVar = $form[FE_FILL_STORE_VAR];
unset($form[FE_FILL_STORE_VAR]);
}
// Save specific elements to be expanded later.
$parseLater = OnArray::getArrayItems($form, [F_FORWARD_PAGE, F_REST_SQL_LIST, F_REST_SQL_DATA]);
$parseLater = OnArray::getArrayItems($form, [F_FORWARD_PAGE, FE_FILL_STORE_VAR, F_REST_SQL_LIST, F_REST_SQL_DATA]);
$form[FE_FILL_STORE_VAR] = '';
$form[F_FORWARD_PAGE] = '';
$form[F_REST_SQL_LIST] = '';
$form[F_REST_SQL_DATA] = '';
......@@ -991,8 +1035,17 @@ class QuickFormQuery {
// and for evaluating variables in the Form title
$this->store->fillStoreWithRecord($form[F_TABLE_NAME], $recordId, $this->dbArray[$this->dbIndexData], $form[F_PRIMARY_KEY]);
// In case $form[F_REST_TOKEN] is a query which results to an empty answer; every token will fail.
$flagRestToken = !empty($form[F_REST_TOKEN]);
// Evaluate all fields
$formSpec = $this->evaluate->parseArray($form);
// If it is empty, set it to true to force the TOKEN check (which will always fail)
if ($flagRestToken && $form[F_REST_TOKEN] == '') {
$form[F_REST_TOKEN] = true;
}
$parameterLanguageFieldName = $this->store->getVar(SYSTEM_PARAMETER_LANGUAGE_FIELD_NAME, STORE_SYSTEM);
$formSpec = HelperFormElement::setLanguage($formSpec, $parameterLanguageFieldName);
......@@ -1015,8 +1068,11 @@ class QuickFormQuery {
!empty($form[FORM_LOG_ACTIVE]) && Logger::logFormLine($form, "F:$mode:evaluated:" . date('Y-m-d H:i:s'), $form, true);
// Fire FE_FILL_STORE_VAR after the primary form record has been loaded
if (!empty($fillStoreVar)) {
$rows = $this->evaluate->parse($fillStoreVar, ROW_EXPECT_0_1);
if (!empty($formSpec[FE_FILL_STORE_VAR])) {
$rows = $this->evaluate->parse($formSpec[FE_FILL_STORE_VAR], ROW_EXPECT_0_1);
unset($formSpec[FE_FILL_STORE_VAR]);
if (is_array($rows)) {
$this->store->appendToStore($rows, STORE_VAR);
// LOG
......@@ -1053,6 +1109,7 @@ class QuickFormQuery {
case FORM_SAVE:
case FORM_UPDATE:
case FORM_REST:
$feSpecNative = $this->getNativeFormElements(SQL_FORM_ELEMENT_NATIVE_TG_COUNT, [$this->formSpec[F_ID]], $this->formSpec);
break;
......@@ -1371,22 +1428,23 @@ class QuickFormQuery {
}
/**
* Check if loading of the given form is permitted. If not, throw an exception.
* Check if the form loading is permitted. If not, throw an exception.
*
* @param string $formNameFoundInStore
* @param string $formMode
*
* @return bool 'true' if SIP exists, else 'false'
* @throws \qfq\CodeException
* @throws \qfq\UserFormException
* @internal param $foundInStore
* @throws CodeException
* @throws UserFormException
*/
private function validateForm($formNameFoundInStore, $formMode) {
private function validateForm($formNameFoundInStore, $formMode, &$formModeNew) {
$formModeNew = $formMode;
// Retrieve record_id either from SIP (prefered) or via URL
// Retrieve record_id either from SIP (preferred) or via URL
$r = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_TYPO3 . STORE_CLIENT, '', $recordIdFoundInStore);
// If not found: Fake a definition in STORE_TYPO3.
// No record id: Fake a definition in STORE_TYPO3.
if ($r === false) {
$r = 0;
$this->store->setVar(TYPO3_RECORD_ID, $r, STORE_TYPO3);
......@@ -1406,38 +1464,63 @@ class QuickFormQuery {
}
}
switch ($permitMode) {
case FORM_PERMISSION_SIP:
if (!$sipFound || $formNameFoundInStore !== STORE_SIP || $recordIdFoundInStore !== STORE_SIP) {
throw new UserFormException("SIP Parameter needed for this form.", ERROR_SIP_NEEDED_FOR_THIS_FORM);
}
break;
case FORM_PERMISSION_LOGGED_IN:
if (!$feUserLoggedIn) {
throw new UserFormException("User not logged in.", ERROR_USER_NOT_LOGGED_IN);
}
break;
case FORM_PERMISSION_LOGGED_OUT:
if ($feUserLoggedIn) {
throw new UserFormException("User logged in.", ERROR_USER_LOGGED_IN);
}
break;
case FORM_PERMISSION_ALWAYS:
break;
case FORM_PERMISSION_NEVER:
throw new UserFormException("Loading form forbidden.", ERROR_FORM_FORBIDDEN);
break;
case FORM_PERMISSION_REST:
if ($formMode != FORM_REST) {
throw new UserFormException("Try to load a REST form", ERROR_FORM_REST);
}
break;
default:
throw new CodeException("Unknown permission mode: '" . $permitMode . "'", ERROR_FORM_UNKNOWN_PERMISSION_MODE);
}
if ($formMode == FORM_REST) {
$method = $this->store::getVar(CLIENT_REQUEST_METHOD, STORE_CLIENT);
if (false === Support::findInSet(strtolower($method), $this->formSpec[F_REST_METHOD])) {
throw new UserFormException(json_encode([ERROR_MESSAGE_TO_USER => 'Invalid HTTP method',
ERROR_MESSAGE_SUPPORT => "Endpoint '" . $this->formSpec[F_NAME] . "' is not allowed with HTTP method '$method'",
ERROR_MESSAGE_HTTP_STATUS => HTTP_401_UNAUTHORIZED
]), ERROR_FORM_REST);
}
$this->restCheckAuthToken($this->formSpec[F_REST_TOKEN] ?? '');
switch ($method) {
case REQUEST_METHOD_GET:
break;
case REQUEST_METHOD_POST:
case REQUEST_METHOD_PUT:
$formModeNew = FORM_SAVE;
break;
case REQUEST_METHOD_DELETE:
$formModeNew = FORM_DELETE;
break;
default:
throw new CodeException('This code should never be reached', ERROR_CODE_SHOULD_NOT_HAPPEN);
}
} else {
if ($formMode == FORM_REST && $permitMode != FORM_PERMISSION_REST) {
throw new UserFormException("Try to load a non-REST form in REST mode", ERROR_FORM_REST);
switch ($permitMode) {
case FORM_PERMISSION_SIP:
if (!$sipFound || $formNameFoundInStore !== STORE_SIP || $recordIdFoundInStore !== STORE_SIP) {
throw new UserFormException("SIP Parameter needed for this form.", ERROR_SIP_NEEDED_FOR_THIS_FORM);
}
break;
case FORM_PERMISSION_LOGGED_IN:
if (!$feUserLoggedIn) {
throw new UserFormException("User not logged in.", ERROR_USER_NOT_LOGGED_IN);
}
break;
case FORM_PERMISSION_LOGGED_OUT:
if ($feUserLoggedIn) {
throw new UserFormException("User logged in.", ERROR_USER_LOGGED_IN);
}
break;
case FORM_PERMISSION_ALWAYS:
break;
case FORM_PERMISSION_NEVER:
throw new UserFormException("Loading form forbidden.", ERROR_FORM_FORBIDDEN);
break;
default:
throw new CodeException("Unknown permission mode: '" . $permitMode . "'", ERROR_FORM_UNKNOWN_PERMISSION_MODE);
}
}
// Form Definition valid?
......@@ -1449,6 +1532,7 @@ class QuickFormQuery {
return $sipFound;
}
$sipArray = $this->store->getStore(STORE_SIP);
// Check: requiredParameter: '' or 'form' or 'form,grId' or 'form #formname for form,grId'
$requiredParameter = ($r > 0) ? $this->formSpec[F_REQUIRED_PARAMETER_EDIT] : $this->formSpec[F_REQUIRED_PARAMETER_NEW];
......@@ -1827,7 +1911,7 @@ EOF;
/**
* @param array $restId
* @param array $restForm
* @param string $method
* @return array|string
* @throws CodeException
* @throws DbException
......@@ -1854,6 +1938,9 @@ EOF;
$ii++;
}
$this->store::setVar(SIP_FORM, end($restForm), STORE_SIP);
$this->store::setVar(SIP_RECORD_ID, end($restId), STORE_SIP);
return $this->doForm(FORM_REST);