Commit c117542a authored by Carsten  Rose's avatar Carsten Rose

Merge branch 'develop' into 'master'

Develop

See merge request !262
parents ced8891f 67393e96
Pipeline #3400 passed with stages
in 3 minutes and 26 seconds
......@@ -59,9 +59,42 @@ Use with LDAP: `typeAheadLdap`
### .data-typeahead-limit
* Defines the limit of entries shown on the client. Default on client is 5. The server will always send a value. The server default is 20.
* Defines the limit of entries shown on the client. Default on client is 5. The server will always send a value.
The server default is 20.
### .data-typeahead-minlength
* Defines the string minlegth, typed by the user, before the first lookup is started. Default is 2.
* Defines the string minlength, typed by the user, before the first lookup is started. Default is 2.
### data-typeahead-pedantic
* If present, only suggested values are allowed in the input element
## Tags Form Element
The tags form element depends on Typeahead by default. The following attributes define the tags form element, additional
to the attributes for Typeahead (see above).
Mockups can be found in `mockup/typahead.php`
### .data-typeahead-tags
* If present, the field becomes a tag field
### .data-typeahead-tag-delimiters
* List of ascii key codes of the keys which may be pressed to add a new tag when typing.
### .value
* JSON encoded list of key value pairs of existing tags. e.g.
`[{value: "Alaska", key: "AK"}, {value: "Alabama", key: "AL"}]`
### POST data
* JSON encoded list of key value pairs of the selected tags. e.g.
`[{value: "Alaska", key: "AK"}, {value: "Alabama", key: "AL"}]`
......@@ -9,15 +9,13 @@ Neuer Build
Neue Versionsnummer
===================
0) Fuer jede neue Version ein Ticket erstellen. Template: #6994
1) Fuer jede neue Version ein Ticket erstellen. Template: #6994
1) Alle offenen Branches auf **Develop** mergen.
2) * Merge 'open Branches' to **Develop**.
* Merge 'Develop' to **Master**.
* Checkout **Master**.
1a) Develop auf **Master mergen**.
1b) Wechseln auf **Master**.
2) Die aktuellen Commits anschauen und wichtige Topics uebernehmen (git log > ~/qfq.log, alles bis zum letzten TAG anschauen):
3) Die aktuellen Commits anschauen und wichtige Topics uebernehmen (git log > ~/qfq.log, alles bis zum letzten TAG anschauen):
* **All commits since last tag**:
......@@ -43,7 +41,7 @@ Neue Versionsnummer
* Alle offenen Tickets im aktuellen Milestones verschieben auf den naechsten Milestone.
* Abgeschlossene Tickets schliessen.
3) In folgenden Files anpassen:
4) In folgenden Files anpassen:
**Achtung**: die Release Minor darf KEINE fuehrenden Nullen enthalten!!! Ansonsten funktioniert die Verteilung vie TER nicht.
......@@ -54,36 +52,36 @@ Neue Versionsnummer
* Documentation/Settings.cfg: version
* extension/ext_emconf.php: version
4) **Documentation**
5) **Documentation**
make doc-local (dadurch fallen Fehler in der RESTdoc Syntax auf)
5) **Update Version & Commit**
6) **Update Version & Commit**
* Update the version number in this document (topic 6)
* Commit & Push new version changes to master branch:
New version 20.2.0
6) **New Tag**:
7) **New Tag**:
git tag v20.2.0
git push -u origin v20.2.0
7) Tickets:
8) **Merge 'master' into 'develop'**
9) Tickets:
* Schliessen und der QFQ Version zuweisen.
* Aktuellen Milestone schliessen
8) T3 Doc aktualisieren
10) T3 Doc aktualisieren
Sollte durch den git.math.uzh.ch WebHook passieren.
9) Extension hochladen
11) Extension hochladen
TER: https://extensions.typo3.org/ > Log in > My Extensions.
10) PhpStorm: **Sync** all files to VM qfq.
......
This diff is collapsed.
......@@ -8,7 +8,7 @@ RELEASE_DATE = $(shell date '+%Y%m%d%H%M')
GIT_REVISION_SHORT = $(shell git rev-parse --short HEAD || true)
GIT_REVISION_LONG = $(shell git rev-parse HEAD || true)
EXTENSION_CONTENT = Classes Configuration Resources ext_emconf.php ext_localconf.php ext_tables.php ext_icon.png ext_conf_template.txt config.qfq.example.php RELEASE.txt vendor
EXTENSION_CONTENT = Classes Configuration Resources ext_emconf.php ext_localconf.php ext_tables.php ext_icon.png ext_conf_template.txt config-example.qfq.php RELEASE.txt vendor
DISTDIR=dist
......@@ -59,6 +59,7 @@ plantuml:
bootstrap: .npmpackages .plantuml_install .virtual_env
npm update
grunt default
# take care that phpOffice is located under 'qfq/Resources/Private/vendor/phpoffice'
# cd extension/Resources/Private; composer update
cd extension; composer update
......
......@@ -165,7 +165,7 @@ const ERROR_USER_NOT_LOGGED_IN = 1017;
const ERROR_USER_LOGGED_IN = 1018;
const ERROR_FORM_FORBIDDEN = 1019;
const ERROR_FORM_UNKNOWN_PERMISSION_MODE = 1020;
const ERROR_FORMELEMENT_EDITOR_TYPE = 1021;
const ERROR_TOKEN_MISSING = 1022;
const ERROR_RECURSION_TOO_DEEP = 1023;
const ERROR_CHECKBOXMODE_UNKNOWN = 1024;
......@@ -644,10 +644,13 @@ const SYSTEM_ENTER_AS_SUBMIT = 'enterAsSubmit';
const SYSTEM_SHOW_ID_IN_FORM_TITLE = 'showIdInFormTitle';
const SYSTEM_CMD_WKHTMLTOPDF = 'cmdWkhtmltopdf';
// Thumbnail
const SYSTEM_CMD_INKSCAPE = 'cmdInkscape';
const SYSTEM_CMD_CONVERT = 'cmdConvert';
const SYSTEM_CMD_QPDF = 'cmdQpdf';
const SYSTEM_CMD_GS = 'cmdGs';
const SYSTEM_CMD_PDFUNITE = 'cmdPdfunite';
// Thumbnail
const SYSTEM_THUMBNAIL_DIR_SECURE = 'thumbnailDirSecure';
const SYSTEM_THUMBNAIL_DIR_SECURE_DEFAULT = 'fileadmin/protected/qfqThumbnail';
const SYSTEM_THUMBNAIL_DIR_PUBLIC = 'thumbnailDirPublic';
......@@ -728,7 +731,8 @@ const VAR_FILENAME_EXT = 'filenameExt'; // Extension of the original filename of
const VAR_FILE_MIME_TYPE = 'mimeType';
const VAR_FILE_SIZE = 'fileSize';
const VAR_ALL_REQUIRED_GIVEN = 'allRequiredGiven'; // If all required FE are given: 1, else: 0. If there is no required FE: 1
const VAR_TAG_ID = 'tagId';
const VAR_TAG_VALUE = 'tagValue';
// PHP class Typeahead
const TYPEAHEAD_API_QUERY = 'query'; // Name of parameter in API call of typeahead.php?query=...&s=... - See also FE_TYPE_AHEAD_SQL
......@@ -858,6 +862,9 @@ const DATA_ENABLE_SAVE_BUTTON = 'data-enable-save-button';
const DATA_TYPEAHEAD_LIMIT = 'data-typeahead-limit';
const DATA_TYPEAHEAD_MINLENGTH = 'data-typeahead-minlength';
const DATA_TYPEAHEAD_PEDANTIC = 'data-typeahead-pedantic';
const DATA_TYPEAHEAD_INITIAL_SUGGESTION = 'data-typeahead-initial-suggestion';
const DATA_TYPEAHEAD_TAG = 'data-typeahead-tags';
const DATA_TYPEAHEAD_TAG_DELIMITER = 'data-typeahead-tag-delimiters';
const CLASS_CHARACTER_COUNT = 'qfq-character-count';
const DATA_CHARACTER_COUNT_ID = 'data-character-count-id';
......@@ -867,8 +874,6 @@ const CLASS_FORM_ELEMENT_EDIT = 'qfq-form-element-edit';
const CLASS_FORM_ELEMENT_AUTO_GROW = 'qfq-auto-grow';
const ATTRIBUTE_DATA_MAX_HEIGHT = 'data-max-height';
// BuildForm
const SYMBOL_NEW = 'new';
const SYMBOL_EDIT = 'edit';
......@@ -1189,6 +1194,9 @@ const FE_SQL_INSERT = 'sqlInsert'; // Action: Insert Statement to create slave r
const FE_SQL_DELETE = 'sqlDelete'; // Action: Delete Statement to delete unused slave record.
const FE_SQL_HONOR_FORM_ELEMENTS = 'sqlHonorFormElements'; // Action: Honor given list of FormElements for sqlInsert|Update|Delete
const FE_EDITOR_PREFIX = 'editor-'; // TinyMCE configuration settings.
const FE_EDITOR_TYPE = 'editorType'; // tinymce | codemirror
const FE_EDITOR_TYPE_TINYMCE = 'tinymce';
const FE_EDITOR_TYPE_CODEMIRROR = 'codemirror';
const FE_SENDMAIL_TO = 'sendMailTo'; // Receiver email adresses. Separate multiple by comma.
const FE_SENDMAIL_CC = 'sendMailCc'; // CC Receiver email adresses. Separate multiple by comma.
const FE_SENDMAIL_BCC = 'sendMailBcc'; // BCC Receiver email adresses. Separate multiple by comma.
......@@ -1234,6 +1242,13 @@ const FE_LDAP_USE_BIND_CREDENTIALS = F_LDAP_USE_BIND_CREDENTIALS;
const FE_TYPEAHEAD_LIMIT = F_TYPEAHEAD_LIMIT;
const FE_TYPEAHEAD_MINLENGTH = F_TYPEAHEAD_MINLENGTH;
const FE_TYPEAHEAD_PEDANTIC = F_TYPEAHEAD_PEDANTIC;
const FE_TYPEAHEAD_INITIAL_SUGGESTION = 'typeAheadInitialSuggestion';
const FE_TYPEAHEAD_TAG = 'typeAheadTag';
const FE_TYPEAHEAD_TAG_DELIMITER = 'typeAheadTagDelimiter';
const FE_TYPEAHEAD_GLUE_INSERT = 'typeAheadGlueInsert';
const FE_TYPEAHEAD_GLUE_DELETE = 'typeAheadGlueDelete';
const FE_TYPEAHEAD_TAG_INSERT = 'typeAheadTagInsert';
const FE_TYPEAHEAD_SQL = 'typeAheadSql';
const FE_TYPEAHEAD_SQL_PREFETCH = 'typeAheadSqlPrefetch';
const FE_TYPEAHEAD_LDAP_VALUE_PRINTF = F_TYPEAHEAD_LDAP_VALUE_PRINTF;
......@@ -1729,6 +1744,9 @@ const QUESTION_INDEX_FLAG_MODAL = 5;
const PARAM_DELIMITER = '|';
const PARAM_TOKEN_DELIMITER = ':';
const PARAM_LIST_DELIMITER = ',';
const PARAM_KEY_VALUE_DELIMITER = ':';
const TOKEN_URL = 'u';
const TOKEN_MAIL = 'm';
const TOKEN_PAGE = 'p';
......
......@@ -8,13 +8,13 @@
namespace IMATHUZH\Qfq\Core\Database;
use IMATHUZH\Qfq\Core\Store\Store;
use IMATHUZH\Qfq\Core\Helper\Logger;
use IMATHUZH\Qfq\Core\Helper\BindParam;
use IMATHUZH\Qfq\Core\Helper\HelperFile;
use IMATHUZH\Qfq\Core\Helper\HelperFormElement;
use IMATHUZH\Qfq\Core\Helper\Logger;
use IMATHUZH\Qfq\Core\Helper\OnArray;
use IMATHUZH\Qfq\Core\Helper\HelperFile;
use IMATHUZH\Qfq\Core\Store\Store;
/**
* Class Database
......@@ -782,7 +782,7 @@ class Database {
* @throws \UserFormException
*/
public function getTableDefinition($table) {
return $this->sql("SHOW FIELDS FROM `$table`");
return $this->sql("SHOW FIELDS FROM `$table`", ROW_EXPECT_GE_1, array(), "No columns found for table '$table'");
}
/**
......@@ -932,10 +932,12 @@ class Database {
}
/**
* $arr will be converted to a two column array with keys $keyName1 and $keyName2.
* $arr might contain one or more columns.
* Only when $keyName1 and $keyName2 exist, those will be used. Else the first column becomes $keyName1 and the
* second becomes $keyName2. If there is only one column, that column will be doubled.
* $arr = [ 0 => [ $srcColumn1 => $value0_1, $srcColumn2 => $value0_2 ], 1 => [ $srcColumn1 => $value1_1, $srcColumn2 => $value1_2 ], ...]
*
* $arr will be converted to a two column array with keys $destColumn1 and $destColumn2.
* If $destColumn1 or $destColumn2 is empty, take $srcColumn1, $srcColumn2 as names.
* $arr might contain one or more columns. Only the first two columns are used.
* If there is only one column, that column will be doubled.
*
* @param array $arr
* @param string $srcColumn1
......@@ -947,6 +949,11 @@ class Database {
*/
public function makeArrayDict(array $arr, $srcColumn1, $srcColumn2, $destColumn1 = '', $destColumn2 = '') {
if ($arr == array() || $arr === null) {
return array();
}
// Set defaults
if ($destColumn1 == '') {
$destColumn1 = $srcColumn1;
}
......@@ -955,12 +962,7 @@ class Database {
$destColumn2 = $srcColumn2;
}
$new = array();
if ($arr == array() || $arr === null) {
return array();
}
// Set final column names
$row = $arr[0];
$keys = array_keys($row);
if (count($row) < 2) {
......@@ -974,9 +976,11 @@ class Database {
$column2 = $keys[1];
}
$new = array();
$row = array_shift($arr);
while (null !== $row) {
$new[] = [$destColumn1 => $row[$column1], $destColumn2 => $row[$column2]];
// $new[] = [$destColumn1 => htmlentities($row[$column1], ENT_QUOTES), $destColumn2 => htmlentities($row[$column2], ENT_QUOTES)];
$row = array_shift($arr);
}
......@@ -1028,8 +1032,7 @@ class Database {
try {
$this->playMultiQuery($query);
}
catch (\CodeException $e) {
} catch (\CodeException $e) {
throw new \CodeException("Error playing $filename", ERROR_PLAY_SQL_FILE);
}
......
......@@ -135,10 +135,11 @@ class Evaluate {
* @param string $line
* @param string $sqlMode ROW_IMPLODE | ROW_REGULAR | ... - might be overwritten in $line by '{{!...'
* @param int $recursion
*
* @param array $debugStack
* @param string $foundInStore
* @return array|mixed|null|string
*
* @return array|mixed|null|string - in case of INSERT: last_insert_id()
*
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
......
......@@ -342,12 +342,15 @@ class FormAction {
}
/**
* Process slaveId, sqlBefore, sqlInsert|sqlUpdate|sqlDelete, sqlAfter.
* flagFeAction=false: for Native Elements
* flagFeAction=true: for Action Elements
*
* Create the slave record. First try to evaluate a slaveId. Depending if the slaveId > 0 choose `sqlUpdate` or
* `sqlInsert`
*
* @param array $fe
* @param int $recordId
*
* @param bool $flagFeAction indicates of the FE are of type 'native' or 'action'.
* @return int ACTION_ELEMENT_MODIFIED if there are potential(!) changes on the DB like INSERT / UPDATE,
* ACTION_ELEMENT_NO_CHANGE if nothing happened
......
......@@ -715,8 +715,10 @@ EOF;
case FE_MODE_SHOW_REQUIRED:
break;
case FE_MODE_REQUIRED:
$attribute .= Support::doAttribute('required', 'required');
break;
case FE_MODE_READONLY:
$attribute .= Support::doAttribute($feMode, $feMode);
$attribute .= Support::doAttribute('disabled', 'disabled');
break;
default:
throw new \UserFormException("Unknown mode '$feMode'", ERROR_UNKNOWN_MODE);
......
......@@ -41,7 +41,8 @@ class KeyValueStringParser {
*
* @return string
*/
public static function unparse(array $keyValueArray, $keyValueDelimiter = ":", $listDelimiter = ",") {
public static function unparse(array $keyValueArray, $keyValueDelimiter = PARAM_KEY_VALUE_DELIMITER, $listDelimiter = PARAM_LIST_DELIMITER, $flagEscape = false) {
array_walk($keyValueArray, function (&$value) {
if (!is_string($value) || $value === "" || strlen($value) === 1) {
return;
......@@ -54,6 +55,13 @@ class KeyValueStringParser {
$newKeyValuePairImploded = array();
foreach ($keyValueArray as $key => $value) {
if ($flagEscape) {
$key = str_replace($keyValueDelimiter, '\\' . $keyValueDelimiter, $key);
$key = str_replace($listDelimiter, '\\' . $listDelimiter, $key);
$value = str_replace($keyValueDelimiter, '\\' . $keyValueDelimiter, $value);
$value = str_replace($listDelimiter, '\\' . $listDelimiter, $value);
}
$newKeyValuePairImploded[] = trim($key) . $keyValueDelimiter . $value;
}
......
......@@ -427,4 +427,27 @@ class OnArray {
public static function getMd5(array $data) {
return md5(implode($data));
}
/**
* Converts a one dimensional array to JSON array. The 'key' and 'value' will names are hardcoded:
*
* Return: [ { 'key': $key[0], 'value': $value[0] }, { 'key': $key[1], 'value': $value[2] }, ... ]
*
* @param array $arr
* @param bool $flagHtmlEntity true|false
* @return string
*/
public static function arrayToQfqJson(array $arr, $flagHtmlEntity = false) {
$json = '';
foreach ($arr as $arrKey => $arrValue) {
if ($flagHtmlEntity) {
$arrKey = htmlentities($arrKey, ENT_QUOTES);
$arrValue = htmlentities($arrValue, ENT_QUOTES);
}
$json .= ',' . json_encode(["key" => $arrKey, "value" => $arrValue]);
}
return '[' . substr($json, 1) . ']';
}
}
\ No newline at end of file
......@@ -292,8 +292,8 @@ class Support {
*
* TinyMCE: Encoding JS Attributes (keys & values) for TinyMCE needs to be encapsulated in '&quot;' instead of '\"'.
*
* @param $str
* @param string $modeEscape
* @param string $str
* @param string $modeEscape ESCAPE_WITH_BACKSLASH | ESCAPE_WITH_HTML_QUOTE
*
* @return string
* @throws \CodeException
......
......@@ -550,9 +550,6 @@ class QuickFormQuery {
$rc = $save->process();
// Reload fresh saved record and fill STORE_RECORD with it.
$this->store->fillStoreWithRecord($this->formSpec[F_TABLE_NAME], $rc, $this->dbArray[$this->dbIndexData], $this->formSpec[F_PRIMARY_KEY]);
$save->processAllUploads($rc);
// Action: After*, Sendmail
......@@ -572,17 +569,16 @@ class QuickFormQuery {
break;
}
$customForward = $this->setForwardModePage();
$this->setForwardModePage();
// Logic: If a) r=0 and
// b) User presses only 'save' (not save & close) and
// c) there is no forwardMode=='url...'
// b) final: (forwardMode=='auto' and User presses only 'save' (not 'save & close')) OR (forwardMode=='no')
// then the client should reload the current page with the newly created record. A new SIP is necessary!
$getJson = true;
if (0 == $this->store->getVar(SIP_RECORD_ID, STORE_SIP) &&
API_SUBMIT_REASON_SAVE == $this->store->getVar(API_SUBMIT_REASON, STORE_CLIENT . STORE_EMPTY, SANITIZE_ALLOW_ALNUMX) &&
$customForward == false
) {
if (0 == $this->store->getVar(SIP_RECORD_ID, STORE_SIP)
&& (($this->formSpec[F_FORWARD_MODE] == F_FORWARD_MODE_AUTO
&& API_SUBMIT_REASON_SAVE == $this->store->getVar(API_SUBMIT_REASON, STORE_CLIENT . STORE_EMPTY, SANITIZE_ALLOW_ALNUMX)
) || $this->formSpec[F_FORWARD_MODE] == F_FORWARD_MODE_NO)) {
$this->formSpec = $this->buildNSetReloadUrl($this->formSpec, $rc);
$getJson = false;
}
......@@ -791,12 +787,10 @@ class QuickFormQuery {
*
* '$this->formSpec[F_FORWARD_PAGE]':
* a) url http://www.nzz.ch/index.html?a=123#bottom, website.html?a=123#bottom,
* ?[id=]<T3 Alias pageid>&a=123#bottom, ?id=<T3 page id>&a=123#bottom
* ?[id=]<T3 Alias pageId>&a=123#bottom, ?id=<T3 pageId>&a=123#bottom
* b) mode no|client|url|...
* c) mode|url combination of above
*
* @return bool TRUE if F_FORWARD_MODE = 'url..', else FALSE
*
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
......@@ -805,7 +799,7 @@ class QuickFormQuery {
private function setForwardModePage() {
if (F_FORWARD_MODE_URL != substr($this->formSpec[F_FORWARD_MODE], 0, 3)) {
return false;
return;
}
$forwardPageTmp = $this->evaluate->parse($this->formSpec[F_FORWARD_PAGE]);
......@@ -835,20 +829,11 @@ class QuickFormQuery {
break;
}
if ('url' == substr($this->formSpec[F_FORWARD_MODE], 0, 3)) {
if (F_FORWARD_MODE_URL == substr($this->formSpec[F_FORWARD_MODE], 0, 3)) {
if ($this->formSpec[F_FORWARD_PAGE] == '') {
$this->formSpec[F_FORWARD_MODE] = F_FORWARD_MODE_AUTO;
$customForward = false;
} else {
$customForward = true;
}
} else {
$customForward = false;
}
return $customForward;
}
/**
......
......@@ -63,6 +63,21 @@ class Download {
*/
private $downloadDebugLog = '';
/**
* @var string Name of command
*/
private $qpdf = '';
/**
* @var string Name of command
*/
private $gs = '';
/**
* @var string Name of command
*/
private $pdfunite = '';
/**
* @var string DOWNLOAD_OUTPUT_FORMAT_RAW | DOWNLOAD_OUTPUT_FORMAT_JSON
*/
......@@ -83,6 +98,10 @@ class Download {
$this->db = new Database();
$this->html2pdf = new Html2Pdf($this->store->getStore(STORE_SYSTEM), $phpUnit);
$this->qpdf = $this->store->getVar(SYSTEM_CMD_QPDF, STORE_SYSTEM);
$this->gs = $this->store->getVar(SYSTEM_CMD_GS, STORE_SYSTEM);
$this->pdfunite = $this->store->getVar(SYSTEM_CMD_PDFUNITE, STORE_SYSTEM);
if (Support::findInSet(SYSTEM_SHOW_DEBUG_INFO_DOWNLOAD, $this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM))) {
$this->downloadDebugLog = $this->store->getVar(SYSTEM_SQL_LOG, STORE_SYSTEM);
}
......@@ -140,7 +159,7 @@ class Download {
// $cmd = "pdftk $inputFiles cat output $concatFile 2>&1"; # Outdated. Hard to install on Ubuntu 18. Fails for recent PDFs.
// $cmd = "qpdf --empty --pages $inputFiles -- $concatFile 2>&1"; # Fails to merge identical files, if they contain references.
$cmd = "pdfunite $inputFiles $concatFile 2>&1"; // Based on poppler. URLs are preserved. Orientation and size are preserved.
$cmd = $this->pdfunite . " $inputFiles $concatFile 2>&1"; // Based on poppler. URLs are preserved. Orientation and size are preserved.
if ($this->downloadDebugLog != '') {
Logger::logMessage("Download: $cmd", $this->downloadDebugLog);
......@@ -201,13 +220,13 @@ class Download {
}
// Try 1: via 'qpdf --decrypt'
$cmdQpdf = "qpdf --decrypt '$backup' '$file' 2>&1"; // Try to decrypt file
$cmdQpdf = $this->qpdf . " --decrypt '$backup' '$file' 2>&1"; // Try to decrypt file
exec($cmdQpdf, $outputQpdf, $rcQpdf);
if ($rcQpdf != 0) {
// Try 2: via 'gs -sDEVICE=pdfwrite'
$cmdGs = "gs -sDEVICE=pdfwrite -dNOPAUSE -sOutputFile=\"$file\" -- \"$backup\"";
$cmdGs = $this->gs . " -sDEVICE=pdfwrite -dNOPAUSE -sOutputFile=\"$file\" -- \"$backup\"";
exec($cmdGs, $outputGs, $rcGs);
if ($rcGs != 0) {
......@@ -413,10 +432,8 @@ class Download {
$qfq = new QuickFormQuery([T3DATA_BODYTEXT => $tt_content[T3DATA_BODYTEXT]], false, false);
return $qfq->process();
}
/**
* Creates a ZIP Files of all given $files
*
......
......@@ -49,7 +49,7 @@ use IMATHUZH\Qfq\Core\Store\Store;
* G:Glyph
* h:
* H:Help
* i:
* i:icon (Font Awesome, t)
* I:information
* j:
* J:
......@@ -1529,8 +1529,8 @@ EOF;
// By default, qfq saves everything HTML encoded. E.g. in form '&#39;' - decode them back to regual UTF-8 text.
$filename = html_entity_decode($vars[DOWNLOAD_EXPORT_FILENAME], ENT_QUOTES | ENT_XML1, 'UTF-8');
// Remove unsafe characters.
$vars[DOWNLOAD_EXPORT_FILENAME] = Sanitize::safeFilename($filename);
// Remove unsafe characters. For '... AS _savePdf' slashes have to be allowed.
$vars[DOWNLOAD_EXPORT_FILENAME] = Sanitize::safeFilename($filename, false, true);
return $vars;
}
......
......@@ -677,7 +677,7 @@ class Report {
/**
* Called with an array of column names.
* Each column name can be splitted in multiple string by '|': [s1[|s2[|s3]]]
* Each column name can be split in multiple string by '|': [s1[|s2[|s3]]]
* Each s1|s2|s3 can be: {title}, _{special colum name}, _hide, _noWrap, _={title}, _+{tag}, _<{tag1}><{tag2}>
*
* Return an Array: newKeys[idx][C_FULL|C_TITLE|C_NO_WRAP|C_HIDE]
......@@ -688,7 +688,7 @@ class Report {
*/
private function splitColumnNames(array $keys, array $fSkipWrap = array()) {
// Split Keynames in title / specialColumnName / fSkipWrap / hide
// Split key names in title / specialColumnName / fSkipWrap / hide
$ii = 0;
$newKeys = array();
......
......@@ -12,6 +12,7 @@ use IMATHUZH\Qfq\Core\Database\Database;
use IMATHUZH\Qfq\Core\Form\FormAction;
use IMATHUZH\Qfq\Core\Helper\HelperFile;
use IMATHUZH\Qfq\Core\Helper\HelperFormElement;
use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser;
use IMATHUZH\Qfq\Core\Helper\Logger;
use IMATHUZH\Qfq\Core\Helper\OnArray;
use IMATHUZH\Qfq\Core\Helper\Sanitize;
......@@ -288,7 +289,6 @@ class Save {
}
$newValues[$column] = $formValues[$column];
$realColumnFound = true;
}
// Only save record if real columns exist.
......@@ -309,12 +309,17 @@ class Save {
}
}
// Reload fresh saved record and fill STORE_RECORD with it. Do this before nativeDoSlave().
$this->store->fillStoreWithRecord($this->formSpec[F_TABLE_NAME], $recordId, $this->db, $this->formSpec[F_PRIMARY_KEY]);
$this->nativeDoSlave($recordId);
return $recordId;
}
/**
* Process sqlBefore, sqlInsert|.... for all native FE.
*
* @param $recordId
*
* @throws \CodeException
......@@ -330,6 +335,67 @@ class Save {
$this->store->setVar(SYSTEM_FORM_ELEMENT_ID, $fe[FE_ID], STORE_SYSTEM);
$this->formAction->doSqlBeforeSlaveAfter($fe, $recordId, false);
$this->typeAheadDoTagGlue($fe);
}
}
/**
* typeAhead: if given, process Tag or Glue.
*
* @param array $fe
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
* @throws \UserReportException
*/
private function typeAheadDoTagGlue(array $fe) {
// Update 'glue' records?
if (($fe[FE_TYPEAHEAD_TAG] ?? '0') == '0' || (!isset($fe[FE_TYPEAHEAD_GLUE_INSERT]) && !isset($fe[FE_TYPEAHEAD_TAG_INSERT]))) {
return;
}
if (empty($fe[FE_TYPEAHEAD_GLUE_INSERT]) || empty($fe[FE_TYPEAHEAD_GLUE_DELETE])) {
throw new \UserFormException("Missing 'typeAheadGlueInsert' or 'typeAheadGlueDelete'", ERROR_MISSING_REQUIRED_PARAMETER);
}
// Extract assigned tags: last
$tagLast = KeyValueStringParser::parse($this->evaluate->parse($fe[FE_VALUE], ROW_EXPECT_0_1));
// Extract assigned tags: new
$tagNew = KeyValueStringParser::parse($this->store->getVar($fe[FE_NAME], STORE_FORM,
($fe[FE_CHECK_TYPE] == SANITIZE_ALLOW_AUTO) ? SANITIZE_ALLOW_ALNUMX : $fe[FE_CHECK_TYPE]));
// Get all tags who are new
$result = array_diff_assoc($tagNew, $tagLast);
// Create glue records
foreach ($result as $id => $value) {
$this->store->setVar(VAR_TAG_ID, $id, STORE_VAR);
$this->store->setVar(VAR_TAG_VALUE, $value, STORE_VAR);
if ($id == 0) {
if (empty($fe[FE_TYPEAHEAD_TAG_INSERT])) {
throw new \UserFormException("Missing 'typeAheadTagInsert'", ERROR_MISSING_REQUIRED_PARAMETER);
}
// Create tag
$id = $this->evaluate->parse($fe[FE_TYPEAHEAD_TAG_INSERT]);
$this->store->setVar(VAR_TAG_ID, $id, STORE_VAR);
}
// Create glue
$this->evaluate->parse($fe[FE_TYPEAHEAD_GLUE_INSERT]);
}
// Get all tags who has been removed
$result = array_diff_assoc($tagLast, $tagNew);
// Delete Glue records
foreach ($result as $id => $value) {
$this->store->setVar(VAR_TAG_ID, $id, STORE_VAR);
$this->store->setVar(VAR_TAG_VALUE, $value, STORE_VAR);
// Delete glue
$this->evaluate->parse($fe[FE_TYPEAHEAD_GLUE_DELETE]);
}
}