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||...'
$paramArr = KeyValueStringParser::explodeEscape(PARAM_DELIMITER, $str);
// 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 . '||');
// 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,21 +401,22 @@ class Link {
* </div>
* End
*
* @param string $dropdownSymbol
* @param array $menuEntryLinkArr
* @param string $htmlId
* @param bool $flagMenuEnabled
* @param array $menuEntryStrArr
* @param $htmlId
* @return string
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
* @throws \UserReportException
*/
private function renderDropdown(string $dropdownSymbol, array $menuEntryLinkArr, $htmlId, $flagMenuEnabled) {
$ul = '';
private function renderDropdownUl(array $menuEntryStrArr, $htmlId) {
$li = '';
if ($flagMenuEnabled) {
foreach ($menuEntryLinkArr as $link) {
foreach ($menuEntryStrArr as $str) {
$attribute = '';
$link = $this->renderLink($str);
switch (substr($link, 0, 3)) {
case '---':
$link = substr($link, 3);
......@@ -499,83 +445,9 @@ class Link {
// 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
* @throws \CodeException
* @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"';
}
if (!$rcFlagMenuEnabled) {
$class .= ' disabled';
// In case there is no button: set the text and glyphicon to 'muted'
if (($vars[NAME_BOOTSTRAP_BUTTON] ?? '') === '') {
if (($paramArr[TOKEN_TEXT] ?? '') != '') {
$paramArr[TOKEN_TEXT] = Support::wrapTag('<span class="text-muted">', $paramArr[TOKEN_TEXT]);
}
if (($paramArr[TOKEN_GLYPH] ?? '') != '') {
$paramArr[TOKEN_GLYPH] .= ' text-muted';
}
}
}
$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
......@@ -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();
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;
* @throws \UserFormException
* @throws \UserReportException
*/
public function testLinkDropdown() {
public function testLinkDropdownPlain() {
$link = new Link($this->sip, DB_INDEX_DEFAULT, true);
// Empty definition
$expect = '<span class="dropdown"><span title="Details" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" class="dropdown-toggle" id="badcaffee1234" ><span class="glyphicon glyphicon-option-vertical" ></span></span><ul style="max-height: 70vh; overflow-y: auto" class="dropdown-menu" aria-labelledby="badcaffee1234"></ul></span>';
$expect = '<span class="dropdown" ><span class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-option-vertical" ></span></span><ul style="max-height: 70vh; overflow-y: auto" class="dropdown-menu" aria-labelledby="badcaffee1234"></ul></span>';
$result = $link->renderLink('z|b:0');
$this->assertEquals($expect, $result);
// Empty definition, G:1 (default glyph)
$expect = '<div class="btn-group" title="Details" ><button type="button" class="dropdown-toggle btn btn-default" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span class="glyphicon 1" ></span> Menu</button><ul style="max-height: 70vh; overflow-y: auto" class="dropdown-menu" aria-labelledby="badcaffee1234"></ul></div>';
$result = $link->renderLink('z|t:Menu|G:1|o:Details');
$this->assertEquals($expect, $result);
// Dropdown menu with text, no glyph, tooltip.
$expect = '<span class="dropdown" title="Open menu" ><span class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Menu</span><ul style="max-height: 70vh; overflow-y: auto" class="dropdown-menu" aria-labelledby="badcaffee1234"></ul></span>';
$result = $link->renderLink('z|t:Menu|b:0|G:0|o:Open menu');
$this->assertEquals($expect, $result);
// Dropdown menu with text, custom Glyph and a tooltip.
$expect = '<span class="dropdown" title="Open menu" ><span class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyph-icon-left" ></span> Menu</span><ul style="max-height: 70vh; overflow-y: auto" class="dropdown-menu" aria-labelledby="badcaffee1234"></ul></span>';
$result = $link->renderLink('z|t:Menu|b:0|G:glyph-icon-left|o:Open menu');
$this->assertEquals($expect, $result);
// Dropdown menu with menu entry.
$expect = '<span class="dropdown" title="Open menu" ><span class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyph-icon-left" ></span> Menu</span><ul style="max-height: 70vh; overflow-y: auto" class="dropdown-menu" aria-labelledby="badcaffee1234"><li><a href="?id=home" >Home</a></li></ul></span>';
$result = $link->renderLink('z|t:Menu|b:0|G:glyph-icon-left|o:Open menu||p:home|t:Home');
$this->assertEquals($expect, $result);
// Dropdown menu disabled.
$expect = '<span class="dropdown" title="Open menu" ><span class="dropdown-toggle disabled" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyph-icon-left" ></span> Menu</span></span>';
$result = $link->renderLink('z|t:Menu|b:0|G:glyph-icon-left|o:Open menu|r:3||p:home|t:Home');
$this->assertEquals($expect, $result);
// Dropdown menu with two entries, one is SIP encoded.
$expect = '<span class="dropdown" title="Open menu" ><span class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyph-icon-left" ></span> Menu</span><ul style="max-height: 70vh; overflow-y: auto" class="dropdown-menu" aria-labelledby="badcaffee1234"><li><a href="index.php?id=home&s=badcaffee1234" >Home</a></li><li><a href="?id=back" title="Navigate back" >Back</a></li></ul></span>';
$result = $link->renderLink('z|t:Menu|b:0|G:glyph-icon-left|o:Open menu||p:home|t:Home|s||p:back|t:Back|o:Navigate back');
$this->assertEquals($expect, $result);
// Dropdown menu header, separator and disabled entry.
$expect = '<span class="dropdown" title="Open menu" ><span class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyph-icon-left" ></span> Menu</span><ul style="max-height: 70vh; overflow-y: auto" class="dropdown-menu" aria-labelledby="badcaffee1234"><li><a href="index.php?id=home&s=badcaffee1234" >Home</a></li><li><a href="?id=back" title="Navigate back" >Back</a></li><li role="separator" class="divider"></li><li class="dropdown-header">Header</li><li class="disabled"><a href="#">Disabled entry</a></li></ul></span>';
$result = $link->renderLink('z|t:Menu|b:0|G:glyph-icon-left|o:Open menu||p:home|t:Home|s||p:back|t:Back|o:Navigate back||r:1|t:---||t:===Header|r:1||r:1|t:---Disabled entry');
$this->assertEquals($expect, $result);
}
/**
* @throws \CodeException
* @throws \UserFormException
* @throws \UserReportException
*/
public function testLinkDropdownBootstrap() {
$link = new Link($this->sip, DB_INDEX_DEFAULT, true);
// Empty definition
$expect = '<div class="btn-group" ><button type="button" class="dropdown-toggle btn btn-default" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-option-vertical" ></span></button><ul style="max-height: 70vh; overflow-y: auto" class="dropdown-menu" aria-labelledby="badcaffee1234"></ul></div>';