BxBaseMenu.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) UNA, Inc - https://una.io
  4. * MIT License - https://opensource.org/licenses/MIT
  5. *
  6. * @defgroup UnaBaseView UNA Base Representation Classes
  7. * @{
  8. */
  9. require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
  10. /**
  11. * Menu representation.
  12. * @see BxDolMenu
  13. */
  14. class BxBaseMenu extends BxDolMenu
  15. {
  16. protected $_oTemplate;
  17. protected $_iPageType;
  18. protected $_aOptionalParams = array('target' => '', 'onclick' => '');
  19. protected $_bDisplayAddons = false;
  20. protected $_aSelected; // Selected menu item.
  21. /**
  22. * Menus drawn as tabs, by template or by a menu's own flag: picking one of their items plays Cuelume's select
  23. * (interface sounds, see bx_sound).
  24. */
  25. protected $_aTemplatesCueSelect = ['menu_main_submenu.html', 'menu_main_submenu_more_auto.html', 'menu_block_submenu_hor.html', 'menu_block_submenu_ver.html', 'menu_feeds.html'];
  26. protected $_bCueSelect = false;
  27. public function __construct ($aObject, $oTemplate)
  28. {
  29. parent::__construct ($aObject);
  30. if ($oTemplate)
  31. $this->_oTemplate = $oTemplate;
  32. else
  33. $this->_oTemplate = BxDolTemplate::getInstance();
  34. $this->_iPageType = false;
  35. $this->_aSelected = [];
  36. }
  37. public function getDisplayAddons()
  38. {
  39. return $this->_bDisplayAddons;
  40. }
  41. public function setDisplayAddons($b)
  42. {
  43. $bRet = $this->_bDisplayAddons;
  44. $this->_bDisplayAddons = $b;
  45. return $bRet;
  46. }
  47. /**
  48. * Get menu code.
  49. * @return string
  50. */
  51. public function getCode ()
  52. {
  53. $sMenuTitle = isset($this->_aObject['object']) ? $this->_aObject['object'] : 'Menu-' . rand(0, PHP_INT_MAX);
  54. if (isset($GLOBALS['bx_profiler']) && $GLOBALS['bx_profiler']->isMenu($sMenuTitle))
  55. $sMenuTitle .= rand(0, PHP_INT_MAX);
  56. if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->beginMenu($sMenuTitle);
  57. $sRet = '';
  58. $bCacheUsed = false;
  59. $sCacheKey = '';
  60. $mixedCache = null;
  61. // Use cache only if:
  62. // - cache mode is set, AND
  63. // - it's not disabled (`off`), AND
  64. // - it's not "guest cache" while a user is logged in
  65. if (isset($this->_aObject['cache']) && !($this->_aObject['cache'] == 'off' || ($this->_aObject['cache'] == 'guest' && isLogged()))) {
  66. switch ($this->_aObject['cache']) {
  67. case 'global': // one cache for everyone
  68. case 'guest': // one cache for guests only
  69. $sCacheKey = "menu_{$this->_sObject}";
  70. break;
  71. case 'per_user': // separate cache for each user
  72. $sCacheKey = "menu_{$this->_sObject}_p" . bx_get_logged_profile_id();
  73. break;
  74. case 'per_acl': // separate cache for each ACL
  75. $aAcl = BxDolAcl::getInstance()->getMemberMembershipInfo(bx_get_logged_profile_id());
  76. $sCacheKey = "menu_{$this->_sObject}" . "_acl" . $aAcl['id'] . '_a' . (isAdmin() ? 1 : 0);
  77. break;
  78. default:
  79. trigger_error("Unknown cache mode for menu ({$this->_sObject}): {$this->_aObject['cache']}", E_USER_ERROR);
  80. break;
  81. }
  82. $mixedCache = bx_content_cache_get($sCacheKey);
  83. }
  84. if ($mixedCache !== null) {
  85. if ($mixedCache)
  86. $this->_addJsCss();
  87. $bCacheUsed = true;
  88. $sRet = $mixedCache;
  89. }
  90. else {
  91. /**
  92. * @hooks
  93. * @hookdef hook-menu-get_code_before 'menu', 'get_code_before' - hook before menu output
  94. * - $unit_name - equals `menu`
  95. * - $action - equals `get_code_before`
  96. * - $object_id - not used
  97. * - $sender_id - not used
  98. * - $extra_params - array of additional params with the following array keys:
  99. * - `object_name` - menu object name
  100. * - `object_array` - menu object array
  101. * - `object` - menu object
  102. * - `override_result` - menu code
  103. * @hook @ref hook-menu-get_code_before
  104. */
  105. $mixedRes = null;
  106. bx_alert('menu', 'get_code_before', 0, 0, [
  107. 'object_name' => $this->_sObject,
  108. 'object_array' => $this->_aObject,
  109. 'object' => $this,
  110. 'override_result' => &$mixedRes,
  111. ]);
  112. if ($mixedRes !== null) {
  113. $sRet = $mixedRes;
  114. }
  115. else {
  116. if(!$this->_iPageType)
  117. $this->_iPageType = BxDolTemplate::getInstance()->getPageType();
  118. $s = '';
  119. $aVars = $this->_getTemplateVars ();
  120. if (!empty($aVars['bx_repeat:menu_items'])) {
  121. $this->_addJsCss();
  122. $s = $this->_getCode($this->_aObject['template'], $aVars);
  123. }
  124. /**
  125. * @hooks
  126. * @hookdef hook-menu-get_code_after 'menu', 'get_code_after' - hook after menu output
  127. * - $unit_name - equals `menu`
  128. * - $action - equals `get_code_after`
  129. * - $object_id - not used
  130. * - $sender_id - not used
  131. * - $extra_params - array of additional params with the following array keys:
  132. * - `object_name` - menu object name
  133. * - `object_array` - menu object array
  134. * - `object` - menu object
  135. * - `override_result` - menu code
  136. * @hook @ref hook-menu-get_code_after
  137. */
  138. $mixedRes = null;
  139. bx_alert('menu', 'get_code_after', 0, 0, [
  140. 'object_name' => $this->_sObject,
  141. 'object_array' => $this->_aObject,
  142. 'object' => $this,
  143. 'vars' => $aVars,
  144. 'override_result' => &$s,
  145. ]);
  146. $sRet = $s;
  147. }
  148. if ($sCacheKey)
  149. bx_content_cache_set($sCacheKey, $sRet);
  150. }
  151. if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->endMenu($sMenuTitle, $this->_aObject['cache'] ?? 'undefined', $bCacheUsed);
  152. return $sRet;
  153. }
  154. /**
  155. * Get menu code API.
  156. * @return array
  157. */
  158. public function getCodeAPI ()
  159. {
  160. $aItems = [];
  161. $aVars = $this->_getTemplateVars();
  162. if(!empty($aVars['bx_repeat:menu_items']))
  163. $aItems = $aVars['bx_repeat:menu_items'];
  164. return [
  165. 'object' => $this->_sObject,
  166. 'title' => _t($this->_aObject['title_public']),
  167. 'config' => $this->_aObject['config_api'],
  168. 'persistent' => $this->_aObject['persistent'],
  169. 'params' => $this->getContentParams(),
  170. 'items' => $aItems
  171. ];
  172. }
  173. /**
  174. * Get menu short code API.
  175. * @return array
  176. */
  177. public function getShortCodeAPI ()
  178. {
  179. return [
  180. 'object' => $this->_sObject,
  181. 'title' => _t($this->_aObject['title_public']),
  182. 'params' => $this->getContentParams()
  183. ];
  184. }
  185. protected function _getCode($sTmplName, $aTmplVars)
  186. {
  187. return $this->_oTemplate->parseHtmlByName($this->getTemplateName($sTmplName), $aTmplVars);
  188. }
  189. public function getCodeItem ($sName)
  190. {
  191. if(empty($sName))
  192. return '';
  193. $sCode = $this->_oTemplate->getHtml(str_replace('.html', '_item.html', $this->_aObject['template']));
  194. if(empty($sCode))
  195. return '';
  196. $mixedTmplVars = $this->getMenuItem($sName);
  197. if($mixedTmplVars === false)
  198. return '';
  199. return $this->_oTemplate->parseHtmlByContent($sCode, $mixedTmplVars);
  200. }
  201. /**
  202. * Get template variables array
  203. * @return array
  204. */
  205. protected function _getTemplateVars ()
  206. {
  207. $aMenuItems = $this->getMenuItems();
  208. $sMenuItemSelected = _t($aMenuItems && $this->_aSelected && !empty($this->_aSelected['title']) ? $this->_aSelected['title'] : '_Contents');
  209. return [
  210. 'object' => $this->_sObject,
  211. 'menu_item_selected' => $sMenuItemSelected,
  212. 'bx_repeat:menu_items' => $aMenuItems,
  213. ];
  214. }
  215. /**
  216. * Get menu items array, which is ready to pass to menu template.
  217. * @return array or false
  218. */
  219. public function getMenuItems ()
  220. {
  221. if (!isset($this->_aObject['menu_items']))
  222. $this->_aObject['menu_items'] = $this->getMenuItemsRaw ();
  223. $aItems = array();
  224. foreach ($this->_aObject['menu_items'] as $aItem) {
  225. $aItem = $this->_getMenuItem ($aItem);
  226. if($aItem !== false)
  227. $aItems[] = $aItem;
  228. }
  229. return $aItems;
  230. }
  231. /**
  232. * Get menu item array, which is ready to pass to whole menu or
  233. * single menu item template. May return false if single menu item
  234. * is requested but cannot be shown by circumstances.
  235. * @return array or false
  236. */
  237. public function getMenuItem ($sName)
  238. {
  239. if (!isset($this->_aObject['menu_items']))
  240. $this->_aObject['menu_items'] = $this->getMenuItemsRaw ();
  241. if(!empty($sName))
  242. return $this->_getMenuItem($this->_aObject['menu_items'][$sName]);
  243. }
  244. /**
  245. * Get menu items array, this is just a wrapper for DB function for make it easier to override.
  246. * It is used in @see BxBaseMenu::getMenuItems
  247. * @return array
  248. */
  249. protected function getMenuItemsRaw ()
  250. {
  251. if($this->isMultilevel())
  252. return $this->_oQuery->getMenuItemsHierarchy();
  253. else
  254. return $this->_oQuery->getMenuItems();
  255. }
  256. protected function getMenuItemsRawProfileRelated ()
  257. {
  258. $oProfile = BxDolProfile::getInstance();
  259. if(!$oProfile)
  260. return [];
  261. $aItems = $this->_oQuery->getMenuItemsBy([
  262. 'type' => 'set_name',
  263. 'set_name' => $this->_aObject['set_name']
  264. ]);
  265. $aDuplicates = $this->_oQuery->getMenuItemsBy([
  266. 'type' => 'set_name_duplicates',
  267. 'set_name' => $this->_aObject['set_name']
  268. ]);
  269. $sModule = $oProfile->getModule();
  270. $aResult = [];
  271. foreach($aItems as $aItem) {
  272. if(in_array($aItem['name'], $aDuplicates) && $aItem['module'] != $sModule)
  273. continue;
  274. $aResult[$aItem['name']] = $aItem;
  275. }
  276. return $aResult;
  277. }
  278. protected function _getMenuItem ($a)
  279. {
  280. if (!$this->_isActive($a) || !$this->_isVisible($a))
  281. return false;
  282. if ($this->_bIsApi)
  283. return $this->_getMenuItemAPI ($a);
  284. $bIsSelected = $this->_isSelected($a);
  285. if($bIsSelected)
  286. $this->_aSelected = $a;
  287. $a['object'] = $this->_sObject;
  288. $a['title'] = _t($a['title']);
  289. $a['title_attr'] = $this->_getMenuTitle($a);
  290. $a['info'] = isset($a['info']) ? _t($a['info']) : '';
  291. $this->removeMarker('addon');
  292. $a = $this->_replaceMarkers($a);
  293. if ($this->_bDisplayAddons) {
  294. $mixedAddon = $this->_getMenuAddon($a);
  295. if (!is_array($mixedAddon)) {
  296. $this->addMarkers(array('addon' => $mixedAddon));
  297. $a = $this->_replaceMarkers($a);
  298. }
  299. }
  300. $aMarkers = $this->_getMenuMarkers($a);
  301. if ($aMarkers && is_array($aMarkers)) {
  302. $this->addMarkers($aMarkers);
  303. $a = $this->_replaceMarkers($a);
  304. }
  305. list ($sIcon, $sIconUrl, $sIconA, $sIconHtml) = $this->_getMenuIcon($a);
  306. if(!isset($a['class_add']))
  307. $a['class_add'] = '';
  308. if($bIsSelected)
  309. $a['class_add'] .= ' bx-menu-tab-active';
  310. $a['class_add'] .= $this->_getVisibilityClass($a);
  311. $a['class_link'] = '';
  312. $a['link'] = isset($a['link']) ? $this->_oPermalinks->permalink($a['link']) : 'javascript:void(0);';
  313. $aTmplVarsAddon = $this->_bDisplayAddons ? $this->_getTmplVarsAddon($mixedAddon, $a) : ['addon' => '', 'addonf' => ''];
  314. $a['bx_if:addon'] = [
  315. 'condition' => $this->_bDisplayAddons && !empty($aTmplVarsAddon['addon']),
  316. 'content' => $aTmplVarsAddon
  317. ];
  318. $a['bx_if:info'] = [
  319. 'condition' => (bool)$a['info'],
  320. 'content' => [
  321. 'info' => $a['info'],
  322. ],
  323. ];
  324. // The More item of a tab row ('more-auto', BxBaseMenuMoreAuto) opens a popup, which plays its own cue.
  325. if(($this->_bCueSelect || (isset($this->_aObject['template']) && in_array($this->_aObject['template'], $this->_aTemplatesCueSelect))) && $a['name'] != 'more-auto')
  326. $a['attrs_add'] = trim((!empty($a['attrs_add']) ? $a['attrs_add'] : '') . ' data-cuelume-select');
  327. $a['attrs'] = $this->_getMenuAttrs($a);
  328. $a['attrs_wrp'] = '';
  329. if($this->_bHx && !empty($a['link']) && strpos($a['link'], 'javascript:') === false) {
  330. $this->_aHx['get'] = $a['link'];
  331. $a['attrs'] .= bx_get_htmx_attrs($this->_aHx, $this->_mHxPreload);
  332. if(!bx_is_htmx_request() && !$bIsSelected)
  333. $a['attrs_wrp'] .= bx_get_htmx_attrs([
  334. 'get' => $a['link'],
  335. 'trigger' => 'load',
  336. 'target' => '#bx-content-preload',
  337. 'swap' => 'beforeend',
  338. ]);
  339. }
  340. $a['bx_if:image'] = [
  341. 'condition' => (bool)$sIconUrl,
  342. 'content' => [
  343. 'icon_url' => $sIconUrl,
  344. 'attrs' => $this->_getMenuIconAttrs('image', $a)
  345. ],
  346. ];
  347. $a['bx_if:image_inline'] = array (
  348. 'condition' => false,
  349. 'content' => array('image' => ''),
  350. );
  351. $a['bx_if:icon'] = array (
  352. 'condition' => (bool)$sIcon,
  353. 'content' => array('icon' => $sIcon),
  354. );
  355. $a['bx_if:icon-html'] = array (
  356. 'condition' => (bool)$sIconHtml,
  357. 'content' => array('icon' => $sIconHtml),
  358. );
  359. $a['bx_if:icon-a'] = array (
  360. 'condition' => (bool)$sIconA,
  361. 'content' => array('icon-a' => $sIconA),
  362. );
  363. $a['bx_if:title'] = [
  364. 'condition' => (bool)$a['title'] && (!isset($a['icon_only']) || (int)$a['icon_only'] == 0),
  365. 'content' => [
  366. 'title' => $a['title'],
  367. 'title_attr' => $a['title_attr'],
  368. 'bx_if:info' => $a['bx_if:info'],
  369. 'bx_if:addon' => $a['bx_if:addon']
  370. ],
  371. ];
  372. $bOnClick = !empty($a['onclick']);
  373. $aOnClick = $bOnClick ? [
  374. 'onclick' => $a['onclick'],
  375. ] : [];
  376. $a['bx_if:onclick'] = [
  377. 'condition' => $bOnClick,
  378. 'content' => $aOnClick
  379. ];
  380. $aTmplVarsSubitems = array('subitems' => '');
  381. $bTmplVarsSubitems = $this->isMultilevel() && !empty($a['subitems']);
  382. if($bTmplVarsSubitems) {
  383. $sClassCollpsed = 'bx-mi-collapsed';
  384. if(($iCollapsed = $this->getUserChoiceCollapsedSubmenu($a)) !== false)
  385. $a['class_add'] .= $iCollapsed ? ' ' . $sClassCollpsed : '';
  386. else if(isset($a['collapsed']) && $a['collapsed'])
  387. $a['class_add'] .= ' ' . $sClassCollpsed;
  388. $aSubitems = array();
  389. foreach($a['subitems'] as $aSubitem) {
  390. $aSubitem = $this->_getMenuItem($aSubitem);
  391. if($aSubitem !== false)
  392. $aSubitems[] = $aSubitem;
  393. }
  394. if(!$bOnClick)
  395. $a['bx_if:onclick'] = [
  396. 'condition' => true,
  397. 'content' => [
  398. 'onclick' => "javascript:return bx_menu_toggle(this, '" . $this->_sObject . "', '" . $a['name'] . "')"
  399. ]
  400. ];
  401. $aTmplVarsSubitems['subitems'] = $this->_oTemplate->parseHtmlByName(str_replace('.html', '_subitems.html', $this->getTemplateName()), array(
  402. 'bx_repeat:menu_items' => $aSubitems,
  403. ));
  404. }
  405. $a['bx_if:show_toggler'] = [
  406. 'condition' => $bTmplVarsSubitems,
  407. 'content' => []
  408. ];
  409. $a['bx_if:show_subitems'] = array (
  410. 'condition' => $bTmplVarsSubitems,
  411. 'content' => $aTmplVarsSubitems
  412. );
  413. unset($a['subitems']);
  414. foreach ($this->_aOptionalParams as $sName => $sDefaultValue)
  415. if (!isset($a[$sName]))
  416. $a[$sName] = $sDefaultValue;
  417. return $a;
  418. }
  419. protected function _getMenuItemAPI ($a)
  420. {
  421. list ($sIcon, $sIconUrl, $sIconA, $sIconHtml) = $this->_getMenuIcon($a);
  422. $aResult = [
  423. 'id' => $a['id'],
  424. 'name' => $a['name'],
  425. 'title' => _t($a['title']),
  426. 'info' => isset($a['info']) ? _t($a['info']) : '',
  427. 'link' => isset($a['link']) ? $a['link'] : '',
  428. 'icon' => $sIcon ? BxDolIconset::getObjectInstance()->getIcon($sIcon) : ($sIconHtml ? $sIconHtml : ''),
  429. 'image' => $sIconUrl ? $sIconUrl : '',
  430. 'submenu' => !empty($a['submenu_object']) ? $a['submenu_object'] : '',
  431. 'addon' => $this->_bDisplayAddons ? $this->_getMenuAddon($a) : '',
  432. 'hidden_on' => isset($a['hidden_on']) ? $a['hidden_on'] : false,
  433. 'hidden_on_col' => isset($a['hidden_on_col']) ? $a['hidden_on_col'] : false,
  434. 'config' => isset($a['config_api']) ? $a['config_api'] : '',
  435. 'primary' => isset($a['primary']) ? $a['primary'] : 0,
  436. 'persistent' => isset($a['persistent']) ? $a['persistent'] : 0,
  437. ];
  438. if(!empty($a['onclick']))
  439. $aResult = array_merge($aResult, [
  440. 'display_type' => 'callback',
  441. 'data' => $this->_getMenuCallbackDataAPI($a)
  442. ]);
  443. $this->_updateVisibilityParamsAPI($aResult);
  444. if(($aMarkers = $this->_getMenuMarkers($a)) && is_array($aMarkers))
  445. $this->addMarkers($aMarkers);
  446. $aResult = $this->_replaceMarkers($aResult);
  447. if(!empty($aResult['link'])) {
  448. if(($sSubstr = 'javascript') && substr($aResult['link'], 0, strlen($sSubstr)) == $sSubstr)
  449. $aResult['link'] = '';
  450. else
  451. $aResult['link'] = $this->_oPermalinks->permalink($aResult['link']);
  452. }
  453. if($this->isMultilevel() && !empty($a['subitems'])) {
  454. $aSubitems = [];
  455. foreach($a['subitems'] as $aSubitem) {
  456. $aSubitem = $this->_getMenuItem($aSubitem);
  457. if($aSubitem !== false)
  458. $aSubitems[] = $aSubitem;
  459. }
  460. $aResult['subitems'] = $aSubitems;
  461. }
  462. return $aResult;
  463. }
  464. protected function _getMenuIcon ($a)
  465. {
  466. return BxTemplFunctions::getInstanceWithTemplate($this->_oTemplate)->getIcon(!empty($a['icon']) ? $a['icon'] : '');
  467. }
  468. protected function _getMenuIconAttrs ($sType, $a)
  469. {
  470. $sAttrs = '';
  471. if($sType == 'image')
  472. $sAttrs .= ' alt="' . $this->_getMenuTitle($a) . '"';
  473. return $sAttrs;
  474. }
  475. protected function _getMenuTitle($a)
  476. {
  477. $sTitle = ($sTitleAttr = $a['title_attr'] ?? '') && ($sTitleAttr = _t($sTitleAttr)) ? $sTitleAttr : ($a['title'] ?? '');
  478. /**
  479. * An icon only menu item (the toolbar hamburger, [+ Add], [Notifications], the 'more' triggers, ...) has an
  480. * empty `title` in `sys_menu_items`, so it used to get no accessible name at all. Every menu item does have
  481. * `title_system` - the label the Studio menu builder shows - so it is used as the fallback name. No new
  482. * language keys are needed and every icon only menu item benefits, not just the toolbar.
  483. */
  484. if(trim(strip_tags($sTitle)) === '' && ($sTitleSystem = $a['title_system'] ?? ''))
  485. $sTitle = _t($sTitleSystem);
  486. return bx_html_attribute(strip_tags($sTitle));
  487. }
  488. protected function _getMenuAreaLabel($a)
  489. {
  490. return ($sAreaLabel = $a['area_label'] ?? '') && ($sAreaLabel = strip_tags(_t($sAreaLabel))) ? bx_html_attribute($sAreaLabel) : '';
  491. }
  492. /**
  493. * True when the visible content of the menu item link already carries its accessible name AND that content is
  494. * richer than the title alone (an addon counter is rendered next to it). In that case an aria-label repeating
  495. * only the title would hide the counter from the accessible name and break WCAG 2.5.3 Label in Name, so it is
  496. * omitted and the link is named by its own text. Icon only items - and items whose template never prints the
  497. * title - keep their aria-label.
  498. */
  499. protected function _isMenuLabelInContent($a)
  500. {
  501. $bTitle = !empty($a['title']) && (!isset($a['icon_only']) || (int)$a['icon_only'] == 0);
  502. return $bTitle && !empty($a['bx_if:addon']['condition']);
  503. }
  504. protected function _getMenuCallbackDataAPI($a)
  505. {
  506. return [];
  507. }
  508. public function getMenuIconHtml($sIcon)
  509. {
  510. list ($sIcon, $sIconUrl, $sIconA, $sIconHtml) = BxTemplFunctions::getInstanceWithTemplate($this->_oTemplate)->getIcon($sIcon);
  511. $a['bx_if:image'] = [
  512. 'condition' => (bool)$sIconUrl,
  513. 'content' => [
  514. 'icon_url' => $sIconUrl,
  515. 'attrs' => 'alt=""'
  516. ],
  517. ];
  518. $a['bx_if:icon'] = array (
  519. 'condition' => (bool)$sIcon,
  520. 'content' => array('icon' => $sIcon),
  521. );
  522. $a['bx_if:icon-a'] = array (
  523. 'condition' => (bool)$sIconA,
  524. 'content' => array('icon-a' => $sIconA),
  525. );
  526. $a['bx_if:icon-html'] = array (
  527. 'condition' => (bool)$sIconHtml,
  528. 'content' => array('icon' => $sIconHtml),
  529. );
  530. return $this->_oTemplate->parseHtmlByName('menu_icon.html', $a);
  531. }
  532. protected function _getMenuAddon ($aMenuItem)
  533. {
  534. if (empty($aMenuItem['addon']))
  535. return '';
  536. if (isset($aMenuItem['addon_cache']) && $aMenuItem['addon_cache']) {
  537. $oCache = BxDolDb::getInstance()->getDbCacheObject();
  538. $sKey = 'menu_' . $this->_sObject . '_' . $aMenuItem['name'] . '_' . bx_get_logged_profile_id() . '_' . bx_site_hash() . '.php';
  539. $s = $oCache->getData($sKey);
  540. if ($s !== null) {
  541. return $s;
  542. }
  543. else {
  544. $s = BxDolService::callSerialized($aMenuItem['addon'], $this->_aMarkers);
  545. $oCache->setData($sKey, $s);
  546. }
  547. return $s;
  548. }
  549. else {
  550. return BxDolService::callSerialized($aMenuItem['addon'], $this->_aMarkers);
  551. }
  552. }
  553. protected function _getMenuMarkers ($aMenuItem)
  554. {
  555. if (empty($aMenuItem['markers']))
  556. return '';
  557. return BxDolService::callSerialized($aMenuItem['markers'], $this->_aMarkers);
  558. }
  559. protected function _getMenuAttrs ($aMenuItem)
  560. {
  561. $sAttrs = '';
  562. $sTitleAttr = $this->_getMenuTitle($aMenuItem);
  563. if($sTitleAttr)
  564. $sAttrs .= ' title="' . $sTitleAttr . '"';
  565. if(!empty($aMenuItem['target']))
  566. $sAttrs .= ' target="' . $aMenuItem['target'] . '"';
  567. if($this->_bAddNoFollow && !empty($aMenuItem['link']) && preg_match('@^https?://@', $aMenuItem['link']) && strncmp($aMenuItem['link'], BX_DOL_URL_ROOT, strlen(BX_DOL_URL_ROOT)) !== 0)
  568. $sAttrs .= ' rel="noreferrer"';
  569. /**
  570. * `attrs_add` lets a menu item declare extra attributes of its own - the same convention as `class_add` -
  571. * so that a PHP built item (a disclosure trigger, for example) can add aria-haspopup/aria-expanded without
  572. * a mechanism of its own.
  573. */
  574. if(!empty($aMenuItem['attrs_add']))
  575. $sAttrs .= ' ' . $aMenuItem['attrs_add'];
  576. /**
  577. * An explicit `area_label` always wins. Otherwise the item title is used as the accessible name, but only
  578. * when the visible content of the link does not already provide one - an aria-label that repeats the title
  579. * while the link also renders an addon (a counter) hides that counter from the accessible name and breaks
  580. * WCAG 2.5.3 Label in Name.
  581. */
  582. if(!($sAreaLabel = $this->_getMenuAreaLabel($aMenuItem)) && !$this->_isMenuLabelInContent($aMenuItem))
  583. $sAreaLabel = $sTitleAttr;
  584. if($sAreaLabel)
  585. $sAttrs .= ' aria-label="' . $sAreaLabel . '"';
  586. return $sAttrs;
  587. }
  588. protected function _addMenuItemsMoreLess($aItems, $iMenuItemsMin)
  589. {
  590. $iMenuItems = count($aItems);
  591. if($iMenuItems <= $iMenuItemsMin)
  592. return $aItems;
  593. $mixedCollpsed = $this->getUserChoiceCollapsed();
  594. $bCollpsed = $mixedCollpsed === false || $mixedCollpsed == 1;
  595. for($i = $iMenuItemsMin; $i < $iMenuItems; $i++)
  596. $aItems[$i]['class_add'] .= ' bx-mi-aux' . ($bCollpsed ? ' bx-mi-hidden' : '');
  597. $aShowMoreLinks = [
  598. 'more' => ['title' => '_sys_show_more', 'icon' => 'chevron-down', 'class' => $bCollpsed ? '' : 'bx-mi-hidden'],
  599. 'less' => ['title' => '_sys_show_less', 'icon' => 'chevron-up', 'class' => !$bCollpsed ? '' : 'bx-mi-hidden']
  600. ];
  601. foreach($aShowMoreLinks as $sLink => $aLink)
  602. $aItems[] = [
  603. 'class_add' => 'bx-psmi-show-' . $sLink . ' ' . $aLink['class'],
  604. 'name' => 'show-' . $sLink,
  605. 'title' => _t($aLink['title']),
  606. 'link' => 'javascript:void(0)',
  607. 'bx_if:onclick' => [
  608. 'condition' => true,
  609. 'content' => [
  610. 'onclick' => 'bx_menu_show_more_less(this, \'' . $this->_sObject . '\', \'.bx-menu-object-' . $this->_sObject . '\')',
  611. ]
  612. ],
  613. 'attrs' => '',
  614. 'bx_if:image' => [
  615. 'condition' => false,
  616. 'content' => ['icon_url' => ''],
  617. ],
  618. 'bx_if:image_inline' => [
  619. 'condition' => false,
  620. 'content' => ['image' => ''],
  621. ],
  622. 'bx_if:icon' => [
  623. 'condition' => true,
  624. 'content' => ['icon' => $aLink['icon']],
  625. ],
  626. 'bx_if:icon-html' => [
  627. 'condition' => false,
  628. 'content' => ['icon-a' => ''],
  629. ],
  630. 'bx_if:icon-a' => [
  631. 'condition' => false,
  632. 'content' => ['icon-a' => ''],
  633. ],
  634. 'bx_if:addon' => [
  635. 'condition' => false,
  636. 'content' => []
  637. ]
  638. ];
  639. return $aItems;
  640. }
  641. /**
  642. * Add css/js files which are needed for menu display and functionality.
  643. */
  644. protected function _addJsCss()
  645. {
  646. $this->_oTemplate->addCss('menu.css');
  647. }
  648. protected function _getTmplVarsAddon($mixedAddon, $aMenuItem)
  649. {
  650. $sAddon = '';
  651. if(!is_array($mixedAddon))
  652. $sAddon = $mixedAddon;
  653. else if(!empty($mixedAddon['addon']))
  654. $sAddon = $mixedAddon['addon'];
  655. return array(
  656. 'addon' => $sAddon,
  657. 'addonf' => ''
  658. );
  659. }
  660. }
  661. /** @} */