Commit bb160754 authored by Elias Villiger's avatar Elias Villiger

Merge branch 'master' into 5467-min-max-parameters

# Conflicts:
#	extension/qfq/qfq/QuickFormQuery.php
parents 1b25afad e5bb6217
Pipeline #867 passed with stage
in 1 minute and 40 seconds
......@@ -37,6 +37,29 @@ Bug Fixes
^^^^^^^^^
Version 18.9.0
--------------
Date: 07.09.2018
Features
^^^^^^^^
* #6357 / Save pdf on server
* #5381 / Stored procedures can be called from QFQ Reports
* F4996 / Log QFQ Update with timestamp.
* #6255 / Inline Report Editing - now with SIP and save.php api
Bug Fixes
^^^^^^^^^
* #6465 / Allow newlines in form action queries (e.g. sqlInsert)
* #4654 / Better FE color highlighting (UX)
* #5689 / Default BS Columns for FormElement match Form setting
* #6484 / Download Links mit css class
* #6576 / download buttons are now rendered disabled with render mode r:3
* Cookie Sitepath: wrong detected in case of API calls.
Version 18.8.2
--------------
......
......@@ -40,15 +40,16 @@ Neue Versionsnummer
make t3sphinx (dadurch fallen Fehler in der RESTdoc Syntax auf)
5) **Update Version & Commit**
* Update the version number in this document (topic 6)
* Commit & Push new version changes to master branch:
New version 18.8.2
New version 18.9.0
6) **New Tag**:
git tag v18.8.2
git push -u origin v18.8.2
git tag v18.9.0
git push -u origin v18.9.0
7) PhpStorm: **Sync** all files to VM qfq.
......
This diff is collapsed.
......@@ -37,6 +37,29 @@ Bug Fixes
^^^^^^^^^
Version 18.9.0
--------------
Date: 07.09.2018
Features
^^^^^^^^
* #6357 / Save pdf on server
* #5381 / Stored procedures can be called from QFQ Reports
* #4996 / Log QFQ Update with timestamp.
* #6255 / Inline Report Editing - now with SIP and save.php api
Bug Fixes
^^^^^^^^^
* #6465 / Allow newlines in form action queries (e.g. sqlInsert)
* #4654 / Better FE color highlighting (UX)
* #5689 / Default BS Columns for FormElement match Form setting
* #6484 / Download Links mit css class
* #6576 / download buttons are now rendered disabled with render mode r:3
* Cookie Sitepath: wrong detected in case of API calls.
Version 18.8.2
--------------
......
......@@ -2,8 +2,8 @@
[general]
project = QFQ - Quick Form Query
version = 18.8
release = 18.8.2
version = 18.9
release = 18.9.0
t3author = Carsten Rose
copyright = since 2017 by the author
......
......@@ -57,9 +57,9 @@ copyright = u'2017, Carsten Rose'
# built documents.lease
#
# The short X.Y version.
version = '18.8'
version = '18.9'
# The full version, including alpha/beta/rc tags.
release = '18.8.2'
release = '18.9.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
......
......@@ -37,6 +37,29 @@ Bug Fixes
^^^^^^^^^
Version 18.9.0
--------------
Date: 07.09.2018
Features
^^^^^^^^
* #6357 / Save pdf on server
* #5381 / Stored procedures can be called from QFQ Reports
* F4996 / Log QFQ Update with timestamp.
* #6255 / Inline Report Editing - now with SIP and save.php api
Bug Fixes
^^^^^^^^^
* #6465 / Allow newlines in form action queries (e.g. sqlInsert)
* #4654 / Better FE color highlighting (UX)
* #5689 / Default BS Columns for FormElement match Form setting
* #6484 / Download Links mit css class
* #6576 / download buttons are now rendered disabled with render mode r:3
* Cookie Sitepath: wrong detected in case of API calls.
Version 18.8.2
--------------
......
......@@ -11,7 +11,7 @@ $EM_CONF[$_EXTKEY] = array(
'dependencies' => 'fluid,extbase',
'clearcacheonload' => true,
'state' => 'stable',
'version' => '18.8.2',
'version' => '18.9.0',
'constraints' => [
'depends' => [
'typo3' => '6.0.0-9.2.99',
......
......@@ -59,6 +59,11 @@ try {
$data = $qfq->saveForm();
if (isset($data[REPORT_SAVE])) {
// Redirect to previous page
header("Location: {$_SERVER['HTTP_REFERER']}");
}
$arr = $qfq->getForwardMode();
$answer[API_REDIRECT] = $arr[API_REDIRECT];
$answer[API_REDIRECT_URL] = $arr[API_REDIRECT_URL];
......
......@@ -35,13 +35,37 @@ require_once(__DIR__ . '/report/Report.php');
* @package qfq
*/
abstract class AbstractBuildForm {
/**
* @var array
*/
protected $formSpec = array(); // copy of the loaded form
/**
* @var array
*/
protected $feSpecAction = array(); // copy of all formElement.class='action' of the loaded form
/**
* @var array
*/
protected $feSpecNative = array(); // copy of all formElement.class='native' of the loaded form
/**
* @var array
*/
protected $buildElementFunctionName = array();
/**
* @var array
*/
protected $pattern = array();
/**
* @var array
*/
protected $wrap = array();
/**
* @var array
*/
protected $symbol = array();
/**
* @var bool
*/
protected $showDebugInfoFlag = false;
// protected $feDivClass = array(); // Wrap FormElements in <div class="$feDivClass[type]">
......@@ -80,7 +104,13 @@ abstract class AbstractBuildForm {
*/
protected $dbArray = array();
/**
* @var bool|mixed
*/
protected $dbIndexData = false;
/**
* @var bool|string
*/
protected $dbIndexQfq = false;
/**
......@@ -183,8 +213,12 @@ abstract class AbstractBuildForm {
* formElement.dynamicUpdate-yes values/states
* @throws CodeException
* @throws DbException
* @throws DownloadException
* @throws UserFormException
* @throws UserReportException
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
*/
public function process($mode, $htmlElementNameIdZero = false, $latestFeSpecNative = array()) {
$htmlHead = '';
......@@ -493,9 +527,12 @@ abstract class AbstractBuildForm {
* @return array|string
* @throws CodeException
* @throws DbException
* @throws DownloadException
* @throws UserFormException
* @throws UserReportException
* @throws DownloadException
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
*/
private function processReportSyntax($value) {
......@@ -544,8 +581,12 @@ abstract class AbstractBuildForm {
* @return string
* @throws CodeException
* @throws DbException
* @throws DownloadException
* @throws UserFormException
* @throws UserReportException
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
*/
public function elements($recordId, $filter = FORM_ELEMENTS_NATIVE, $feIdContainer = 0, array &$json,
$modeCollectFe = FLAG_DYNAMIC_UPDATE, $htmlElementNameIdZero = false,
......@@ -1049,6 +1090,7 @@ abstract class AbstractBuildForm {
* @return string
* @throws CodeException
* @throws UserFormException
* @throws UserReportException
*/
public function deriveNewRecordUrlFromExistingSip(&$toolTipNew) {
......@@ -1114,6 +1156,7 @@ abstract class AbstractBuildForm {
* @return string complete rendered HTML input element.
* @throws CodeException
* @throws UserFormException
* @throws UserReportException
*/
public function buildInput(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) {
$textarea = '';
......@@ -1446,6 +1489,7 @@ abstract class AbstractBuildForm {
* @return string
* @throws CodeException
* @throws UserFormException
* @throws UserReportException
*/
public function buildCheckbox(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) {
$itemKey = array();
......@@ -2048,6 +2092,7 @@ abstract class AbstractBuildForm {
* @return string
* @throws CodeException
* @throws UserFormException
* @throws UserReportException
*/
public function buildRadio(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) {
......@@ -2269,6 +2314,7 @@ abstract class AbstractBuildForm {
* @return mixed
* @throws CodeException
* @throws UserFormException
* @throws UserReportException
*/
public function buildSelect(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) {
$itemKey = array();
......@@ -2337,7 +2383,8 @@ abstract class AbstractBuildForm {
/**
* @param string $linkNew Complete Button, incl. SIP href
* @param string $deleteColumnTitle - if null, no delete column is rendered
* @param $flagDelete
* @param $deleteTitle
* @param array $firstRow First row of all subrecords to extract columntitles
* @param array $control Array with <th> column names / format.
*
......@@ -2376,6 +2423,7 @@ abstract class AbstractBuildForm {
*
* @return string
* @throws CodeException
* @throws DbException
* @throws UserFormException
* @throws UserReportException
*/
......@@ -3132,6 +3180,7 @@ abstract class AbstractBuildForm {
* @return string
* @throws CodeException
* @throws UserFormException
* @throws UserReportException
*/
public function buildDateTime(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) {
$attribute = '';
......@@ -3329,6 +3378,7 @@ abstract class AbstractBuildForm {
* @return string
* @throws CodeException
* @throws UserFormException
* @throws UserReportException
*/
public function buildEditor(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) {
$attribute = '';
......@@ -3517,6 +3567,7 @@ abstract class AbstractBuildForm {
* @return mixed
* @throws CodeException
* @throws DbException
* @throws DownloadException
* @throws UserFormException
* @throws UserReportException
*/
......@@ -3587,6 +3638,7 @@ abstract class AbstractBuildForm {
* @return mixed
* @throws CodeException
* @throws DbException
* @throws DownloadException
* @throws UserFormException
* @throws UserReportException
*/
......@@ -3703,6 +3755,7 @@ EOT;
* @return string
* @throws CodeException
* @throws DbException
* @throws DownloadException
* @throws UserFormException
* @throws UserReportException
*/
......
......@@ -111,26 +111,30 @@ class BodytextParser {
return;
}
$pos = 0;
$tokenList = '{}<>[]()';
// Definition: first line of bodytext, has to be a comment line. If the last char is one of the valid token: set that one.
// Nothing found: set {}.
$nestingOpen = '{';
$nestingClose = '}';
if ($firstLine[0] === '#') {
$token = substr($firstLine, -1);
$pos = strpos($tokenList, $token);
if ($pos === false) {
$pos = 0;
} else {
if ($pos % 2 === 1) {
$pos -= 1;
}
switch($token) {
case '<':
$nestingOpen = '<';
$nestingClose = '>';
break;
case '[':
$nestingOpen = '[';
$nestingClose = ']';
break;
case '(':
$nestingOpen = '(';
$nestingClose = ')';
break;
default:
break;
}
}
$nestingOpen = substr($tokenList, $pos, 1);
$nestingClose = substr($tokenList, $pos + 1, 1);
}
/**
......
......@@ -574,6 +574,7 @@ class BuildFormBootstrap extends AbstractBuildForm {
* @return string
* @throws CodeException
* @throws UserFormException
* @throws UserReportException
*/
public function tail() {
......@@ -654,8 +655,12 @@ EOF;
* @return mixed
* @throws CodeException
* @throws DbException
* @throws DownloadException
* @throws UserFormException
* @throws UserReportException
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
*/
public function buildPill(array $formElement, $htmlFormElementName, $value, array &$json) {
$html = '';
......
......@@ -65,7 +65,9 @@ class BuildFormPlain extends AbstractBuildForm {
* @return string
* @throws CodeException
* @throws DbException
* @throws DownloadException
* @throws UserFormException
* @throws UserReportException
*/
public function doSubrecords() {
$json = array();
......
......@@ -68,7 +68,9 @@ class BuildFormTable extends AbstractBuildForm {
* @return string
* @throws CodeException
* @throws DbException
* @throws DownloadException
* @throws UserFormException
* @throws UserReportException
*/
public function doSubrecords() {
//TODO: $json is not returned - which is wrong. In this case, dynamic update won't work for subrecords
......@@ -83,6 +85,7 @@ class BuildFormTable extends AbstractBuildForm {
* @throws CodeException
* @throws DbException
* @throws UserFormException
* @throws UserReportException
*/
public function head($mode = FORM_LOAD) {
$html = '';
......
......@@ -45,6 +45,8 @@ const FORM_BUTTON_DELETE = 'delete';
const FORM_BUTTON_CLOSE = 'close';
const FORM_BUTTON_SAVE = 'save';
const REPORT_SAVE = 'reportSave';
const F_BS_COLUMNS = 'bsColumns';
const F_BS_LABEL_COLUMNS = 'bsLabelColumns';
......@@ -234,6 +236,7 @@ const ERROR_MULTIPLE_URL_PAGE_MAILTO_DEFINITION = 1406;
const ERROR_UNKNOWN_TOKEN = 1407;
const ERROR_TOO_FEW_PARAMETER_FOR_SENDMAIL = 1408;
const ERROR_TOO_MANY_PARAMETER = 1409;
const ERROR_INVALID_SAVE_PDF_FILENAME = 1410;
// Upload
const ERROR_UPLOAD = 1500;
......@@ -408,6 +411,8 @@ const SYSTEM_DB_1_SERVER = 'DB_1_SERVER';
const SYSTEM_DB_1_PASSWORD = 'DB_1_PASSWORD';
const SYSTEM_DB_1_NAME = 'DB_1_NAME';
const SYSTEM_T3_DB_NAME = 'T3_DB_NAME';
const SYSTEM_DB_INIT = 'init';
const SYSTEM_DB_INDEX_DATA = "indexData";
......@@ -1204,7 +1209,10 @@ const INDEX_PHP = 'index.php';
// QuickFormQuery.php
const T3DATA_BODYTEXT = 'bodytext';
const T3DATA_BODYTEXT_RAW = 'bodytext-raw';
const T3DATA_UID = 'uid';
const T3DATA_HEADER = 'header';
const REPORT_INLINE_BODYTEXT = 'bodytext';
// Special Column to check for uploads
const COLUMN_PATH_FILE_NAME = 'pathFileName';
......@@ -1284,11 +1292,16 @@ const TOKEN_FORM = CLIENT_FORM;
const TOKEN_RECORD_ID = CLIENT_RECORD_ID;
const TOKEN_DEBUG_BODYTEXT = TYPO3_DEBUG_SHOW_BODY_TEXT;
const TOKEN_DB_INDEX = F_DB_INDEX;
const TOKEN_CONTENT = 'content';
const TOKEN_VALID_LIST = 'sql|head|althead|altsql|tail|shead|stail|rbeg|rend|renr|rsep|fbeg|fend|fsep|rbgd|debug|form|r|debugShowBodyText|dbIndex|sqlLog|sqlLogMode';
const TOKEN_VALID_LIST = 'sql|head|althead|altsql|tail|shead|stail|rbeg|rend|renr|rsep|fbeg|fend|fsep|rbgd|debug|form|r|debugShowBodyText|dbIndex|sqlLog|sqlLogMode|content';
const TOKEN_COLUMN_CTRL = '_';
const TOKEN_CONTENT_STORE = 'store';
const TOKEN_CONTENT_HIDE = 'hide';
const TOKEN_CONTENT_SHOW = 'show';
//Report: Column Token
const COLUMN_LINK = 'link';
const COLUMN_EXEC = 'exec';
......@@ -1318,6 +1331,7 @@ const COLUMN_PAGES = 'pages';
const COLUMN_YANK = 'yank';
const COLUMN_PDF = 'pdf';
const COLUMN_SAVE_PDF = 'savePdf';
const COLUMN_FILE = 'file';
const COLUMN_ZIP = 'zip';
const COLUMN_MONITOR = 'monitor';
......@@ -1351,6 +1365,7 @@ const FORM_LOG_ACTIVE = 'formLogActive';
const DOWNLOAD_MODE = 'mode';
const DOWNLOAD_MODE_FILE = 'file';
const DOWNLOAD_MODE_PDF = 'pdf';
const DOWNLOAD_MODE_SAVE_PDF = 'save-pdf';
const DOWNLOAD_MODE_EXCEL = 'excel';
const DOWNLOAD_MODE_ZIP = 'zip';
const DOWNLOAD_MODE_THUMBNAIL = 'thumbnail';
......
......@@ -34,6 +34,7 @@ class Delete {
* @throws CodeException
* @throws DbException
* @throws UserFormException
* @throws UserReportException
*/
public function __construct($dbIndexData = false, $phpUnit = false) {
......
......@@ -309,7 +309,7 @@ class Save {
$sql .= '`' . $column . '` = ?, ';
}
$sql = substr($sql, 0, strlen($sql) - 2) . ' WHERE id = ?';
$sql = substr($sql, 0, strlen($sql) - 2) . " WHERE id = ?";
$values[] = $recordId;
$rc = $this->db->sql($sql, ROW_REGULAR, array_values($values));
......@@ -541,7 +541,9 @@ class Save {
*
* @return false|string New pathFilename or false on error
* @throws CodeException
* @throws DbException
* @throws UserFormException
* @throws UserReportException
* @internal param $recordId
*/
private function doUpload($formElement, $sipUpload, Sip $sip, &$modeUpload) {
......@@ -632,21 +634,8 @@ class Save {
throw new UserFormException("Upload failed, no target '" . FE_FILE_DESTINATION . "' specified.", ERROR_NO_TARGET_PATH_FILE_NAME);
}
if (file_exists($pathFileName)) {
if (isset($formElement[FE_FILE_REPLACE_MODE]) && $formElement[FE_FILE_REPLACE_MODE] == FE_FILE_REPLACE_MODE_ALWAYS) {
if (!unlink($pathFileName)) {
throw new UserFormException('Copy upload failed - file exist and unlink() failed: ' . $pathFileName, ERROR_IO_UNLINK);
}
} else {
throw new UserFormException('Copy upload failed - file already exist: ' . $pathFileName, ERROR_IO_FILE_EXIST);
}
}
Support::mkDirParent($pathFileName);
if (!rename($srcFile, $pathFileName)) {
throw new UserFormException("Rename file: '$srcFile' > '$pathFileName'", ERROR_IO_RENAME);
}
$overwrite = isset($formElement[FE_FILE_REPLACE_MODE]) && $formElement[FE_FILE_REPLACE_MODE] == FE_FILE_REPLACE_MODE_ALWAYS;
Support::copyFile($srcFile, $pathFileName, $overwrite);
$this->splitUpload($formElement, $pathFileName);
......
......@@ -416,6 +416,7 @@ class Database {
case 'ALTER':
case 'DROP':
case 'CREATE':
case 'CALL':
$queryType = QUERY_TYPE_CONTROL;
$stat[DB_AFFECTED_ROWS] = 0;
$count = $stat[DB_AFFECTED_ROWS];
......@@ -782,6 +783,7 @@ class Database {
* @param $mode
* @throws CodeException
* @throws DbException
* @throws UserFormException
*/
public function createTable($table, $columnDefinition, $mode) {
......
......@@ -56,6 +56,9 @@ class Dirty {
* @param bool $dbIndexData
* @param bool $dbIndexQfq
* @throws CodeException
* @throws DbException
* @throws UserFormException
* @throws UserReportException
*/
public function __construct($phpUnit = false, $dbIndexData = false, $dbIndexQfq = false) {
......@@ -71,6 +74,10 @@ class Dirty {
/**
* @param $dbIndexData
* @param $dbIndexQfq
* @throws CodeException
* @throws DbException
* @throws UserFormException
* @throws UserReportException
*/
private function doDbArray($dbIndexData, $dbIndexQfq) {
......@@ -95,6 +102,7 @@ class Dirty {
* @throws CodeException
* @throws DbException
* @throws UserFormException
* @throws UserReportException
*/
public function process() {
......@@ -144,7 +152,6 @@ class Dirty {
*
* @return array
* @throws CodeException
* @throws DbException
*/
private function acquireDirty($recordId, array $tableVars, $recordHashMd5) {
......@@ -152,26 +159,25 @@ class Dirty {
$formDirtyMode = $tableVars[F_DIRTY_MODE];
$rcMd5 = '';
// Check for changed record. Compute $rcMd5
$flagModified = $this->isRecordModified($tableName, $recordId, $recordHashMd5, $rcMd5);
if (($recordHashMd5 != '') && $flagModified) {
return [API_STATUS => API_ANSWER_STATUS_CONFLICT, API_MESSAGE => 'The record has been modified in the meantime. Please reload the form, edit and save again.'];
}
if ($formDirtyMode == DIRTY_MODE_NONE) {
$answer = [API_STATUS => 'success', API_MESSAGE => ''];
} else {
// Check for changed record. Compute $rcMd5
$flagModified = $this->isRecordModified($tableName, $recordId, $recordHashMd5, $rcMd5);
if (($recordHashMd5 != '') && $flagModified) {
return [API_STATUS => API_ANSWER_STATUS_CONFLICT, API_MESSAGE => 'The record has been modified in the meantime. Please reload the form, edit and save again.'];
}
$feUser = $this->session->get(SESSION_FE_USER);
// Look for already existing dirty record.
$recordDirty = $this->getRecordDirty($tableName, $recordId);
// Look for already existing dirty record.
$recordDirty = $this->getRecordDirty($tableName, $recordId);
if (count($recordDirty) == 0) {
if ($formDirtyMode == DIRTY_MODE_NONE) {
$answer = [API_STATUS => 'success', API_MESSAGE => ''];
} else {
if (count($recordDirty) == 0) {
// No dirty record found.
$feUser = $this->session->get(SESSION_FE_USER);
$answer = $this->writeDirty($this->client[SIP_SIP], $recordId, $tableVars, $feUser, $rcMd5);
} else {
$answer = $this->conflict($recordDirty, $formDirtyMode);
}
} else {
$answer = $this->conflict($recordDirty, $formDirtyMode);
}
return $answer;
......@@ -277,7 +283,6 @@ class Dirty {