v1.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. <?php
  2. namespace zin;
  3. require_once dirname(__DIR__) . DS . 'nav' . DS . 'v1.php';
  4. require_once dirname(__DIR__) . DS . 'mainnavbar' . DS . 'v1.php';
  5. require_once dirname(__DIR__) . DS . 'menuviewswitcher' . DS . 'v1.php';
  6. class navbar extends wg
  7. {
  8. /**
  9. * @var mixed[]
  10. */
  11. protected static $defineProps = array(
  12. 'items?: array'
  13. );
  14. public static function getPageCSS()
  15. {
  16. return <<<'CSS'
  17. #navbar {position: relative;}
  18. #navbar .nav[z-use-sortable] > li:hover {cursor: grab !important;}
  19. #navbar .nav[z-use-sortable] > li > a:hover {cursor: grab !important;}
  20. #navbar .nav li.nav-divider.divider {border: none; width: 1px; background: currentColor; margin: 0; padding-left: var(--nav-divider-margin); padding-right: var(--nav-divider-margin); box-sizing: content-box; background-clip: content-box;}
  21. #navbarHeading {position: absolute; top: 0; left: 1rem; bottom: 0; display: flex; align-items: center; justify-content: center;}
  22. @media (min-width: 1400px) {#navbarHeading{left: 0}}
  23. CSS;
  24. }
  25. public static function getPageJS()
  26. {
  27. global $lang, $app;
  28. $app->loadLang('index');
  29. jsVar('langData', $lang->index->dock);
  30. return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
  31. }
  32. protected function getOriginItems()
  33. {
  34. $items = $this->prop('items');
  35. if($items !== null) return $items;
  36. return static::getItems();
  37. }
  38. /**
  39. * @param string $workspace
  40. */
  41. protected function buildWorkspaceNavbar($workspace)
  42. {
  43. global $app, $config;
  44. $originItems = $this->getOriginItems();
  45. $isTaskViews = $app->rawModule === 'execution' && data('activeMenu') === 'task';
  46. $isTaskReport = $app->tab === 'execution' && $app->rawModule === 'task' && $app->rawMethod === 'report';
  47. $showViewSwitcher = ($isTaskViews || $isTaskReport) && !empty($config->sanplexVersion) && $config->vision === 'rnd';
  48. $items = static::getWorkspaceItems($showViewSwitcher);
  49. $activeID = data('activeMenu');
  50. $menuGroup = data('mainNavbarGroup');
  51. $activeItem = array('data-id' => $activeID);
  52. foreach($originItems as $item)
  53. {
  54. if(empty($item['data-id']) || $item['data-id'] != $activeID) continue;
  55. $activeItem = $item;
  56. break;
  57. }
  58. return h::nav
  59. (
  60. set::id('navbar'),
  61. div
  62. (
  63. setID('navbarHeading'),
  64. setClass('text-lg'),
  65. isset($activeItem['text']) ? $activeItem['text'] : null
  66. ),
  67. new nav
  68. (
  69. setData('workspace', $workspace),
  70. setData('navbarGroup', $menuGroup),
  71. setData('mainNavbarGroup', $app->tab . '-' . $activeID),
  72. on::init()->call('initPageNavbar', $originItems, $workspace, $activeID, "__WORKSPACE_{$workspace}__", empty($config->customMenu->{$menuGroup}) ? [] : json_decode($config->customMenu->{$menuGroup})),
  73. set::items($items),
  74. $this->children()
  75. ),
  76. $showViewSwitcher ? css('#actionBar>a[href^="' . str_replace('.html', '', createLink('task', 'report')) . '"]{display: none;}') : null
  77. );
  78. }
  79. /**
  80. * Build.
  81. *
  82. * @access protected
  83. */
  84. protected function build()
  85. {
  86. $workspace = commonModel::getWorkspaceInfo();
  87. if($workspace['opened']) return $this->buildWorkspaceNavbar($workspace['type']);
  88. $items = $this->getOriginItems();
  89. $navItems = [];
  90. foreach($items as $item)
  91. {
  92. if(!empty($item['hidden'])) continue;
  93. $navItems[] = array_merge($item, ['icon' => null]);
  94. }
  95. global $lang;
  96. $responsiveNavOptions = [];
  97. $responsiveNavOptions['container'] = 'parent';
  98. $responsiveNavOptions['more'] = ['text' => $lang->other, 'caret' => true];
  99. $responsiveNavOptions['moreDropdown'] = ['trigger' => 'hover'];
  100. $responsiveNavOptions['watch'] = ['container', '#toolbar', '#heading'];
  101. $responsiveNavOptions['getContainerSize'] = jsRaw('(container) => ($(container).width() - 20 - (2 * Math.max(($("#heading").outerWidth() || 0), $("#toolbar").outerWidth() || 0)))');
  102. return h::nav
  103. (
  104. set::id('navbar'),
  105. commonModel::isTutorialMode() ? null : on::contextmenu('.nav-item:not(.nav-dropdown) > a, .nav-divider')->call('handleNavbarContextmenu', jsRaw('event'), jsRaw('this')),
  106. new nav
  107. (
  108. setData('navbarGroup', data('mainNavbarGroup')),
  109. on::init()->call('initPageNavbar', $items),
  110. set::items($navItems),
  111. empty($items) ? null : zui::create('ResponsiveNavHelper', $responsiveNavOptions),
  112. $this->children()
  113. )
  114. );
  115. }
  116. /**
  117. * @param bool $showViewSwitcher
  118. */
  119. protected static function getWorkspaceItems($showViewSwitcher = false)
  120. {
  121. if($showViewSwitcher)
  122. {
  123. list($items) = menuViewSwitcher::getItems();
  124. foreach($items as $index => $item)
  125. {
  126. if(empty($item['selected'])) continue;
  127. $items[$index]['active'] = true;
  128. }
  129. return $items;
  130. }
  131. return mainNavbar::getItems();
  132. }
  133. protected static function getExecutionMoreItem($executionID)
  134. {
  135. if(commonModel::isTutorialMode()) return;
  136. global $lang, $app;
  137. $object = $app->dbh->query('SELECT project,`type` FROM ' . TABLE_EXECUTION . " WHERE `id` = '$executionID'")->fetch();
  138. if(empty($object)) return;
  139. $project = $app->dbh->query('SELECT id,`model` FROM ' . TABLE_PROJECT . " WHERE `id` = '{$object->project}'")->fetch();
  140. $isWaterfall = in_array($project->model, array('waterfall', 'waterfallplus'));
  141. $orderBy = $isWaterfall ? '`order` ASC' : '`id` DESC';
  142. $executionPairs = array();
  143. $executionList = $app->control->dao->select("id,name,parent,grade,path")->from(TABLE_EXECUTION)->where('project')->eq($object->project)->andWhere('deleted')->eq('0')->orderBy($orderBy)->fetchAll('id');
  144. $orderedExecutions = $app->control->loadModel('execution')->resetExecutionSorts($executionList);
  145. foreach(array_keys($orderedExecutions) as $currentID)
  146. {
  147. if(!isset($executionList[$currentID])) continue;
  148. $execution = $executionList[$currentID];
  149. if(isset($executionPairs[$execution->parent])) unset($executionPairs[$execution->parent]);
  150. if(!$app->user->admin && strpos(",{$app->user->view->sprints},", ",{$execution->id},") === false) continue;
  151. if($execution->id == $executionID) continue;
  152. $executionPairs[$currentID] = $execution->name;
  153. if($isWaterfall)
  154. {
  155. $executionName = array_map(function($executionID) use ($executionList)
  156. {
  157. if(isset($executionList[$executionID])) return $executionList[$executionID]->name;
  158. }, array_slice(explode(',', trim($execution->path, ',')), 1));
  159. $executionPairs[$currentID] = implode('/', array_filter($executionName));
  160. }
  161. }
  162. if(empty($executionPairs)) return;
  163. $dropItems = array();
  164. foreach($executionPairs as $executionID => $executionName)
  165. {
  166. $dropItems[] = array(
  167. 'url' => createLink('execution', 'task', "executionID=$executionID"),
  168. 'text' => $executionName,
  169. 'hint' => $executionName,
  170. 'class' => 'text-ellipsis'
  171. );
  172. if(count($dropItems) >= 10) break;
  173. }
  174. if(count($executionPairs) > 10)
  175. {
  176. $dropItems[] = array(
  177. 'url' => createLink('project', 'execution', "status=all&projectID={$object->project}"),
  178. 'text' => "$lang->preview $lang->more",
  179. 'hint' => $lang->more,
  180. 'data-app' => 'project'
  181. );
  182. }
  183. return array(
  184. 'type' => 'dropdown',
  185. 'items' => $dropItems,
  186. 'text' => $lang->more,
  187. 'trigger' => 'hover',
  188. 'id' => 'navbarMoreMenu',
  189. 'data-id' => 'more',
  190. 'menu' => array('style' => array('max-width' => '300px'))
  191. );
  192. }
  193. protected static function getAppBtnItem()
  194. {
  195. if(commonModel::isTutorialMode()) return;
  196. global $app, $config, $lang;
  197. $condition = '';
  198. if(!$app->user->admin)
  199. {
  200. $types = '';
  201. foreach($config->pipelineTypeList as $pipelineType)
  202. {
  203. if(commonModel::hasPriv($pipelineType, 'browse')) $types .= "'$pipelineType',";
  204. }
  205. if(empty($types)) return;
  206. $condition .= ' AND `type` in (' . trim($types, ',') . ')';
  207. }
  208. $pipelineList = $app->dbh->query("SELECT type,name,url FROM " . TABLE_PIPELINE . " WHERE `deleted` = '0' $condition order by type")->fetchAll();
  209. if(empty($pipelineList)) return;
  210. $dropItems = array();
  211. foreach($pipelineList as $pipeline)
  212. {
  213. $dropItems[] = array(
  214. 'url' => $pipeline->url,
  215. 'text' => "[{$pipeline->type}] {$pipeline->name}",
  216. 'hint' => $pipeline->name,
  217. 'class' => 'text-ellipsis',
  218. 'target' => '_blank'
  219. );
  220. }
  221. return array(
  222. 'type' => 'dropdown',
  223. 'items' => $dropItems,
  224. 'text' => $lang->app->common,
  225. 'trigger' => 'hover',
  226. 'menu' => array('style' => array('max-width' => '300px'))
  227. );
  228. }
  229. /**
  230. * Get the items for the navbar.
  231. *
  232. * @access public
  233. * @return array
  234. */
  235. public static function getItems()
  236. {
  237. $items = data('navbarOriginItems');
  238. if($items !== null) return array_values($items);
  239. global $app, $lang, $config;
  240. $adminMenuKey = '';
  241. if($app->tab == 'admin')
  242. {
  243. $groupID = data('groupID') ? data('groupID') : 0;
  244. $app->control->loadModel('admin')->setMenu($groupID);
  245. $adminMenuKey = $app->control->loadModel('admin')->getMenuKey();
  246. }
  247. commonModel::replaceMenuLang();
  248. $isHomeMenu = commonModel::setMainMenu();
  249. commonModel::checkMenuVarsReplaced();
  250. jsVar('isHomeMenu', $isHomeMenu);
  251. $isTutorialMode = commonModel::isTutorialMode();
  252. $currentModule = $app->rawModule;
  253. $currentMethod = $app->rawMethod;
  254. if($isTutorialMode and defined('WIZARD_MODULE')) $currentModule = WIZARD_MODULE;
  255. if($isTutorialMode and defined('WIZARD_METHOD')) $currentMethod = WIZARD_METHOD;
  256. $tab = $app->tab;
  257. $menu = \customModel::getMainMenu($isHomeMenu);
  258. $activeMenu = '';
  259. $activeMenuID = data('activeMenuID');
  260. $items = array();
  261. $flows = $config->edition != 'open' ? $app->control->loadModel('my')->getFlowPairs() : array();
  262. $projectModel = '';
  263. $navbarInMainMenu = [];
  264. $activeInMainMenu = '';
  265. $getMenuIcon = function($menuItem) use ($lang)
  266. {
  267. if(isset($menuItem->icon)) return $menuItem->icon;
  268. if(!empty($lang->iconMap[$menuItem->name])) return 'icon-' . $lang->iconMap[$menuItem->name];
  269. return '';
  270. };
  271. foreach($menu as $menuItem)
  272. {
  273. if(isset($menuItem->class) && strpos($menuItem->class, 'automation-menu'))
  274. {
  275. if($menuItem->divider) $items[] = array('type' => 'divider');
  276. $items[] = array('class' => $menuItem->class, 'text' => $menuItem->text, 'type' => 'text', 'tagName' => 'span', 'icon' => $getMenuIcon($menuItem));
  277. continue;
  278. }
  279. if(empty($menuItem->link)) continue;
  280. if($menuItem->divider && empty($menuItem->hidden)) $items[] = array('type' => 'divider');
  281. /* Init the these vars. */
  282. $subModule = isset($menuItem->subModule) ? explode(',', $menuItem->subModule) : array();
  283. $class = isset($menuItem->class) ? $menuItem->class : '';
  284. $exclude = isset($menuItem->exclude) ? $menuItem->exclude : '';
  285. $isActive = false;
  286. if($menuItem->name == $currentModule and !str_contains(",$exclude,", ",$currentModule-$currentMethod,"))
  287. {
  288. $isActive = true;
  289. }
  290. elseif($subModule and in_array($currentModule, $subModule) and !str_contains(",$exclude,", ",$currentModule-$currentMethod,"))
  291. {
  292. $isActive = true;
  293. }
  294. if($menuItem->link['module'] == 'project' and $menuItem->link['method'] == 'index')
  295. {
  296. $projectID = str_replace('project=', '', $menuItem->link['vars']);
  297. $projectInfo = $app->dbh->query("SELECT `model` FROM " . TABLE_PROJECT . " WHERE `id` = '$projectID'")->fetch();
  298. if($projectInfo && isset($projectInfo->model)) $projectModel = $projectInfo->model;
  299. }
  300. $newItem = null;
  301. if($menuItem->link['module'] == 'execution' and $menuItem->link['method'] == 'more')
  302. {
  303. $executionID = $menuItem->link['vars'];
  304. $executionMoreItem = static::getExecutionMoreItem($executionID);
  305. if(!empty($executionMoreItem)) $newItem = $executionMoreItem;
  306. elseif(isset(end($items)['type']) && end($items)['type'] == 'divider') array_pop($items); // 最后一个是分割线,则删除
  307. }
  308. elseif($menuItem->link['module'] == 'app' and $menuItem->link['method'] == 'serverlink')
  309. {
  310. $appBtnItem = static::getAppBtnItem();
  311. if(!empty($appBtnItem)) $newItem = $appBtnItem;
  312. }
  313. elseif($menuItem->link)
  314. {
  315. $alias = isset($menuItem->alias) ? $menuItem->alias : '';
  316. $target = '';
  317. $module = '';
  318. $method = '';
  319. $label = $menuItem->text;
  320. if(is_array($menuItem->link))
  321. {
  322. if(isset($menuItem->link['target'])) $target = $menuItem->link['target'];
  323. if(isset($menuItem->link['module'])) $module = $menuItem->link['module'];
  324. if(isset($menuItem->link['method'])) $method = $menuItem->link['method'];
  325. }
  326. if($module == $currentModule and ($method == $currentMethod or str_contains(",$alias,", ",$currentMethod,")) and !str_contains(",$exclude,", ",$currentMethod,")) $isActive = true;
  327. $dataApp = (isset($lang->navGroup->$module) && $tab != $lang->navGroup->$module) || isset($flows[$module]) ? $tab : null;
  328. if($isActive && $activeMenuID) $isActive = $menuItem->name == $activeMenuID;
  329. if($isActive && empty($activeMenu)) $activeMenu = $menuItem->name;
  330. else $isActive = false;
  331. /* Print drop menus. */
  332. if(isset($menuItem->dropMenu))
  333. {
  334. $dropItems = array();
  335. foreach($menuItem->dropMenu as $dropMenuName => $dropMenuItem)
  336. {
  337. if(empty($dropMenuItem)) continue;
  338. if(isset($dropMenuItem['hidden']) and $dropMenuItem['hidden']) continue;
  339. /* Parse drop menu link. */
  340. if(!empty($dropMenuItem['links']))
  341. {
  342. $dropMenuLink = common::getHasPrivLink($dropMenuItem);
  343. if(empty($dropMenuLink)) continue;
  344. list($subLabel, $subModule, $subMethod, $subParams) = $dropMenuLink;
  345. }
  346. else
  347. {
  348. $dropMenuLink = zget($dropMenuItem, 'link', $dropMenuItem);
  349. list($subLabel, $subModule, $subMethod, $subParams) = explode('|', $dropMenuLink);
  350. if(!common::hasPriv($subModule, $subMethod)) continue;
  351. }
  352. $subLink = createLink($subModule, $subMethod, $subParams);
  353. $subActive = false;
  354. $activeMainMenu = false;
  355. if($currentModule == strtolower($subModule) and $currentMethod == strtolower($subMethod))
  356. {
  357. $activeMainMenu = true;
  358. }
  359. else
  360. {
  361. $subModule = isset($dropMenuItem['subModule']) ? explode(',', $dropMenuItem['subModule']) : array();
  362. $subAlias = zget($dropMenuItem, 'alias', '');
  363. if($subModule and in_array($currentModule, $subModule) and !str_contains(",$exclude,", ",$currentModule-$currentMethod,")) $activeMainMenu = true;
  364. if(str_contains(",$subAlias,", ",$currentModule-$currentMethod,")) $activeMainMenu = true;
  365. }
  366. if($activeMenuID) $activeMainMenu = $dropMenuName == $activeMenuID;
  367. if($activeMainMenu)
  368. {
  369. $activeMenu = $dropMenuName;
  370. $isActive = true;
  371. $subActive = true;
  372. $label = $subLabel;
  373. }
  374. $dropItemIcon = isset($dropMenuItem->icon) ? $dropMenuItem->icon : '';
  375. if(empty($dropItemIcon)) $dropItemIcon = isset($lang->iconMap[$dropMenuName]) ? 'icon-' . $lang->iconMap[$dropMenuName] : '';
  376. $dataApp = !empty($dropMenuItem['data-app']) ? $dropMenuItem['data-app'] : $dataApp;
  377. $dropItems[] = array('active' => $subActive, 'data-id' => $dropMenuName, 'url' => $subLink, 'text' => $subLabel, 'data-app' => $dataApp, 'zui-key' => $dropMenuName, 'icon' => $dropItemIcon);
  378. }
  379. if(empty($dropItems)) continue;
  380. if($menuItem->name === 'other')
  381. {
  382. foreach($dropItems as $dropItem)
  383. {
  384. $dropItem['outerClass'] = 'is-rsh-more';
  385. $items['other-' . $dropItem['data-id']] = $dropItem;
  386. }
  387. continue;
  388. }
  389. $newItem = array('type' => 'dropdown', 'items' => $dropItems, 'class' => $class, 'active' => $isActive, 'target' => $target, 'text' => $label, 'data-id' => $menuItem->name, 'data-app' => $dataApp, 'trigger' => 'hover', 'icon' => 'icon-more-circle');
  390. }
  391. else
  392. {
  393. $hidden = (isset($menuItem->hidden) && $menuItem->hidden && (!isset($menuItem->tutorial) || !$menuItem->tutorial));
  394. $newItem = array('class' => $class, 'icon' => $getMenuIcon($menuItem), 'text' => $label, 'url' => commonModel::createMenuLink($menuItem, $tab), 'active' => $isActive, 'target' => $target, 'data-id' => $menuItem->name, 'data-app' => $dataApp, 'hidden' => $hidden);
  395. }
  396. }
  397. else
  398. {
  399. $newItem = array('class' => $class, 'icon' => $getMenuIcon($menuItem), 'text' => $menuItem->text, 'active' => $isActive);
  400. }
  401. if(!$newItem) continue;
  402. if(isset($newItem['data-id']))
  403. {
  404. $newItem['zui-key'] = $newItem['data-id'];
  405. if($newItem['data-id'] === 'settings' && strpos(',execution,project,product,', ",{$app->tab},") !== false) $newItem['outerClass'] = 'is-rsh-fixed';
  406. }
  407. $showInMainMenu = isset($menuItem->showInMainMenu) ? $menuItem->showInMainMenu : false;
  408. if($showInMainMenu)
  409. {
  410. if($isActive) $activeInMainMenu = $showInMainMenu;
  411. $navbarInMainMenu[] = $newItem;
  412. }
  413. if(!$showInMainMenu || $showInMainMenu === $menuItem->name) $items[$menuItem->name] = $newItem;
  414. }
  415. data('actualActiveMenu', $activeMenu);
  416. $isExecutionView = ($activeMenu === 'view' || $activeMenu === 'task') && $currentModule === 'execution';
  417. $isTaskReport = $currentMethod === 'report' && $currentModule === 'task';
  418. $isKanbanView = $currentModule === 'execution' && $currentMethod === 'taskkanban';
  419. $isBurnView = $currentModule === 'execution' && $currentMethod === 'burn';
  420. if(!$isExecutionView && !$isTaskReport && !$isKanbanView && !$isBurnView) $navbarInMainMenu = null;
  421. if(empty($items[$activeMenu]) && !empty($activeInMainMenu) && !empty($items[$activeInMainMenu]))
  422. {
  423. $items[$activeInMainMenu]['active'] = true;
  424. $activeMenu = $activeInMainMenu;
  425. }
  426. /* Set active menu to global data, make it accessible to other widgets */
  427. data('activeMenu', $activeMenu);
  428. data('navbarInMainMenu', $navbarInMainMenu);
  429. $menuGroup = $tab;
  430. if(!empty($projectModel)) $menuGroup = "project-$projectModel";
  431. elseif($isHomeMenu) $menuGroup = "{$tab}-home";
  432. elseif($tab == 'admin') $menuGroup = "admin-$adminMenuKey";
  433. data('mainNavbarGroup', $menuGroup);
  434. data('navbarOriginItems', $items);
  435. return array_values($items);
  436. }
  437. }