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'; ...@@ -931,7 +931,7 @@ const GLYPH_ICON_DUPLICATE = 'glyphicon-duplicate';
const GLYPH_ICON_VIEW = 'glyphicon-eye-open'; const GLYPH_ICON_VIEW = 'glyphicon-eye-open';
const GLYPH_ICON_FILE = 'glyphicon-file'; const GLYPH_ICON_FILE = 'glyphicon-file';
const GLYPH_ICON_COPY = 'glyphicon-copy'; const GLYPH_ICON_COPY = 'glyphicon-copy';
const GLYPH_ICON_OPTION_VERTICAL = 'glyphicon-option-vertical';
// FORM columns: real // FORM columns: real
const F_ID = 'id'; const F_ID = 'id';
const F_NAME = 'name'; const F_NAME = 'name';
...@@ -1842,8 +1842,6 @@ const TOKEN_L_CONTENT = 'content'; ...@@ -1842,8 +1842,6 @@ const TOKEN_L_CONTENT = 'content';
const TOKEN_L_TIMEOUT = 'timeout'; const TOKEN_L_TIMEOUT = 'timeout';
const TOKEN_L_SSL = 'ssl'; const TOKEN_L_SSL = 'ssl';
const LINK_SPECIAL_MODE_DROPDOWN = 'dropdown';
const MONITOR_MODE_APPEND_0 = '0'; const MONITOR_MODE_APPEND_0 = '0';
const MONITOR_MODE_APPEND_1 = '1'; const MONITOR_MODE_APPEND_1 = '1';
const MONITOR_SESSION_FILE_SEEK = 'monitor-seek-file'; const MONITOR_SESSION_FILE_SEEK = 'monitor-seek-file';
......
...@@ -45,6 +45,7 @@ class Token { ...@@ -45,6 +45,7 @@ class Token {
case TOKEN_SIP: case TOKEN_SIP:
case TOKEN_BOOTSTRAP_BUTTON: case TOKEN_BOOTSTRAP_BUTTON:
case TOKEN_MONITOR: case TOKEN_MONITOR:
case TOKEN_DROPDOWN:
$value = '1'; $value = '1';
break; break;
case TOKEN_QUESTION: case TOKEN_QUESTION:
......
...@@ -325,36 +325,6 @@ class Link { ...@@ -325,36 +325,6 @@ class Link {
$attributes .= Support::doAttribute('class', [$vars[NAME_BOOTSTRAP_BUTTON], 'disabled']); $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); return Support::wrapTag("<span $attributes>", $text);
} }
...@@ -366,39 +336,28 @@ class Link { ...@@ -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. * '||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. * '||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 * @return string
* @throws \CodeException
* @throws \UserFormException
* @throws \UserReportException
*/ */
private function processDropdown($str) { private function processDropdown($str, array &$rcMenuEntryStrArr) {
$menuEntryStrArr = array(); $rcMenuEntryStrArr = array();
$menuEntryLinkArr = array();
$tokenCollect = 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||...' // Iterate over token. Find delimiter to separate dropdown definition and all individual menu entries.
$paramArr = KeyValueStringParser::explodeEscape(PARAM_DELIMITER, $str);
// Iterate over token. Find delimiter to separate dropdown definition and all menu entries.
foreach ($paramArr as $tokenStr) { foreach ($paramArr as $tokenStr) {
$tokenArr = explode(PARAM_TOKEN_DELIMITER, $tokenStr, 2); $tokenArr = explode(PARAM_TOKEN_DELIMITER, $tokenStr, 2);
switch ($tokenArr[0] ?? '') { 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. // Indicator to start menu entry: force a flush of existing token and start a new round.
case '': case '':
// New menu entry. // New menu entry.
if (!empty($tokenCollect)) { if (!empty($tokenCollect)) {
$menuEntryStrArr[] = implode(PARAM_DELIMITER, $tokenCollect); $rcMenuEntryStrArr[] = implode(PARAM_DELIMITER, $tokenCollect);
} }
$tokenCollect = array(); $tokenCollect = array();
// $tokenCollect[] = $tokenStr;
break; break;
default: default:
...@@ -406,24 +365,10 @@ class Link { ...@@ -406,24 +365,10 @@ class Link {
} }
} }
// Flush remaining element. // First entry is the dropdown button, all others are the menu entries
$menuEntryStrArr[] = implode(PARAM_DELIMITER, $tokenCollect); $dropdownButtonStr = array_shift($rcMenuEntryStrArr);
// 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);
return $dropdownButtonStr;
} }
/** /**
...@@ -456,126 +401,53 @@ class Link { ...@@ -456,126 +401,53 @@ class Link {
* </div> * </div>
* End * End
* *
* @param string $dropdownSymbol * @param array $menuEntryStrArr
* @param array $menuEntryLinkArr * @param $htmlId
* @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
* @return string * @return string
* @throws \CodeException * @throws \CodeException
* @throws \DbException
* @throws \UserFormException * @throws \UserFormException
* @throws \UserReportException * @throws \UserReportException
*/ */
private function renderDropdownSymbol($dropdownStr, $htmlId, &$rcFlagMenuEnabled) { private function renderDropdownUl(array $menuEntryStrArr, $htmlId) {
$class = ''; $li = '';
$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"';
}
if (!$rcFlagMenuEnabled) { foreach ($menuEntryStrArr as $str) {
$class .= ' disabled'; $attribute = '';
// In case there is no button: set the text and glyphicon to 'muted'
if (($vars[NAME_BOOTSTRAP_BUTTON] ?? '') === '') { $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] ?? '') != '') { case '===':
$paramArr[TOKEN_TEXT] = Support::wrapTag('<span class="text-muted">', $paramArr[TOKEN_TEXT]); // Header
} $link = substr($link, 3);
$attribute = ' class="dropdown-header"';
break;
if (($paramArr[TOKEN_GLYPH] ?? '') != '') { default:
$paramArr[TOKEN_GLYPH] .= ' text-muted'; break;
}
} }
// Menu entries
$li .= '<li' . $attribute . '>' . $link . '</li>';
} }
$paramArr[TOKEN_CLASS] .= 'dropdown-toggle' . $class; // Wrapped Menu entries
$paramArr[TOKEN_ATTRIBUTE] .= ' class="dropdown-toggle' . $class . '"'; return Support::wrapTag('<ul style="max-height: 70vh; overflow-y: auto" class="dropdown-menu" aria-labelledby="' . $htmlId . '">', $li);
$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) {
} }
/** /**
...@@ -636,7 +508,6 @@ class Link { ...@@ -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 $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 $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 * @return string The complete HTML encoded Link like
* <a href='http://example.com' class='external'><img src='icon.gif' title='help text'>Description</a> * <a href='http://example.com' class='external'><img src='icon.gif' title='help text'>Description</a>
...@@ -645,21 +516,24 @@ class Link { ...@@ -645,21 +516,24 @@ class Link {
* @throws \UserFormException * @throws \UserFormException
* @throws \UserReportException * @throws \UserReportException
*/ */
public function renderLink($str, $strDefault = '', $specialMode = '') { public function renderLink($str, $strDefault = '') {
$tokenGiven = array(); $tokenGiven = array();
$link = ""; $link = "";
$ddHtmlId = '';
$rcMenuEntryStrArr = array();
$vars = $this->initVars();
if (empty($str)) { if (empty($str)) {
return ''; return '';
} }
// Special cases
switch ($str[0] ?? '') { switch ($str[0] ?? '') {
case TOKEN_DROPDOWN: case TOKEN_DROPDOWN:
// Check for dropdown menu $ddHtmlId = Support::uniqIdQfq('dd_');
// $str = $this->processDropdown($str, $rcMenuEntries); // Parse and split $str to '$rcMenuEntryStrArr' and 'remaining button'
// $specialMode = LINK_SPECIAL_MODE_DROPDOWN; $str = $this->processDropdown($str, $rcMenuEntryStrArr);
return $this->processDropdown($str);
break; break;
case TOKEN_WEBSOCKET: case TOKEN_WEBSOCKET:
...@@ -673,14 +547,38 @@ class Link { ...@@ -673,14 +547,38 @@ class Link {
break; break;
} }
$vars = $this->fillParameter($str, $tokenGiven, $strDefault); // General processing
$vars = $this->fillParameter($vars, $str, $tokenGiven, $strDefault);
$vars = $this->processParameter($vars, $tokenGiven); $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) { if (isset($tokenGiven[TOKEN_DOWNLOAD]) && $tokenGiven[TOKEN_DOWNLOAD] === true) {
$this->store->setVar(SYSTEM_DOWNLOAD_POPUP, DOWNLOAD_POPUP_REQUEST, STORE_SYSTEM); $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 // 0-6 URL, plain email
// 10-14 encrypted email // 10-14 encrypted email
// 20-24 delete / ajax // 20-24 delete / ajax
...@@ -694,13 +592,13 @@ class Link { ...@@ -694,13 +592,13 @@ class Link {
// 1: 'text' // 1: 'text'
case '1': case '1':
case '11': case '11':
$link = $this->wrapLinkTextOnly($vars, FINAL_CONTENT); $link = $this->wrapLinkTextOnly($vars, FINAL_CONTENT);
break; break;
// 2: 'url' // 2: 'url'
case '2': case '2':
case '12': case '12':
$link = $this->wrapLinkTextOnly($vars, FINAL_HREF); $link = $this->wrapLinkTextOnly($vars, FINAL_HREF);
break; break;
// 3: <a href=url ...>url</a> // 3: <a href=url ...>url</a>
...@@ -740,17 +638,6 @@ class Link { ...@@ -740,17 +638,6 @@ class Link {
case '26': case '26':
throw new \UserReportException ("Mode not implemented. internal render mode=$mode", ERROR_UNKNOWN_MODE); throw new \UserReportException ("Mode not implemented. internal render mode=$mode", ERROR_UNKNOWN_MODE);
break; 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': case '8':
$link = substr($vars[FINAL_HREF], -SIP_TOKEN_LENGTH); // get only the last 13 characters (the sip) $link = substr($vars[FINAL_HREF], -SIP_TOKEN_LENGTH); // get only the last 13 characters (the sip)
break; break;
...@@ -824,6 +711,7 @@ class Link { ...@@ -824,6 +711,7 @@ class Link {
/** /**
* Iterate over all given token. Check for double definition. * Iterate over all given token. Check for double definition.
* *
* @param $vars
* @param string $str * @param string $str
* @param array $rcTokenGiven - return an array with found token. * @param array $rcTokenGiven - return an array with found token.
* *
...@@ -834,11 +722,14 @@ class Link { ...@@ -834,11 +722,14 @@ class Link {
* @throws \UserFormException * @throws \UserFormException
* @throws \UserReportException * @throws \UserReportException
*/ */
public function fillParameter($str, array &$rcTokenGiven, $strDefault = '') { public function fillParameter($vars, $str, array &$rcTokenGiven, $strDefault = '') {
$rcTokenGiven = array(); $rcTokenGiven = array();
// Define all possible vars: no more isset(). if ($vars == array()) {
$vars = $this->initVars(); // Define all possible vars: no more isset().
$vars = $this->initVars();
}
$flagArray = array(); $flagArray = array();
$items = $this->paramPreparation($str, $strDefault); $items = $this->paramPreparation($str, $strDefault);
...@@ -923,6 +814,11 @@ class Link { ...@@ -923,6 +814,11 @@ class Link {
$vars = $this->buildCopyToClipboardLate($vars); $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. // Check for special default setting.
if ($vars[NAME_SIP] === false) { if ($vars[NAME_SIP] === false) {
$vars[NAME_SIP] = "0"; $vars[NAME_SIP] = "0";
...@@ -985,7 +881,6 @@ class Link { ...@@ -985,7 +881,6 @@ class Link {
FINAL_QUESTION => '', FINAL_QUESTION => '',
FINAL_THUMBNAIL => '', FINAL_THUMBNAIL => '',
]; ];
} }
/** /**
...@@ -1365,13 +1260,13 @@ class Link { ...@@ -1365,13 +1260,13 @@ class Link {
* *
* @param array $vars * @param array $vars
* *
* @param $contentPure * @param $rcContentPure
* @return string * @return string
* @throws \CodeException * @throws \CodeException
* @throws \UserFormException * @throws \UserFormException
* @throws \UserReportException * @throws \UserReportException
*/ */
private function doContent(array $vars, &$contentPure) { private function doContent(array $vars, &$rcContentPure) {
$arr = array(); $arr = array();
if ($vars[NAME_MONITOR] == '1') { if ($vars[NAME_MONITOR] == '1') {
...@@ -1386,8 +1281,8 @@ class Link { ...@@ -1386,8 +1281,8 @@ class Link {
} }
} }
$contentPure = implode(' ', $arr); $rcContentPure = implode(' ', $arr);
return Support::wrapTag($vars[NAME_EXTRA_CONTENT_WRAP], $contentPure); return Support::wrapTag($vars[NAME_EXTRA_CONTENT_WRAP], $rcContentPure);
} }
/** /**
...@@ -1508,7 +1403,7 @@ EOF; ...@@ -1508,7 +1403,7 @@ EOF;
* @return string * @return string
* @throws \UserReportException * @throws \UserReportException
*/ */
private function getModeRender(array $vars, array $tokenGiven, $specialMode) { private function getModeRender(array $vars, array $tokenGiven) {