Commit 86e1c393 authored by Carsten  Rose's avatar Carsten Rose
Browse files

Bug Fixes #11925. Form.parameter.downloadButton might contain now all...

Bug Fixes #11925. Form.parameter.downloadButton might contain now all parameter, incl. d, G, ... . KeyValueStringParser::explodeEscape() has been refactored. Function Link->paramPriority() has been renamed/rewritten to Link->paramPreparation(): double fire of explode() eliminated, code should be more efficient. Several new/enhanced unit tests.
parent 0c4fbab1
Pipeline #4918 passed with stages
in 4 minutes and 9 seconds
......@@ -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``.
......
......@@ -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.";
......
......@@ -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;
}
}
......@@ -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;
......
......@@ -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
* <a href='http://example.com' class='external'><img src='icon.gif' title='help text'>Description</a>
* @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])) {
......
......@@ -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);
}
......
......@@ -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'] = '<span class="btn btn-default" data-toggle="modal" data-target="#qfqModal101" data-title="#downloadPopupReplaceTitle#" data-text="#downloadPopupReplaceText#" data-backdrop="static" data-keyboard="false" onclick="$(\'#qfqModalTitle101\').text($(this).data(\'title\')); $(\'#qfqModalText101\').text($(this).data(\'text\'));">';
$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);
......
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