Commit 78207b79 authored by Carsten  Rose's avatar Carsten Rose
Browse files

Fixes #11265. Checked for BS and plain, active & disabled, with or without...

Fixes #11265. Checked for BS and plain, active & disabled, with or without glyph. Code refactored to use more of Link class.
parent 0c289c75
Pipeline #5091 passed with stages
in 3 minutes and 59 seconds
......@@ -931,7 +931,7 @@ const GLYPH_ICON_DUPLICATE = 'glyphicon-duplicate';
const GLYPH_ICON_VIEW = 'glyphicon-eye-open';
const GLYPH_ICON_FILE = 'glyphicon-file';
const GLYPH_ICON_COPY = 'glyphicon-copy';
const GLYPH_ICON_OPTION_VERTICAL = 'glyphicon-option-vertical';
// FORM columns: real
const F_ID = 'id';
const F_NAME = 'name';
......@@ -1842,8 +1842,6 @@ const TOKEN_L_CONTENT = 'content';
const TOKEN_L_TIMEOUT = 'timeout';
const TOKEN_L_SSL = 'ssl';
const LINK_SPECIAL_MODE_DROPDOWN = 'dropdown';
const MONITOR_MODE_APPEND_0 = '0';
const MONITOR_MODE_APPEND_1 = '1';
const MONITOR_SESSION_FILE_SEEK = 'monitor-seek-file';
......
......@@ -45,6 +45,7 @@ class Token {
case TOKEN_SIP:
case TOKEN_BOOTSTRAP_BUTTON:
case TOKEN_MONITOR:
case TOKEN_DROPDOWN:
$value = '1';
break;
case TOKEN_QUESTION:
......
......@@ -325,36 +325,6 @@ class Link {
$attributes .= Support::doAttribute('class', [$vars[NAME_BOOTSTRAP_BUTTON], 'disabled']);
}
return Support::wrapTag("<span $attributes>", $text);
}
/**
* In render mode 3,4,5 there is no '<a href ...>'. Nevertheless, tooltip and BS Button should be displayed.
* Do this by applying a '<span>' attribute around the text.
*
* @param array $vars
* @param $keyName
*
* @return mixed|string
* @throws \CodeException
*/
private function wrapDropdown(array $vars) {
$text = $vars[FINAL_CONTENT];
if ($vars[NAME_BOOTSTRAP_BUTTON] == '' && $vars[FINAL_TOOL_TIP] == '' && $vars[NAME_ATTRIBUTE] == '') {
return $text;
}
$attributes = Support::doAttribute('title', $vars[FINAL_TOOL_TIP]);
if ($vars[NAME_ATTRIBUTE] != '') {
$attributes .= $vars[NAME_ATTRIBUTE] . ' ';
}
if ($vars[NAME_BOOTSTRAP_BUTTON] != '') {
$attributes .= Support::doAttribute('class', [$vars[NAME_BOOTSTRAP_BUTTON], 'disabled']);
}
return Support::wrapTag("<span $attributes>", $text);
}
......@@ -366,39 +336,28 @@ class Link {
* '||p:detail&pId=1&s|t:Person 1' - Page id=detail with pId=1 will be opened in the browser.
* '||d:file.pdf|p:detail&pId=1&_sip=1||t:Person as PDF' - Page id=detail with pId=1 will downloaded as a PDF.
*
* @param array $rcMenuEntryStrArr
* @return string
* @throws \CodeException
* @throws \UserFormException
* @throws \UserReportException
*/
private function processDropdown($str) {
private function processDropdown($str, array &$rcMenuEntryStrArr) {
$menuEntryStrArr = array();
$menuEntryLinkArr = array();
$rcMenuEntryStrArr = array();
$tokenCollect = array();
$htmlId = Support::uniqIdQfq('dd_');
// Split 'z|t:menu|b|o:click me||p:detail&pId=1&s|t:Person 1||...'. Add '||' to take care that the last element is flushed.
$paramArr = KeyValueStringParser::explodeEscape(PARAM_DELIMITER, $str . '||');
// Split 'z|t:menu|b|o:click me||p:detail&pId=1&s|t:Person 1||...'
$paramArr = KeyValueStringParser::explodeEscape(PARAM_DELIMITER, $str);
// Iterate over token. Find delimiter to separate dropdown definition and all menu entries.
// Iterate over token. Find delimiter to separate dropdown definition and all individual menu entries.
foreach ($paramArr as $tokenStr) {
$tokenArr = explode(PARAM_TOKEN_DELIMITER, $tokenStr, 2);
switch ($tokenArr[0] ?? '') {
case TOKEN_DROPDOWN:
$tokenCollect[] = ''; // In case there are no further options given at all, it's necessary to have at least one
break;
// Indicator to start menu entry: force a flush of existing token and start a new round.
case '':
// New menu entry.
if (!empty($tokenCollect)) {
$menuEntryStrArr[] = implode(PARAM_DELIMITER, $tokenCollect);
$rcMenuEntryStrArr[] = implode(PARAM_DELIMITER, $tokenCollect);
}
$tokenCollect = array();
// $tokenCollect[] = $tokenStr;
break;
default:
......@@ -406,24 +365,10 @@ class Link {
}
}
// Flush remaining element.
$menuEntryStrArr[] = implode(PARAM_DELIMITER, $tokenCollect);
// Remove first array element - that's the menu, not a menu entry. Render the menu.
// $flagMenuEnabled = false;
// Final rendering of dropdown Menu
$dropdownSymbol = $this->renderDropdownSymbol(array_shift($menuEntryStrArr), $htmlId, $flagMenuEnabled);
if ($flagMenuEnabled) {
// For each menu entry get the link
foreach ($menuEntryStrArr as $str) {
$menuEntryLinkArr[] = $this->renderLink($str);
}
}
return $this->renderDropdown($dropdownSymbol, $menuEntryLinkArr, $htmlId, $flagMenuEnabled);
// First entry is the dropdown button, all others are the menu entries
$dropdownButtonStr = array_shift($rcMenuEntryStrArr);
return $dropdownButtonStr;
}
/**
......@@ -456,126 +401,53 @@ class Link {
* </div>
* End
*
* @param string $dropdownSymbol
* @param array $menuEntryLinkArr
* @param string $htmlId
* @param bool $flagMenuEnabled
* @return string
*/
private function renderDropdown(string $dropdownSymbol, array $menuEntryLinkArr, $htmlId, $flagMenuEnabled) {
$ul = '';
$li = '';
if ($flagMenuEnabled) {
foreach ($menuEntryLinkArr as $link) {
$attribute = '';
switch (substr($link, 0, 3)) {
case '---':
$link = substr($link, 3);
if ($link == '') {
# Separator
$attribute = ' role="separator" class="divider"';
} else {
# Disabled
$attribute = ' class="disabled"';
if (false === strstr($link, '<a ')) {
# If there is no '<a>'-tag, the 'disabled' class is broken - set a fake one.
$link = Support::wrapTag('<a href="#">', $link);
}
}
break;
case '===':
// Header
$link = substr($link, 3);
$attribute = ' class="dropdown-header"';
break;
default:
break;
}
// Menu entries
$li .= '<li' . $attribute . '>' . $link . '</li>';
}
// Wrapped Menu entries
$ul = Support::wrapTag('<ul style="max-height: 70vh; overflow-y: auto" class="dropdown-menu" aria-labelledby="' . $htmlId . '">', $li);
}
return Support::wrapTag('<span class="dropdown">', $dropdownSymbol . $ul);
}
/**
* @param string $dropdownStr
* @param string $htmlId
* @param $rcFlagMenuEnabled
* @param array $menuEntryStrArr
* @param $htmlId
* @return string
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
* @throws \UserReportException
*/
private function renderDropdownSymbol($dropdownStr, $htmlId, &$rcFlagMenuEnabled) {
$class = '';
$paramArr = KeyValueStringParser::parse($dropdownStr, PARAM_TOKEN_DELIMITER, PARAM_DELIMITER);
foreach ([TOKEN_URL, TOKEN_MAIL, TOKEN_PAGE, TOKEN_DOWNLOAD, TOKEN_COPY_TO_CLIPBOARD] as $token) {
if (isset($paramArr[$token])) {
throw new \UserReportException("Dropdown / broken definition: Token '$token' can't be part of " . TOKEN_DROPDOWN, ERROR_INVALID_VALUE);
}
}
// All render mode <3 means: enabled
$rcFlagMenuEnabled = ($paramArr[TOKEN_RENDER] ?? 0) < 3;
// Misuse the link class to render the menu symbol: this gives tooltip, glyph, button, text ... - set render mode to '3' = 'no link'
$paramArr[TOKEN_RENDER] = '3';
if (!isset($paramArr[TOKEN_GLYPH]) || $paramArr[TOKEN_GLYPH] == '1') {
$paramArr[TOKEN_GLYPH] = 'glyphicon-option-vertical';
}
if (isset($paramArr[TOKEN_BOOTSTRAP_BUTTON])) {
$vars = $this->buildBootstrapButton(array(), $paramArr[TOKEN_BOOTSTRAP_BUTTON]);
$class = ' ' . ($vars[NAME_BOOTSTRAP_BUTTON] ?? '');
}
if (!isset($paramArr[TOKEN_ATTRIBUTE])) {
$paramArr[TOKEN_ATTRIBUTE] = 'data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"';
}
private function renderDropdownUl(array $menuEntryStrArr, $htmlId) {
$li = '';
if (!$rcFlagMenuEnabled) {
$class .= ' disabled';
// In case there is no button: set the text and glyphicon to 'muted'
if (($vars[NAME_BOOTSTRAP_BUTTON] ?? '') === '') {
foreach ($menuEntryStrArr as $str) {
$attribute = '';
$link = $this->renderLink($str);
switch (substr($link, 0, 3)) {
case '---':
$link = substr($link, 3);
if ($link == '') {
# Separator
$attribute = ' role="separator" class="divider"';
} else {
# Disabled
$attribute = ' class="disabled"';
if (false === strstr($link, '<a ')) {
# If there is no '<a>'-tag, the 'disabled' class is broken - set a fake one.
$link = Support::wrapTag('<a href="#">', $link);
}
}
break;
if (($paramArr[TOKEN_TEXT] ?? '') != '') {
$paramArr[TOKEN_TEXT] = Support::wrapTag('<span class="text-muted">', $paramArr[TOKEN_TEXT]);
}
case '===':
// Header
$link = substr($link, 3);
$attribute = ' class="dropdown-header"';
break;
if (($paramArr[TOKEN_GLYPH] ?? '') != '') {
$paramArr[TOKEN_GLYPH] .= ' text-muted';
}
default:
break;
}
// Menu entries
$li .= '<li' . $attribute . '>' . $link . '</li>';
}
$paramArr[TOKEN_CLASS] .= 'dropdown-toggle' . $class;
$paramArr[TOKEN_ATTRIBUTE] .= ' class="dropdown-toggle' . $class . '"';
$paramArr[TOKEN_ATTRIBUTE] .= ' id="' . $htmlId . '"';
$str = KeyValueStringParser::unparse($paramArr, PARAM_TOKEN_DELIMITER, PARAM_DELIMITER);
return $this->renderLink($str, '', LINK_SPECIAL_MODE_DROPDOWN);
}
/**
* @param $str
* @return string
* @throws \UserFormException
* @throws \UserReportException
*/
public function processRestClient($str) {
// Wrapped Menu entries
return Support::wrapTag('<ul style="max-height: 70vh; overflow-y: auto" class="dropdown-menu" aria-labelledby="' . $htmlId . '">', $li);
}
/**
......@@ -636,7 +508,6 @@ class 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.
* @param string $specialMode '', 'dropdown'
*
* @return string The complete HTML encoded Link like
* <a href='http://example.com' class='external'><img src='icon.gif' title='help text'>Description</a>
......@@ -645,21 +516,24 @@ class Link {
* @throws \UserFormException
* @throws \UserReportException
*/
public function renderLink($str, $strDefault = '', $specialMode = '') {
public function renderLink($str, $strDefault = '') {
$tokenGiven = array();
$link = "";
$ddHtmlId = '';
$rcMenuEntryStrArr = array();
$vars = $this->initVars();
if (empty($str)) {
return '';
}
// Special cases
switch ($str[0] ?? '') {
case TOKEN_DROPDOWN:
// Check for dropdown menu
// $str = $this->processDropdown($str, $rcMenuEntries);
// $specialMode = LINK_SPECIAL_MODE_DROPDOWN;
return $this->processDropdown($str);
$ddHtmlId = Support::uniqIdQfq('dd_');
// Parse and split $str to '$rcMenuEntryStrArr' and 'remaining button'
$str = $this->processDropdown($str, $rcMenuEntryStrArr);
break;
case TOKEN_WEBSOCKET:
......@@ -673,14 +547,38 @@ class Link {
break;
}
$vars = $this->fillParameter($str, $tokenGiven, $strDefault);
// General processing
$vars = $this->fillParameter($vars, $str, $tokenGiven, $strDefault);
$vars = $this->processParameter($vars, $tokenGiven);
$mode = $this->getModeRender($vars, $tokenGiven, $specialMode);
$mode = $this->getModeRender($vars, $tokenGiven);
if (isset($tokenGiven[TOKEN_DOWNLOAD]) && $tokenGiven[TOKEN_DOWNLOAD] === true) {
$this->store->setVar(SYSTEM_DOWNLOAD_POPUP, DOWNLOAD_POPUP_REQUEST, STORE_SYSTEM);
}
if (($vars[NAME_DROPDOWN] ?? '') == '1') {
$ul = '';
// Render menu items only if the menu is active.
if ($vars[NAME_RENDER] == 0) {
$ul = $this->renderDropdownUl($rcMenuEntryStrArr, $ddHtmlId);
}
// Tooltip and attributes
$attributes = Support::doAttribute('title', $vars[FINAL_TOOL_TIP]);
if ($vars[NAME_ATTRIBUTE] != '') {
$attributes .= $vars[NAME_ATTRIBUTE] . ' ';
}
// Bootstrap or plain
if ($vars[NAME_BOOTSTRAP_BUTTON] == '0') {
$tag = '<span class="dropdown" ' . $attributes . '>';
} else {
$tag = '<div class="btn-group" ' . $attributes . '>';
}
return Support::wrapTag($tag, $vars[FINAL_CONTENT] . $ul);
}
// 0-6 URL, plain email
// 10-14 encrypted email
// 20-24 delete / ajax
......@@ -694,13 +592,13 @@ class Link {
// 1: 'text'
case '1':
case '11':
$link = $this->wrapLinkTextOnly($vars, FINAL_CONTENT);
$link = $this->wrapLinkTextOnly($vars, FINAL_CONTENT);
break;
// 2: 'url'
case '2':
case '12':
$link = $this->wrapLinkTextOnly($vars, FINAL_HREF);
$link = $this->wrapLinkTextOnly($vars, FINAL_HREF);
break;
// 3: <a href=url ...>url</a>
......@@ -740,17 +638,6 @@ class Link {
case '26':
throw new \UserReportException ("Mode not implemented. internal render mode=$mode", ERROR_UNKNOWN_MODE);
break;
case '7':
switch ($specialMode) {
case LINK_SPECIAL_MODE_DROPDOWN:
$link = $this->wrapDropdown($vars);
break;
default:
throw new \UserReportException ("Unknown specialMode=$specialMode", ERROR_UNKNOWN_MODE);
break;
}
break;
case '8':
$link = substr($vars[FINAL_HREF], -SIP_TOKEN_LENGTH); // get only the last 13 characters (the sip)
break;
......@@ -824,6 +711,7 @@ class Link {
/**
* Iterate over all given token. Check for double definition.
*
* @param $vars
* @param string $str
* @param array $rcTokenGiven - return an array with found token.
*
......@@ -834,11 +722,14 @@ class Link {
* @throws \UserFormException
* @throws \UserReportException
*/
public function fillParameter($str, array &$rcTokenGiven, $strDefault = '') {
public function fillParameter($vars, $str, array &$rcTokenGiven, $strDefault = '') {
$rcTokenGiven = array();
// Define all possible vars: no more isset().
$vars = $this->initVars();
if ($vars == array()) {
// Define all possible vars: no more isset().
$vars = $this->initVars();
}
$flagArray = array();
$items = $this->paramPreparation($str, $strDefault);
......@@ -923,6 +814,11 @@ class Link {
$vars = $this->buildCopyToClipboardLate($vars);
}
// CopyToClipboard (Download) Link needs some extra work
if ($rcTokenGiven[TOKEN_DROPDOWN] ?? false) {
$vars = $this->buildDropdownLate($vars);
}
// Check for special default setting.
if ($vars[NAME_SIP] === false) {
$vars[NAME_SIP] = "0";
......@@ -985,7 +881,6 @@ class Link {
FINAL_QUESTION => '',
FINAL_THUMBNAIL => '',
];
}
/**
......@@ -1365,13 +1260,13 @@ class Link {
*
* @param array $vars
*
* @param $contentPure
* @param $rcContentPure
* @return string
* @throws \CodeException
* @throws \UserFormException
* @throws \UserReportException
*/
private function doContent(array $vars, &$contentPure) {
private function doContent(array $vars, &$rcContentPure) {
$arr = array();
if ($vars[NAME_MONITOR] == '1') {
......@@ -1386,8 +1281,8 @@ class Link {
}
}
$contentPure = implode(' ', $arr);
return Support::wrapTag($vars[NAME_EXTRA_CONTENT_WRAP], $contentPure);
$rcContentPure = implode(' ', $arr);
return Support::wrapTag($vars[NAME_EXTRA_CONTENT_WRAP], $rcContentPure);
}
/**
......@@ -1508,7 +1403,7 @@ EOF;
* @return string
* @throws \UserReportException
*/
private function getModeRender(array $vars, array $tokenGiven, $specialMode) {
private function getModeRender(array $vars, array $tokenGiven) {
if (isset($tokenGiven[TOKEN_COPY_TO_CLIPBOARD]) && $tokenGiven[TOKEN_COPY_TO_CLIPBOARD]) {
if ($vars[NAME_RENDER] == '0') {
......@@ -1516,10 +1411,6 @@ EOF;
}
}
if ($specialMode != '') {
return 7; // this is not r:7, it's meaning of $this->renderControl. A soon as there is a specialMode given, skip the rest.
}
if ($vars[NAME_MONITOR] == '1') {
return 1;
}
......@@ -2038,7 +1929,6 @@ EOF;
* @return array
*/
private function buildHelp($vars, $value) {
$vars[NAME_GLYPH] = GLYPH_ICON_HELP;
$vars[NAME_GLYPH_TITLE] = "Help";
$vars[NAME_LINK_CLASS_DEFAULT] = NO_CLASS;
......@@ -2199,4 +2089,43 @@ EOF;
return $vars;
}
/**
* @param array $vars
* @param $value
* @return array
*/
private function buildDropdown(array $vars, $value) {
return $vars;
}
/**
* $vars[NAME_EXTRA_CONTENT_WRAP]:
* Bootstrap: <button type="button" class="dropdown-toggle btn btn-default" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
* Bootstrap disabled: <button type="button" class="dropdown-toggle disabled btn btn-default" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
* Plain: <span class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
* Plain disabled: <span class="dropdown-toggle disabled" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
*
* @param array $vars
* @param $value
* @return array
*/
private function buildDropdownLate(array $vars) {
if ($vars[NAME_GLYPH] == '') {
$vars[NAME_GLYPH] = GLYPH_ICON_OPTION_VERTICAL;
}
$class = "dropdown-toggle";
if ($vars[NAME_RENDER] == '3') {
$class .= ' disabled';
}
$attributes = 'data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"';
if ($vars[NAME_BOOTSTRAP_BUTTON] == '0') {
$vars[NAME_EXTRA_CONTENT_WRAP] = '<span class="' . $class . '" ' . $attributes . '>';
} else {
$vars[NAME_EXTRA_CONTENT_WRAP] = '<button type="button" class="' . $class . ' btn btn-default" ' . $attributes . '>';
}
return $vars;
}
}
\ No newline at end of file
......@@ -1111,7 +1111,7 @@ class Report {
case COLUMN_SAVE_PDF:
$tokenGiven = [];
$vars = $this->link->fillParameter($columnValue, $tokenGiven);
$vars = $this->link->fillParameter(array(), $columnValue, $tokenGiven);
$vars[DOWNLOAD_MODE] = DOWNLOAD_MODE_PDF;
$vars[SIP_DOWNLOAD_PARAMETER] = implode(PARAM_DELIMITER, $vars[NAME_COLLECT_ELEMENTS]);
......
......@@ -109,20 +109,20 @@ class LinkTest extends TestCase {
// Empty definition
$expect = $args;
$result = $link->fillParameter('', $rcTokenGiven);
$result = $link->fillParameter(array(), '', $rcTokenGiven);
$this->assertEquals($expect, $result);
// 1 Parameter
$expect = $args;
$expect['page'] = '?id=page1';
$result = $link->fillParameter('p:page1', $rcTokenGiven);
$result = $link->fillParameter(array(), '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');
$result = $link->fillParameter(array(), 'p:page1', $rcTokenGiven, 't:comment');
$this->assertEquals($expect, $result);
// 1 Parameter, 1 Default
......@@ -130,7 +130,7 @@ class LinkTest extends TestCase {
$expect['page'] = '?id=page1';
$expect['text'] = 'comment';
$expect['sip'] = '1';
$result = $link->fillParameter('p:page1|t:comment', $rcTokenGiven, 'p:page1|t:comment|s');
$result = $link->fillParameter(array(), 'p:page1|t:comment', $rcTokenGiven, 'p:page1|t:comment|s');
$this->assertEquals($expect, $result);
$expect = $args;
......@@ -144,12 +144,12 @@ class LinkTest extends TestCase {
$expect['sip'] = '1';
$expect['linkClassDefault'] = 'no_class';
$expect['_exportFilename'] = 'download.pdf';
$result = $link->fillParameter('p:page1|t:text', $rcTokenGiven, 'd:download.pdf');
$result = $link->fillParameter(array(), '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');
$result = $link->fillParameter(array(), 'p:page1|t:text|d:download.pdf', $rcTokenGiven, 's|M:file|d|F:file.pdf');
$this->assertEquals($expect, $result);
}
......@@ -1614,51 +1614,100 @@ EOF;