Commit a7eb45b1 authored by Marc Egger's avatar Marc Egger
Browse files

Merge remote-tracking branch 'origin/develop' into F11035QfqWithoutTypo3

# Conflicts:
#	Documentation/Report.rst
parents e87a2421 335b73be
Pipeline #4056 passed with stages
in 4 minutes and 19 seconds
......@@ -12,6 +12,7 @@
.webprj
.vscode
nbprojec
nohup.out
# Created by .ignore support plugin (hsz.mobi)
.python_virtualenv/
......@@ -29,13 +30,11 @@ nbprojec
.support_plantuml
.support_sonar
qfq.flowchart.dia.autosave
test.json
test.php
test.html
*.autosave
composer.lock
/test.*
/Documentation-GENERATED-temp
/bower_components
/composer.phar
......@@ -57,6 +56,7 @@ composer.lock
/extension/Resources/Public/webfonts
/extension/Resources/Public/JavaScript
/extension/Tests/selenium/tmp
/extension/Resources/Private/OnlineRecruitingTool
/fonts
/webfonts
/js
......@@ -72,4 +72,7 @@ composer.lock
/docker/chromedriver
/docker/geckodriver
/docker/run_qfq_docker.output
__pycache__
package-lock.json
......@@ -70,6 +70,8 @@ All data will imported / exported in JSON notation.
Any QFQ form becomes a REST form via: ``Form > Access > Permit REST: get / insert / update / delete``
If the REST endpoint specifies an unknown form or access is forbidden, an HTTP error is reported.
Endpoint
--------
......@@ -86,8 +88,8 @@ E.g.:
1. List of all persons: ``<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person``
2. Data of person 123: ``<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person/123``
3. Adresses of person 123: ``<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person/123/address``
4. Adress details of address 45 from person 123: ``<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person/123/address/45``
3. Addresses of person 123: ``<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person/123/address``
4. Address details of address 45 from person 123: ``<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person/123/address/45``
QFQ 'Forms' are used as a 'container' (to define all details).
......@@ -115,11 +117,11 @@ GET - Read
A REST (GET) form has two modes:
data
Specific content to a given id. Defined via 'form.parameter.restSqlData'. This mode is selected if there is an
Specific content to a given id. Defined via ``form.parameter.restSqlData``. This mode is selected if there is an
id>0 given.
list
A list of records will be exported. Defined via 'form.parameter.restSqlList'. This mode is selected if there is no
A list of records will be exported. Defined via ``form.parameter.restSqlList``. This mode is selected if there is no
id or id=0.
.. note::
......@@ -199,12 +201,14 @@ POST - Insert
+-------------------+----------------------------------------------------------------------------------+
| restToken | Optional. User defined string or dynamic token (see :ref:`restAuthorization`). |
+-------------------+----------------------------------------------------------------------------------+
| restSqlPostPut | Optional. Instead of returning the last_insert_id, a customized result might be |
| | specified. E.g. ``{{! SELECT id, token FROM Token WHERE id={{id:R0}} }}`` |
+-------------------+----------------------------------------------------------------------------------+
FormElement:
* For each column to save one FormElement with ``FE.name=<column>`` is necessary.
* A regular QFQ form can be used as REST Post endpoint
* A regular QFQ form can be used as REST Post endpoint.
PUT - Update
------------
......
......@@ -127,7 +127,7 @@ For QFQ variables and FormElements:
+------------------+------+-------+-----------------------------------------------------------------------------------------+
| Name | Form | Query | Pattern |
+==================+======+=======+=========================================================================================+
| **alnumx** | Form | Query | [A-Za-z][0-9]@-_.,;: /() ÀÈÌÒÙàèìòùÁÉÍÓÚÝáéíóúýÂÊÎÔÛâêîôûÃÑÕãñõÄËÏÖÜŸäëïöüÿç |
| **alnumx** | Form | Query | [A-Za-z][0-9]@-_.,;: /() ÀÈÌÒÙàèìòùÁÉÍÓÚÝáéíóúýÂÊÎÔÛâêîôûÃÑÕãñõÄËÏÖÜŸäëïöüÿçß |
+------------------+------+-------+-----------------------------------------------------------------------------------------+
| **digit** | Form | Query | [0-9] |
+------------------+------+-------+-----------------------------------------------------------------------------------------+
......@@ -375,4 +375,4 @@ For better reading, the format string might be wrapped in single or double quote
These variables are especially helpful in:
* `report`, to create create links or buttons outside of an SQL statement. E.g. in `head`, `rbeg`, ...
* `form`, to create links and buttons in labels or notes.
\ No newline at end of file
* `form`, to create links and buttons in labels or notes.
......@@ -101,7 +101,7 @@ const SANITIZE_TYPE_MESSAGE_VIOLATE_EMPTY = 'e';
const SANITIZE_TYPE_MESSAGE_VIOLATE_ZERO = '0';
const SANITIZE_TYPE_MESSAGE_VIOLATE_CLASS = 'c';
const PATTERN_ALNUMX = '^[@\-_\.,;: \/\(\)a-zA-Z0-9ÀÈÌÒÙàèìòùÁÉÍÓÚÝáéíóúýÂÊÎÔÛâêîôûÃÑÕãñõÄËÏÖÜŸäëïöüÿç]*$';
const PATTERN_ALNUMX = '^[@\-_\.,;: \/\(\)a-zA-Z0-9ÀÈÌÒÙàèìòùÁÉÍÓÚÝáéíóúýÂÊÎÔÛâêîôûÃÑÕãñõÄËÏÖÜŸäëïöüÿçß]*$';
const PATTERN_DIGIT = '^[\d]*$';
const PATTERN_NUMERICAL = '^[\d.+-]*$';
const PATTERN_EMAIL = '^([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})?$';
......@@ -1067,6 +1067,7 @@ const F_MULTI_MSG_NO_RECORD_TEXT = 'No data';
const F_REST_SQL_LIST = 'restSqlList';
const F_REST_SQL_DATA = 'restSqlData';
const F_REST_SQL_POST_PUT = 'restSqlPostPut';
const F_REST_PARAM = 'restParam';
const F_REST_TOKEN = 'restToken';
const CLIENT_REST_ID = '_id';
......
......@@ -87,7 +87,7 @@ class Database {
// DB Init
if ($dbInit !== false && $dbInit != '') {
$arr = explode(';', $dbInit);
foreach ($arr AS $sql) {
foreach ($arr as $sql) {
$sql = trim($sql);
if ('' != $sql) {
$this->sql($sql);
......@@ -254,6 +254,9 @@ class Database {
default:
throw new \DbException($specificMessage . "Unknown mode: $mode", ERROR_UNKNOWN_MODE);
}
$this->freeResult();
} elseif ($queryType === QUERY_TYPE_INSERT) {
$result = $stat[DB_INSERT_ID];
} else {
......@@ -417,6 +420,7 @@ class Database {
case 'SHOW':
case 'DESCRIBE':
case 'EXPLAIN':
case 'CALL':
if (false === ($result = $this->mysqli_stmt->get_result())) {
throw new \DbException(
json_encode([ERROR_MESSAGE_TO_USER => 'Error DB execute', ERROR_MESSAGE_TO_DEVELOPER => '[ mysqli: ' . $this->mysqli_stmt->errno . ' ] ' . $this->mysqli_stmt->error . $specificMessage]),
......@@ -450,7 +454,6 @@ class Database {
case 'ALTER':
case 'DROP':
case 'CREATE':
case 'CALL':
$queryType = QUERY_TYPE_CONTROL;
$stat[DB_AFFECTED_ROWS] = 0;
$count = $stat[DB_AFFECTED_ROWS];
......@@ -622,7 +625,7 @@ class Database {
}
/**
* Fetch all rows of the result as associative array.
* Fetch all rows of the result.
*
* mode:
* ROW_IMPLODE_ALL: Return string. All cells of all rows imploded to one string.
......@@ -639,6 +642,9 @@ class Database {
*
*/
private function fetchAll($mode = '', &$keys = array()) {
$result = null;
if ($this->mysqli_result == null || $this->mysqli_result == false) {
return false;
}
......@@ -649,12 +655,10 @@ class Database {
switch ($mode) {
case ROW_IMPLODE_ALL:
$str = "";
$result = "";
foreach ($this->mysqli_result->fetch_all(MYSQLI_NUM) as $row) {
$str .= implode($row);
$result .= implode($row);
}
return $str;
break;
case ROW_KEYS:
......@@ -664,12 +668,14 @@ class Database {
$keys[$ii] = $this->mysqli_result->fetch_field_direct($ii)->name;
}
return $this->mysqli_result->fetch_all(MYSQLI_NUM);
$result = $this->mysqli_result->fetch_all(MYSQLI_NUM);
break;
default:
return $this->mysqli_result->fetch_all(MYSQLI_ASSOC);
$result = $this->mysqli_result->fetch_all(MYSQLI_ASSOC);
}
return $result;
}
/**
......@@ -741,7 +747,7 @@ class Database {
private function getFieldDefinitionFromTable($table, $columnName) {
$tableDefinition = $this->getTableDefinition($table);
foreach ($tableDefinition AS $row) {
foreach ($tableDefinition as $row) {
if ($row["Field"] == $columnName) {
return $row;
}
......@@ -842,7 +848,7 @@ class Database {
if (!$this->existTable($table)) {
$sql = "CREATE TABLE $table (";
foreach ($columnDefinition AS $key => $value) {
foreach ($columnDefinition as $key => $value) {
$cols[] = "`" . $key . "` " . $value . " NOT NULL,";
}
$sql .= implode(',', $cols);
......@@ -1002,12 +1008,18 @@ class Database {
}
// discard all results: this is important - if missed, following calls on $mysqli will fail.
$this->freeResult();
}
/**
* Free all results. If there are multiple, free them all. Required at least for multi_query() (mysqli) and call() (mysql)
*/
private function freeResult() {
do {
if ($res = $this->mysqli->store_result()) {
$res->free();
}
} while ($this->mysqli->more_results() && $this->mysqli->next_result());
}
/**
......@@ -1041,7 +1053,7 @@ class Database {
$sql = 'SELECT `pathFileName` FROM `' . TABLE_NAME_SPLIT . '` WHERE `tableName`=? AND `xId`=?';
$data = $this->sql($sql, ROW_REGULAR, [$tableName, $xId]);
foreach ($data AS $row) {
foreach ($data as $row) {
if (!empty($row['pathFileName']) && is_writable($row['pathFileName'])) {
HelperFile::unlink($row['pathFileName']);
}
......
......@@ -209,7 +209,7 @@ class OnString {
/**
* Split a $_SERVER['PATH_INFO'] of the form '/form1/id1/form2/id2/form3/id3/.../formN[/idN])' to
* $rcArrrIds=[ id1, id2, ..., idN]
* $rcArrIds=[ id1, id2, ..., idN]
* return: 'formN'
*
* @param $pathInfo
......
......@@ -37,6 +37,7 @@ class SessionCookie {
public function __construct(array $config) {
$lines = '';
// In debug mode, keep temporary files
$this->cleanTempFiles = !Support::findInSet(SYSTEM_SHOW_DEBUG_INFO_DOWNLOAD, $config[SYSTEM_SHOW_DEBUG_INFO]);
$urlParts = parse_url($config[SYSTEM_BASE_URL]);
......@@ -78,5 +79,4 @@ class SessionCookie {
return $this->pathFileNameCookie;
}
}
\ No newline at end of file
......@@ -609,7 +609,7 @@ class QuickFormQuery {
$this->pasteClipboard($this->formSpec[F_ID], $formAction);
if ($formMode == FORM_REST) {
$data = ['id' => $rc];
$data = $this->doRestPostPut($rc);
$flagApiStructureReGroup = false;
break;
}
......@@ -708,7 +708,24 @@ class QuickFormQuery {
}
return $this->evaluate->parse($this->formSpec[$key]);
}
/**
* @return bool|array
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
* @throws \UserReportException
*/
private function doRestPostPut($id) {
if (!isset($this->formSpec[F_REST_SQL_POST_PUT])) {
return ['id' => $id];
}
$this->nameGenericRestParam();
return $this->evaluate->parse($this->formSpec[F_REST_SQL_POST_PUT]);
}
/**
......@@ -787,7 +804,7 @@ class QuickFormQuery {
$feFilter = OnArray::filter($this->feSpecNative, FE_TYPE, FE_TYPE_PILL);
if (!empty($feFilter)) {
foreach ($feFilter AS $feParent) {
foreach ($feFilter as $feParent) {
if ($feParent[FE_MODE_SQL]) {
$mode = $this->evaluate->parse($feParent[FE_MODE_SQL]);
......@@ -798,7 +815,7 @@ class QuickFormQuery {
if ($feParent[FE_MODE] == FE_MODE_HIDDEN) {
$feChild = OnArray::filter($this->feSpecNative, FE_ID_CONTAINER, $feParent[FE_ID]);
foreach ($feChild AS $fe) {
foreach ($feChild as $fe) {
# Search for origin
foreach ($this->feSpecNative as $key => $value) {
......@@ -930,7 +947,7 @@ class QuickFormQuery {
$arrClipboard = $this->dbArray[$this->dbIndexQfq]->sql($sql);
// Process clipboard records.
foreach ($arrClipboard AS $srcIdRecord) {
foreach ($arrClipboard as $srcIdRecord) {
$formAction->doAllFormElementPaste($this->feSpecAction, $this->formSpec[F_TABLE_NAME], $this->formSpec[F_TABLE_NAME], "", $srcIdRecord);
}
......@@ -1082,7 +1099,7 @@ class QuickFormQuery {
unset($form[F_PARAMETER]);
// Save specific elements to be expanded later.
$parseLater = OnArray::getArrayItems($form, [F_FORWARD_PAGE, FE_FILL_STORE_VAR, F_REST_SQL_LIST, F_REST_SQL_DATA, F_MULTI_SQL]);
$parseLater = OnArray::getArrayItems($form, [F_FORWARD_PAGE, FE_FILL_STORE_VAR, F_REST_SQL_LIST, F_REST_SQL_DATA, F_REST_SQL_POST_PUT, F_MULTI_SQL]);
$form[FE_FILL_STORE_VAR] = '';
$form[F_FORWARD_PAGE] = '';
$form[F_REST_SQL_LIST] = '';
......@@ -1593,7 +1610,7 @@ class QuickFormQuery {
$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',
throw new \UserFormException(json_encode([ERROR_MESSAGE_TO_USER => 'Invalid HTTP method: endpoint (form) not found or access not allowed',
ERROR_MESSAGE_TO_DEVELOPER => "Endpoint '" . $this->formSpec[F_NAME] . "' is not allowed with HTTP method '$method'",
ERROR_MESSAGE_HTTP_STATUS => HTTP_401_UNAUTHORIZED
]), ERROR_FORM_REST);
......@@ -1662,7 +1679,7 @@ class QuickFormQuery {
$requiredParameterArr = explode('#', $requiredParameter, 2);
$param = explode(',', $requiredParameterArr[0]);
foreach ($param AS $name) {
foreach ($param as $name) {
$name = trim($name);
......
......@@ -637,19 +637,20 @@ class Download {
ERROR_MESSAGE_TO_DEVELOPER => "File: $filename"]), ERROR_IO_FILE_EXIST);
}
$filesCleanLater[] = $filename;
switch ($outputMode) {
case OUTPUT_MODE_FILE:
// At least for sendmail with dynamic PDF created from URL: unlink temporary files later, not now.
break;
case OUTPUT_MODE_COPY_TO_FILE:
HelperFile::copy($filename, $vars[DOWNLOAD_EXPORT_FILENAME]);
$filesCleanLater[] = $filename;
break;
case OUTPUT_MODE_DIRECT:
$this->outputFile($filename, $vars[DOWNLOAD_EXPORT_FILENAME]);
$filesCleanLater[] = $filename;
$filename = '';
break;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment