diff --git a/Documentation/Form.rst b/Documentation/Form.rst
index 5d7a35e207462a95dd910ccb8558ac9358c5fb0e..a475eb5c4990b0d0a2262f385c3f134291c6d696 100644
--- a/Documentation/Form.rst
+++ b/Documentation/Form.rst
@@ -2027,9 +2027,7 @@ FormElement.parameter
* If `downloadButton` is empty, just shows the regular download glyph.
* To just show the filename: `downloadButton = t:{{filenameOnly:V}}`
- * Additional attributes might be given like `downloadButton = t:Download|o:check file`. Please check :ref:`download`.
-
- * The following attributes are hard coded (can't be changed): `s|M:file|d|F`
+ * Additional attributes might be given like `downloadButton = t:Download|o:check file|G:0`. Please check :ref:`download`.
* *fileUnzip* - If the file is a ZIP file (only then) it will be unzipped. If no directory is given via ``fileUnzip``, the
basedir of ``fileDestination`` is taken, appended by ``unpack``.
diff --git a/extension/Classes/Core/AbstractBuildForm.php b/extension/Classes/Core/AbstractBuildForm.php
index 43c104acb077464c8846c364d70ecb958b68a6a6..8b032674bea11135326c55d9673c74dec2d5c1b4 100644
--- a/extension/Classes/Core/AbstractBuildForm.php
+++ b/extension/Classes/Core/AbstractBuildForm.php
@@ -2728,7 +2728,7 @@ abstract class AbstractBuildForm {
if (!empty($value) && Support::isEnabled($formElement, FE_FILE_DOWNLOAD_BUTTON)) {
if (is_readable($value)) {
$link = new Link($this->sip, $this->dbIndexData);
- $value = $link->renderLink('s|M:file|d|F:' . $value . '|' . $this->evaluate->parse($formElement[FE_FILE_DOWNLOAD_BUTTON]));
+ $value = $link->renderLink($this->evaluate->parse($formElement[FE_FILE_DOWNLOAD_BUTTON]), 's|M:file|d|F:' . $value);
} else {
$msg = "Already uploaded file not found.";
diff --git a/extension/Classes/Core/Helper/KeyValueStringParser.php b/extension/Classes/Core/Helper/KeyValueStringParser.php
index 86b7626fc3be34209a7760650a884844ccbc3ca6..ae817874c3eb4deb777984b2bc53b16b87b397ba 100644
--- a/extension/Classes/Core/Helper/KeyValueStringParser.php
+++ b/extension/Classes/Core/Helper/KeyValueStringParser.php
@@ -10,7 +10,6 @@
namespace IMATHUZH\Qfq\Core\Helper;
-
/**
* Class KeyValueStringParser
@@ -82,7 +81,7 @@ class KeyValueStringParser {
}
/**
- * Parse key/value pairs string and returns them as an assoc array
+ * Parse key/value pairs string and returns them as an assoc array. Respects escape '\'.
*
* Hint $keyValueString: "a:1,b:2,c:,d", "," (empty key AND empty value)
*
@@ -112,7 +111,7 @@ class KeyValueStringParser {
// Check if there are 'escaped delimiter', If yes, use the more expensive explodeEscape().
if (strpos($keyValueString, '\\' . $listDelimiter) !== false) {
- $keyValuePairs = self::explodeEscape($listDelimiter, $keyValueString, 2);
+ $keyValuePairs = self::explodeEscape($listDelimiter, $keyValueString);
} else {
$keyValuePairs = explode($listDelimiter, $keyValueString);
}
@@ -178,12 +177,18 @@ class KeyValueStringParser {
// In case there is an upper limit of array elements
if ($max > 0 && count($items) > $max) {
$remain = array_slice($items, 0, $max);
- $remain[$max - 1] .= implode('', array_slice($items, $max));
+ $remain[$max - 1] .= $delimiter . implode($delimiter, array_slice($items, $max));
+ # Take care, that the 'escape' in last element is not cleaned!
+ for ($ii = 0; $ii <= $max - 2; $ii++) {
+ $remain[$ii] = str_replace('\\' . $delimiter, $delimiter, $remain[$ii]);
+ }
$items = $remain;
+ } else {
+ // Escaped tokens: the escape character needs to be replaced.
+ $items = OnArray::arrayValueReplace($items, '\\' . $delimiter, $delimiter);
}
- // Escaped tokens: the escape character needs to be replaced.
- return OnArray::arrayValueReplace($items, '\\' . $delimiter, $delimiter);
+ return $items;
}
/**
@@ -289,4 +294,26 @@ class KeyValueStringParser {
return $final;
}
+
+ /**
+ * Parse kvp string. Key has to be uniq otherwise only the last will be taken. Escaping not supported.
+ *
+ * p:{{pageAlias:T}}|q:Delete?:yes:no|download:file.pdf
+ * @param $str
+ * @param string $keyValueDelimiter
+ * @param string $listDelimiter
+ */
+ public static function explodeKvpSimple($str, $keyValueDelimiter = ":", $listDelimiter = ",") {
+ $result = array();
+
+ $items = explode($listDelimiter, $str);
+ foreach ($items as $item) {
+ if ($item == '') {
+ continue;
+ }
+ $arg = explode($keyValueDelimiter, $item, 2);
+ $result[$arg[0]] = $arg[1] ?? '';
+ }
+ return $result;
+ }
}
diff --git a/extension/Classes/Core/Helper/Token.php b/extension/Classes/Core/Helper/Token.php
index 87abadb46bcc04c5c1dfcc09572e5fefde2d260a..e9b0f13c4a171b4169164dd6ed8fd02c8f363fcd 100644
--- a/extension/Classes/Core/Helper/Token.php
+++ b/extension/Classes/Core/Helper/Token.php
@@ -42,14 +42,8 @@ class Token {
$value = 'r';
break;
case TOKEN_ENCRYPTION:
- $value = '1';
- break;
case TOKEN_SIP:
- $value = '1';
- break;
case TOKEN_BOOTSTRAP_BUTTON:
- $value = '1';
- break;
case TOKEN_MONITOR:
$value = '1';
break;
diff --git a/extension/Classes/Core/Report/Link.php b/extension/Classes/Core/Report/Link.php
index b8ee821ebb84f9e5a0bb7992fed8f0bffdb9fafc..ed78c3ac6da551d7d52078b63261dd280c0f9115 100644
--- a/extension/Classes/Core/Report/Link.php
+++ b/extension/Classes/Core/Report/Link.php
@@ -585,14 +585,16 @@ class Link {
* Build the whole link.
*
* @param string $str Qualifier with params. 'report'-syntax. F.e.: u:www.example.com|P:home.gif|t:Home"
+ * @param string $strDefault Same as $str, but might give some defaults if corresponding values in $str are missing.
*
* @return string The complete HTML encoded Link like
*
Description
* @throws \CodeException
+ * @throws \DbException
* @throws \UserFormException
* @throws \UserReportException
*/
- public function renderLink($str) {
+ public function renderLink($str, $strDefault = '') {
$tokenGiven = array();
$link = "";
@@ -618,7 +620,7 @@ class Link {
break;
}
- $vars = $this->fillParameter($str, $tokenGiven);
+ $vars = $this->fillParameter($str, $tokenGiven, $strDefault);
$vars = $this->processParameter($vars, $tokenGiven);
$mode = $this->getModeRender($vars, $tokenGiven);
@@ -697,35 +699,61 @@ class Link {
}
/**
- * Order $param. Parameter with priority are hardcoded at the moment.
+ * $str and $strDefault are standard QFQ link format strings like 'p:{{pageAlias:T}}|t:linktext|b|s|...'
+ * Parameter missing in $str and given in $strDefault will used.
*
- * @param array $param
+ * Split Parameter string in num Array (assoc is not possible cause for 'download', multiple sources with same key are possible).
+ * Reorder param to bring prio token (currently only 'd') to top.
*
+ * @param $str
+ * @param $strDefault
* @return array
*/
- private function paramPriority(array $param) {
+ private function paramPreparation($str, $strDefault = '') {
$prio = array();
$regular = array();
+ // str="u:http://www.example.com|c:i|t:Hello World|q:Do you really want to delete the record 25:warn:yes:no"
+ // Return a numbered array with strings like [ 0 => 'p:..', 1 => 't:...' , ...]
+ $param = KeyValueStringParser::explodeEscape(PARAM_DELIMITER, $str);
+
+ // Return an assoc array like [ 'd' => 'file.pdf', 'p' => 'content', ... ]
+ $assocDefault = KeyValueStringParser::explodeKvpSimple($strDefault, PARAM_TOKEN_DELIMITER, PARAM_DELIMITER);
+
foreach ($param as $value) {
if ($value == '') {
continue;
}
- $key = substr($value, 0, 2);
+ $arr = explode(PARAM_TOKEN_DELIMITER, $value, 2);
+ $key = $arr[0];
+ $value = $arr[1] ?? '';
- if (strlen($key) == 1) {
- $key .= PARAM_TOKEN_DELIMITER;
+ if ($key == TOKEN_DOWNLOAD) {
+ $prio[] = [$key => $value ?? ''];
+ } else {
+ $regular[] = [$key => $value ?? ''];
}
- if ($key == TOKEN_DOWNLOAD . PARAM_TOKEN_DELIMITER) {
- $prio[] = $value;
- } else {
- $regular[] = $value;
+ // Explicit given arg: remove from default
+ if (isset($assocDefault[$key])) {
+ unset ($assocDefault[$key]);
}
}
+ // Apply defaults, if not already given
+
+ // First check if there is a prio item - currently only TOKEN_DOWNLOAD is of this type.
+ if (isset($assocDefault[TOKEN_DOWNLOAD])) {
+ $prio[] = [TOKEN_DOWNLOAD => $assocDefault[TOKEN_DOWNLOAD]];
+ unset ($assocDefault[TOKEN_DOWNLOAD]);
+ }
+ // Append all remaining defaults to regular
+ foreach ($assocDefault as $key => $value) {
+ $regular[] = [$key => $value];
+ }
+
return array_merge($prio, $regular);
}
@@ -735,39 +763,38 @@ class Link {
* @param string $str
* @param array $rcTokenGiven - return an array with found token.
*
+ * @param string $strDefault
* @return array
* @throws \CodeException
+ * @throws \DbException
* @throws \UserFormException
* @throws \UserReportException
*/
- public function fillParameter($str, array &$rcTokenGiven) {
+ public function fillParameter($str, array &$rcTokenGiven, $strDefault = '') {
+ $rcTokenGiven = array();
// Define all possible vars: no more isset().
$vars = $this->initVars();
$flagArray = array();
- // str="u:http://www.example.com|c:i|t:Hello World|q:Do you really want to delete the record 25:warn:yes:no"
- $param = KeyValueStringParser::explodeEscape(PARAM_DELIMITER, $str);
-
- $param = $this->paramPriority($param);
+ $items = $this->paramPreparation($str, $strDefault);
// Parse all parameter, fill variables.
- foreach ($param as $item) {
+ foreach ($items as $item) {
+
+ $value = reset($item);
+ $key = key($item);
// Skip empty entries
- if ($item === '') {
+ if (empty($key)) {
continue;
}
- // u:www.example.com
- $arr = explode(":", $item, 2);
- $key = isset($arr[0]) ? $arr[0] : '';
- $value = isset($arr[1]) ? $arr[1] : '';
-
// Bookkeeping defined parameter.
if (isset($rcTokenGiven[$key])) {
throw new \UserReportException ("Multiple definitions for key '$key'", ERROR_MULTIPLE_DEFINITION);
}
+
$rcTokenGiven[$key] = true;
if (!isset($this->tableVarName[$key])) {
diff --git a/extension/Tests/Unit/Core/Helper/KeyValueStringParserTest.php b/extension/Tests/Unit/Core/Helper/KeyValueStringParserTest.php
index e1d425f48ca653338f8d6f4c7f630da39cb474d8..5175e8852c07c57c4c1a0716a0cedaa3d7b8badd 100644
--- a/extension/Tests/Unit/Core/Helper/KeyValueStringParserTest.php
+++ b/extension/Tests/Unit/Core/Helper/KeyValueStringParserTest.php
@@ -5,7 +5,7 @@
namespace IMATHUZH\Qfq\Tests\Unit\Core\Helper;
-
+
use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser;
use PHPUnit\Framework\TestCase;
@@ -170,6 +170,42 @@ class KeyValueStringParserTest extends TestCase {
$expected = KeyValueStringParser::parse("key1=value1,key2=value2", ":", ",");
$this->assertEquals($expected, $actual);
$this->assertCount(2, $actual);
+
+ $actual = KeyValueStringParser::parse("key1:value1,key2:value2");
+ $expected = KeyValueStringParser::parse("key1:value1,key2:value2", ":", ",");
+ $this->assertEquals($expected, $actual);
+ $this->assertCount(2, $actual);
+ }
+
+ public function testParseEscapeMax() {
+ $actual = KeyValueStringParser::parse("a:hello A,b:hello B,c:hello C");
+ $this->assertEquals(['a' => 'hello A', 'b' => 'hello B', 'c' => 'hello C'], $actual);
+
+ $actual = KeyValueStringParser::parse("a\:new:hello A,b:hello B,c:hello C");
+ $this->assertEquals(['a:new' => 'hello A', 'b' => 'hello B', 'c' => 'hello C'], $actual);
+
+ $actual = KeyValueStringParser::parse("a\:new:hello A:A:A,b:hello B,c:hello C");
+ $this->assertEquals(['a:new' => 'hello A:A:A', 'b' => 'hello B', 'c' => 'hello C'], $actual);
+
+ // Escape char will be removed
+ $actual = KeyValueStringParser::parse("a\:new:hello A\:A\:A,b:hello B,c:hello C");
+ $this->assertEquals(['a:new' => 'hello A:A:A', 'b' => 'hello B', 'c' => 'hello C'], $actual);
+
+ // Escape list delimiter
+ $actual = KeyValueStringParser::parse("a\,x:A\,A,b\,x:B\,B");
+ $this->assertEquals(['a,x' => 'A,A', 'b,x' => 'B,B'], $actual);
+
+ // Escape list delimiter & key/value delimiter
+ $actual = KeyValueStringParser::parse("a\,\:x:A\,\:A,b\,\:x:B\,\:B");
+ $this->assertEquals(['a,:x' => 'A,:A', 'b,:x' => 'B,:B'], $actual);
+
+ // Escape char in value is untouched
+ $actual = KeyValueStringParser::parse("a\,x\,y:h\,e\,l\,l\,o A,b:hello B,c:C,d:D");
+ $this->assertEquals(['a,x,y' => 'h,e,l,l,o A', 'b' => 'hello B', 'c' => 'C', 'd' => 'D'], $actual);
+
+ // Escape char in value is untouched
+ $actual = KeyValueStringParser::parse("a\,x\,y:h\,e\,l\,l\,o A,b:hello B,c\,x\,y:h\,e\,l\,lo C");
+ $this->assertEquals(['a,x,y' => 'h,e,l,l,o A', 'b' => 'hello B', 'c,x,y' => 'h,e,l,lo C'], $actual);
}
public function testExplodeContent() {
@@ -351,11 +387,13 @@ class KeyValueStringParserTest extends TestCase {
$this->assertEquals(['a', 'b', 'c'], $actual);
$actual = KeyValueStringParser::explodeEscape(',', 'a,b,c', 2);
- $this->assertEquals(['a', 'bc'], $actual);
+ $this->assertEquals(['a', 'b,c'], $actual);
$actual = KeyValueStringParser::explodeEscape(',', 'a,b,c', 1);
- $this->assertEquals(['abc'], $actual);
+ $this->assertEquals(['a,b,c'], $actual);
+ $actual = KeyValueStringParser::explodeEscape(',', 'a\,b,c', 2);
+ $this->assertEquals(['a,b', 'c'], $actual);
}
diff --git a/extension/Tests/Unit/Core/Report/LinkTest.php b/extension/Tests/Unit/Core/Report/LinkTest.php
index 945a1cab80ba3d48f4b119dd368e33292a9a3af4..523ec6fa321755b1a2c5b35658e9f0076ba54afe 100644
--- a/extension/Tests/Unit/Core/Report/LinkTest.php
+++ b/extension/Tests/Unit/Core/Report/LinkTest.php
@@ -8,7 +8,7 @@
namespace IMATHUZH\Qfq\Tests\Unit\Core\Report;
-
+
use IMATHUZH\Qfq\Core\Report\Link;
use IMATHUZH\Qfq\Core\Store\Session;
use IMATHUZH\Qfq\Core\Store\Sip;
@@ -57,6 +57,103 @@ class LinkTest extends TestCase {
$link->renderLink('abc:hello world');
}
+ /**
+ * @throws \CodeException
+ * @throws \DbException
+ * @throws \UserFormException
+ * @throws \UserReportException
+ */
+ public function testfillParameter() {
+
+ $args = ['mail' => '',
+ 'url' => '',
+ 'page' => '',
+ 'text' => '',
+ 'altText' => '',
+ 'bootstrapButton' => '',
+ 'image' => '',
+ 'imageTitle' => '',
+ 'glyph' => '',
+ 'glyphTitle' => '',
+ 'question' => '',
+ 'target' => '',
+ 'toolTip' => '',
+ 'toolTipJs' => '',
+ 'param' => '',
+ 'extraContentWrap' => '',
+ 'mode' => '',
+ 'downloadElements' => array(),
+ 'copyToClipBoard' => '',
+ 'attribute' => '',
+ 'render' => '0',
+ 'picturePositionRight' => 'l',
+ 'sip' => '0',
+ 'encryption' => '0',
+ 'delete' => '',
+ 'monitor' => '0',
+ 'linkClass' => '',
+ 'linkClassDefault' => '',
+ 'actionDelete' => '',
+ 'finalHref' => '',
+ 'finalContent' => '',
+ 'finalSymbol' => '',
+ 'finalToolTip' => '',
+ 'finalClass' => '',
+ 'finalQuestion' => '',
+ 'finalThumbnail' => '',
+ ];
+
+ $link = new Link($this->sip, DB_INDEX_DEFAULT, true);
+
+ $rcTokenGiven = array();
+
+ // Empty definition
+ $expect = $args;
+ $result = $link->fillParameter('', $rcTokenGiven);
+ $this->assertEquals($expect, $result);
+
+ // 1 Parameter
+ $expect = $args;
+ $expect['page'] = '?id=page1';
+ $result = $link->fillParameter('p:page1', $rcTokenGiven);
+ $this->assertEquals($expect, $result);
+
+ // 1 Parameter, 1 Default
+ $expect = $args;
+ $expect['page'] = '?id=page1';
+ $expect['text'] = 'comment';
+ $result = $link->fillParameter('p:page1', $rcTokenGiven, 't:comment');
+ $this->assertEquals($expect, $result);
+
+ // 1 Parameter, 1 Default
+ $expect = $args;
+ $expect['page'] = '?id=page1';
+ $expect['text'] = 'comment';
+ $expect['sip'] = '1';
+ $result = $link->fillParameter('p:page1|t:comment', $rcTokenGiven, 'p:page1|t:comment|s');
+ $this->assertEquals($expect, $result);
+
+ $expect = $args;
+ $expect['url'] = 'typo3conf/ext/qfq/Classes/Api/download.php';
+ $expect['text'] = 'text';
+ $expect['bootstrapButton'] = '0';
+ $expect['glyph'] = 'glyphicon-file';
+ $expect['glyphTitle'] = 'Download';
+ $expect['extraContentWrap'] = '';
+ $expect['downloadElements'] = ['p:page1'];
+ $expect['sip'] = '1';
+ $expect['linkClassDefault'] = 'no_class';
+ $expect['_exportFilename'] = 'download.pdf';
+ $result = $link->fillParameter('p:page1|t:text', $rcTokenGiven, 'd:download.pdf');
+ $this->assertEquals($expect, $result);
+
+ $expect['mode'] = 'file';
+ $expect['downloadElements'] = ['p:page1', 'F:file.pdf'];
+ $result = $link->fillParameter('p:page1|t:text|d:download.pdf', $rcTokenGiven, 's|M:file|d|F:file.pdf');
+ $this->assertEquals($expect, $result);
+
+ }
+
/**
* @throws \CodeException
* @throws \UserFormException
@@ -92,6 +189,7 @@ class LinkTest extends TestCase {
* @throws \CodeException
* @throws \UserFormException
* @throws \UserReportException
+ * @throws \DbException
*/
public function testLinkUrlBasicExceptionDouble() {
$link = new Link($this->sip, DB_INDEX_DEFAULT, true);