| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- <?php
- namespace zin;
- require_once dirname(__DIR__) . DS . 'heading' . DS . 'v1.php';
- require_once dirname(__DIR__) . DS . 'navbar' . DS . 'v1.php';
- require_once dirname(__DIR__) . DS . 'toolbar' . DS . 'v1.php';
- require_once dirname(__DIR__) . DS . 'useravatar' . DS . 'v1.php';
- class header extends wg
- {
- /**
- * @var mixed[]
- */
- protected static $defineBlocks = array
- (
- 'heading' => array('map' => 'heading'),
- 'headingToolbar' => array('map' => 'toolbar'),
- 'dropmenu' => array('map' => 'dropmenu'),
- 'navbar' => array('map' => 'nav'),
- 'toolbar' => array('map' => 'btn')
- );
- public static function getPageCSS()
- {
- return <<<'CSS'
- #heading {z-index: 5}
- .in-workspace #heading {display: none;}
- CSS;
- }
- protected function buildHeading()
- {
- if($this->hasBlock('heading')) return $this->block('heading');
- $headingToolbar = $this->block('headingToolbar');
- $dropmenu = $this->block('dropmenu');
- return new heading($headingToolbar, $dropmenu);
- }
- protected function buildNavbar()
- {
- $navbar = $this->block('navbar');
- if(empty($navbar)) $navbar = new navbar();
- return $navbar;
- }
- protected function buildToolbar()
- {
- $toolbar = $this->block('toolbar');
- if(empty($toolbar))
- {
- $toolbar = new toolbar
- (
- setClass('gap-2'),
- static::quickAddMenu(),
- static::messageBar(),
- static::userBar()
- );
- }
- $pageToolbar = data('pageToolbar');
- return h::div
- (
- set::id('toolbar'),
- div
- (
- setID('pageToolbar'),
- setClass('toolbar mr-2'),
- $pageToolbar ? html($pageToolbar) : null,
- static::workspaceEntry()
- ),
- $toolbar
- );
- }
- /**
- * @param string $workspace
- */
- protected function buildWorkspaceHeader($workspace)
- {
- return h::header(setID('header'), commonModel::isTutorialMode() ? setStyle('min-width', 'fit-content') : null, $this->buildHeading());
- }
- /**
- * Build.
- *
- * @access protected
- */
- protected function build()
- {
- return h::header
- (
- setID('header'),
- commonModel::isTutorialMode() ? setStyle('min-width', 'fit-content') : null,
- h::div
- (
- setClass('container'),
- $this->buildHeading(),
- $this->buildNavbar(),
- $this->buildToolbar()
- )
- );
- }
- public static function userBar()
- {
- global $lang, $app, $config;
- if(!isset($app->user)) return;
- $user = $app->user;
- $isGuest = $user->account == 'guest';
- $items = array();
- $modalClass = (isset($config->zin->mode) && $config->zin->mode == 'compatible') ? 'open-in-parent' : null;
- if(!$isGuest)
- {
- $noRole = empty($user->role) || !isset($lang->user->roleList[$user->role]);
- $items[] = array
- (
- 'url' => createLink('my', 'profile', '', '', true),
- 'leadingClass' => 'row items-center gap-2 px-2 py-1 text-inherit',
- 'icon' => ' hidden',
- 'title' => empty($user->realname) ? $user->account : $user->realname,
- 'titleClass' => 'text-lg',
- 'subtitle' => $noRole ? null : $lang->user->roleList[$user->role],
- 'innerClass' => $modalClass,
- 'data-toggle' => 'modal',
- 'data-size' => 700,
- 'data-id' => 'profile',
- 'leading' => array('html' => userAvatar(set::user($user))->render(), 'className' => 'center mr-1')
- );
- $items[] = array('type' => 'divider');
- $items[] = array
- (
- 'url' => createLink('my', 'profile', '', '', true),
- 'icon' => 'account',
- 'text' => $lang->profile,
- 'innerClass' => $modalClass,
- 'data-toggle' => 'modal',
- 'data-size' => 700,
- 'data-id' => 'profile'
- );
- if(common::hasPriv('my', 'changePassword'))
- {
- $items[] = array
- (
- 'url' => createLink('my', 'changepassword', '', '', true),
- 'icon' => 'cog-outline',
- 'text' => $lang->changePassword,
- 'innerClass' => $modalClass,
- 'data-toggle' => 'modal',
- 'data-size' => 'sm'
- );
- }
- /* 禅道国际版增加站点管理。 */
- if(!empty($config->sanplexVersion) && $app->user->admin) $items[] = array('text' => $lang->site, 'url' => createLink('admin', 'subscription'), 'icon' => 'sitemap');
- $items[] = array('type' => 'divider');
- if($app->config->vision === 'rnd' && !commonModel::isTutorialMode())
- {
- $items[] = array
- (
- 'url' => createLink('tutorial', 'start'),
- 'icon' => 'guide',
- 'text' => $lang->tutorialAB,
- 'class' => '800',
- 'outerClass' => 'user-tutorial',
- 'data-width' => 700,
- 'data-class-name' => 'modal-inverse tutorial-start',
- 'data-headerless' => true,
- 'data-backdrop' => true,
- 'data-keyboard' => true,
- 'innerClass' => $modalClass,
- 'data-toggle' => 'modal'
- );
- }
- }
- $helpItems = array();
- $manualUrl = ((!empty($config->isINT)) ? $config->manualUrl['int'] : $config->manualUrl['home']) . '&theme=' . $_COOKIE['theme'];
- $helpItems[] = array('text' => $lang->manual, 'url' => $manualUrl, 'attrs' => array('data-app' => 'help'));
- $helpItems[] = array('text' => $lang->changeLog, 'url' => createLink('misc', 'changeLog'), 'data-toggle' => 'modal', 'innerClass' => $modalClass);
- /* 禅道国际版隐藏帮助。 */
- if(empty($config->sanplexVersion)) $items[] = array('text' => $lang->manual, 'icon' => 'help', 'url' => $manualUrl, 'attrs' => array('data-app' => 'help'));
- $items[] = array('type' => 'divider');
- if(!$isGuest && $app->config->vision === 'rnd')
- {
- $items[] = array
- (
- 'url' => createLink('my', 'preference', 'showTip=false', '', true),
- 'icon' => 'controls',
- 'text' => $lang->preference,
- 'data-width' => 700,
- 'innerClass' => $modalClass,
- 'data-toggle' => 'modal'
- );
- }
- $themeItems = array();
- foreach($app->lang->themes as $key => $value)
- {
- $themeItems[] = array('text' => $value, 'data-value' => $key, 'url' => "javascript:selectTheme(\"$key\")", 'selected' => $app->cookie->theme == $key);
- }
- $items[] = array
- (
- 'text' => $lang->theme,
- 'icon' => 'theme',
- 'key' => 'theme',
- 'items' => $themeItems,
- 'listProps' => ['getItem' => jsRaw('(item) => {item.selected = item["data-value"] === $.cookie.get("theme"); return item;}')]
- );
- $langItems = array();
- foreach($app->config->langs as $key => $value) $langItems[] = array('text' => $value, 'data-value' => $key, 'url' => "javascript:selectLang(\"$key\")", 'active' => $app->cookie->lang == $key);
- $items[] = array('text' => $lang->lang, 'icon' => 'lang', 'items' => $langItems);
- $items[] = array('type' => 'divider');
- /* Zentao desktop client menu. */
- if(isset($config->xxserver->installed) && $config->xuanxuan->turnon)
- {
- $clientSubMenu = array();
- $clientSubMenu[] = array('text' => $lang->downloadClient, 'url' => createLink('misc', 'downloadClient'), 'data-toggle' => 'modal', 'innerClass' => $modalClass);
- $clientSubMenu[] = array('text' => $lang->clientHelp, 'url' => $lang->clientHelpLink, 'attrs' => array('data-app' => 'help'));
- $items[] = array('text' => $lang->clientName, 'icon' => 'desktop', 'items' => $clientSubMenu);
- }
- $mobileSubMenu[] = array('content' => array('html' => "<img src='{$config->webRoot}static/images/app-qrcode.png' />", 'style' => 'width: 100px; heigth: 100px;'));
- /* 禅道国际版隐藏该内容。 */
- if(empty($config->sanplexVersion))
- {
- $items[] = array('icon' => 'mobile', 'text' => $lang->downloadMobile, 'items' => $mobileSubMenu);
- $items[] = array('text' => $lang->aboutZenTao, 'icon' => 'about', 'url' => createLink('misc', 'about'), 'data-toggle' => 'modal', 'innerClass' => $modalClass);
- $items[] = array('type' => 'html', 'className' => 'menu-item', 'html' => $lang->designedByAIUX);
- $items[] = array('type' => 'divider');
- }
- if($isGuest)
- {
- $items[] = array('text' => $lang->login, 'url' => createLink('user', 'login'), 'target' => '_top');
- }
- else
- {
- $items[] = array('text' => $lang->logout, 'url' => "javascript:$.apps.logout()", 'icon' => 'exit');
- }
- return dropdown
- (
- a
- (
- setClass('w-7 h-7 cursor-pointer'),
- userAvatar
- (
- set::circle(true),
- set::size(28),
- set::user($user)
- ),
- set::caret(false)
- ),
- set::id('userMenu'),
- set::placement('bottom-end'),
- set::menu(array('style' => array('color' => 'var(--color-fore)'))),
- set::strategy('fixed'),
- set::arrow(true),
- set::items($items)
- );
- }
- public static function messageBar()
- {
- global $app, $lang, $config;
- $app->loadConfig('message');
- if(!$config->message->browser->turnon) return null;
- $showCount = $config->message->browser->count;
- $unreadCount = $app->dbh->query("SELECT COUNT(1) AS `count` FROM " . TABLE_NOTIFY . " WHERE `objectType` = 'message' AND status != 'read' AND action != 0 AND `toList` = ',{$app->user->account},'")->fetch()->count;
- $dotStyle = commonModel::getDotStyle($showCount != '0', $unreadCount);
- if($unreadCount > 99) $unreadCount = '99+';
- return dropdown
- (
- set::arrow(true),
- set::placement('bottom-end'),
- set::offset(array("alignmentAxis" => -50)),
- to::trigger
- (
- btn
- (
- setID('messageBar'),
- set(array('data-on' => 'click', 'data-call' => 'fetchMessage', 'data-fetcher' => createLink('message', 'ajaxGetDropMenu'))),
- setClass('rounded-full bg-gray bg-opacity-10 text-primary-900 text-opacity-70 ring-0 w-9'),
- set::square(true),
- set::caret(false),
- set::size('sm'),
- icon('bell', set::size('lg')),
- $unreadCount ? label(setClass('danger label-dot absolute' . ($showCount ? ' rounded-sm' : '')), set::style($dotStyle), $showCount ? $unreadCount : null) : null
- )
- ),
- to::menu(menu
- (
- setClass('dropdown-menu not-hide-menu messageDropdownBox'),
- set::style(array('padding' => '0')),
- div(setID('dropdownMessageMenu'))
- ))
- );
- }
- public static function quickAddMenu()
- {
- global $app, $config, $lang;
- /* Initialize the default values. */
- $showCreateList = $needPrintDivider = false;
- $isCompatible = isset($config->zin->mode) && $config->zin->mode == 'compatible';
- $modalClass = $isCompatible ? 'open-in-parent' : null;
- /* Get default product id. */
- $productID = isset($_SESSION['product']) ? $_SESSION['product'] : 0;
- if($productID)
- {
- $product = $app->dbh->query("SELECT id FROM " . TABLE_PRODUCT . " WHERE `deleted` = '0' and vision = '{$config->vision}' and id = '{$productID}'")->fetch();
- if(empty($product)) $productID = 0;
- }
- if(!$productID and $app->user->view->products)
- {
- $product = $app->dbh->query("SELECT id FROM " . TABLE_PRODUCT . " WHERE `deleted` = '0' and vision = '{$config->vision}' and id " . helper::dbIN($app->user->view->products) . " order by `order` desc limit 1")->fetch();
- if($product) $productID = $product->id;
- }
- if($config->vision == 'lite')
- {
- $condition = " WHERE `deleted` = '0' AND `vision` = 'lite' AND `model` = 'kanban'";
- if(!$app->user->admin) $condition .= " AND `id` " . helper::dbIN($app->user->view->projects);
- $object = $app->dbh->query("select id from " . TABLE_PROJECT . $condition . ' LIMIT 1')->fetch();
- if(empty($object)) unset($lang->createIcons['story'], $lang->createIcons['task'], $lang->createIcons['execution']);
- }
- if($config->edition == 'open') unset($lang->createIcons['effort']);
- if($config->systemMode == 'light') unset($lang->createIcons['program'], $lang->createIcons['charter']);
- if(empty($config->board)) unset($lang->createIcons['board']);
- /* Check whether the creation permission is available, and print create buttons. */
- $items = array();
- foreach($lang->createIcons as $objectType => $objectIcon)
- {
- $createMethod = 'create';
- $module = $objectType == 'kanbanspace' ? 'kanban' : $objectType;
- if($objectType == 'effort') $createMethod = 'batchCreate';
- if($objectType == 'kanbanspace') $createMethod = 'createSpace';
- if($objectType == 'board') $createMethod = 'createBoard';
- if(str_contains('|bug|execution|kanbanspace|', "|$objectType|")) $needPrintDivider = true;
- if(!common::hasPriv($module, $createMethod)) continue;
- if($objectType == 'doc' and !common::hasPriv('doc', 'create')) continue;
- /* Determines whether to print a divider. */
- if($needPrintDivider and $showCreateList)
- {
- $items[] = array('type' => 'divider');
- $needPrintDivider = false;
- }
- $showCreateList = true;
- $isOnlyBody = false;
- $item = array('icon' => $objectIcon, 'text' => $lang->createObjects[$objectType]);
- $params = '';
- switch($objectType)
- {
- case 'doc':
- $params = '';
- $createMethod = 'selectLibType';
- $item['innerClass'] = $modalClass;
- $item['data-toggle'] = 'modal';
- break;
- case 'project':
- if($config->vision == 'lite')
- {
- $params = "model=kanban";
- }
- elseif(!commonModel::isTutorialMode())
- {
- $params = "programID=0&from=global";
- $createMethod = 'createGuide';
- $item['innerClass'] = $modalClass;
- $item['data-toggle'] = 'modal';
- if($isCompatible) $item['data-type'] = 'ajax';
- }
- else
- {
- $params = "model=scrum&programID=0©ProjectID=0&extra=from=global";
- }
- break;
- case 'bug':
- $params = "productID=$productID&branch=&extras=from=global";
- break;
- case 'story':
- if(!$productID and $config->vision == 'lite')
- {
- $module = 'project';
- $params = "model=kanban";
- }
- else
- {
- $params = "productID=$productID&branch=0&moduleID=0&storyID=0&objectID=0&bugID=0&planID=0&todoID=0&extra=from=global";
- if($config->vision == 'lite')
- {
- $projectID = isset($_SESSION['project']) ? $_SESSION['project'] : 0;
- $projects = $app->dbh->query("SELECT t2.id FROM " . TABLE_PROJECTPRODUCT . " AS t1 LEFT JOIN " . TABLE_PROJECT . " AS t2 ON t1.project = t2.id WHERE t1.`product` = '{$productID}' and t2.`type` = 'project' and t2.id " . helper::dbIN($app->user->view->projects) . " ORDER BY `order` desc")->fetchAll();
- $projectIdList = array();
- foreach($projects as $project) $projectIdList[$project->id] = $project->id;
- if($projectID and !isset($projectIdList[$projectID])) $projectID = 0;
- if(empty($projectID)) $projectID = key($projectIdList);
- $params = "productID={$productID}&branch=0&moduleID=0&storyID=0&objectID={$projectID}&bugID=0&planID=0&todoID=0&extra=from=global";
- }
- }
- break;
- case 'task':
- $params = "executionID=0&storyID=0&moduleID=0&taskID=0&todoID=0&extra=from=global";
- break;
- case 'testcase':
- $params = "productID=$productID&branch=&moduleID=0&from=¶m=0&storyID=0&extras=from=global";
- break;
- case 'execution':
- $projectID = isset($_SESSION['project']) ? $_SESSION['project'] : 0;
- $params = "projectID={$projectID}&executionID=0©ExecutionID=0&planID=0&confirm=no&productID=0&extra=from=global";
- break;
- case 'product':
- $params = "programID=&extra=from=global";
- break;
- case 'program':
- $params = "parentProgramID=0&charterID=0&extra=from=global";
- break;
- case 'kanbanspace':
- $isOnlyBody = true;
- $item['innerClass'] = $modalClass;
- $item['data-toggle'] = 'modal';
- $item['data-width'] = '75%';
- break;
- case 'kanban':
- $isOnlyBody = true;
- $item['innerClass'] = $modalClass;
- $item['data-toggle'] = 'modal';
- $item['data-width'] = '75%';
- break;
- case 'board':
- $createMethod = 'createByTemplate';
- $params = 'templateID=1';
- $isOnlyBody = true;
- $item['innerClass'] = $modalClass;
- $item['data-toggle'] = 'modal';
- $item['data-width'] = '75%';
- break;
- }
- $item['url'] = createLink($module, $createMethod, $params, '', $isOnlyBody);
- $items[] = $item;
- }
- if(!$showCreateList) return '';
- return dropdown
- (
- btn
- (
- icon('plus-solid-circle', set::size('lg')),
- setClass('rounded-full bg-gray bg-opacity-10 text-primary-900 text-opacity-70 ring-0 w-9'),
- set::square(true),
- set::size('sm'),
- set::caret(false)
- ),
- set::id('quickAddMenu'),
- set::menu(array('style' => array('color' => 'var(--color-fore)'))),
- set::placement('bottom'),
- set::strategy('fixed'),
- set::arrow(true),
- set::items($items)
- );
- }
- public static function workspaceEntry()
- {
- global $lang;
- $workspace = commonModel::getWorkspaceInfo();
- if(empty($workspace['type'])) return null;
- $opened = $workspace['opened'];
- return array(
- btn
- (
- setClass('rounded-full bg-gray bg-opacity-10 text-primary-900 text-opacity-70 ring-0 w-9'),
- set::size('sm'),
- set::icon($opened ? 'export rotate-90' : 'import rotate-270'),
- set::hint($opened ? $lang->exitWorkspace : $lang->enterWorkspace),
- on::click()->call($opened ? 'exitWorkspace' : 'enterWorkspace')
- ),
- html('<div class="divider h-2 self-center" style="margin:0 0 0 0.75rem"></div>')
- );
- }
- }
|