Merge branch 'master' into sprint/scx_zaction

This commit is contained in:
宋辰轩
2022-06-06 08:30:30 +08:00
159 changed files with 5581 additions and 927 deletions
+10 -204
View File
@@ -1,205 +1,11 @@
Z PUBLIC LICENSE 1.2
本仓库的源代码由以下双重许可涵盖:
(1) Z PUBLIC LICENSE(以下简称ZPL) v1.2。
(2) Affero General Public License(以下简称AGPL) 授权协议。
默认授权协议为ZPL协议。
您也可以选择遵守AGPL协议使用我们的源代码。
许可
Z PUBLIC LICENSE 由青岛易软天创网络科技有限公司(www.cnezsoft.com)起草,简称ZPL协议。
任何人均可使用该协议来发布开源软件,并可对下面协议正文中以下划线标注的空白部分做相应修改,
除此之外的任何内容不得做任何修改。青岛易软天创网络科技有限公司拥有对该协议条款的最终解释权。
前言:
禅道项目管理软件(以下简称该软件)由 青岛易软天创网络科技有限公司(www.cnezsoft.com)开发(以下简称我)。我依法拥有该软件的所有版权。
本着共享开放的角度,我以开放源代码的形式发布该软件。您可以在遵守该协议的前提下使用该软件。
自您安装该软件开始,您和我之间的合同关系自动成立。除非您停止使用该软件或与我有签署额外合同,
您须认真遵循该授权协议约定的每一条款。
我的联系方式:
联系人:徐先生
电话: 4006-8899-23
Email co@cnezsoft.com
QQ 1492153927
地址: 青岛开发区井冈山路东方银座 C座 1106
约定:
下述条款中所指该软件的标志包括如下方面:
该软件源代码及文档中关于该软件的版权提示、文字、图片和链接。
该软件运行时界面上呈现出来的有关该软件的文字、图片和链接。
不包括如下方面:
该软件提供的演示数据中关于该软件的文字、图片和链接。
一、免责
该软件是以开放源代码的方式发行,您使用该软件无需任何费用,因此在使用该软件前,您须知晓:
1.1 我没有对该软件提供任何技术支持的义务,您可联系我购买商业的技术支持。
1.2 我对因使用该软件而产生直接或间接的任何问题不负任何责任。
1.3 开源不等于免费,开源不等于无版权,开源软件的发展需要您我共同的努力。
二、自用该软件
2.1 您个人或您就职的公司(组织)可自由使用该软件,我不对您或您就职公司(组织)的性质做任何限制。
2.2 您可以在您个人或您就职公司(组织)任意数量的电脑上运行该软件,我不对电脑的数量做任何限制。
2.3 您可以对该软件源代码进行修改以适应您个人或您所在公司(组织)使用的要求,您做的改动无需对外发布。
2.4 您个人或您就职公司(组织)使用该软件时,必须保留该软件的所有标志,不得以任何方式隐藏或遮掩任一标志。
三、为用户定制
3.1 您可以使用该软件为您的用户部署各种形式的应用,我不对应用的性质做任何限制。
3.2 您可以使用该软件为您的用户部署任意数量的应用,我不对应用的数量做任何限制。
3.3 您可以对该软件源代码进行修改以适应您的用户的要求,您做的改动无需对外发布。
3.4 您对该软件源代码所做的修改可以源代码或二进制的方式提供给您的用户。
3.5 您使用该软件为您的任一用户部署的任一应用都必须保留该软件所有的标志。
3.6 您使用该软件为您的任一用户部署的任一应用都不得以任何方式隐藏或遮掩该软件任一标志。
四、提供在线服务
4.1 您可以使用该软件搭建在线服务,为您的用户提供服务,我不对该服务及该服务用户的性质做任何限制。
4.2 您可以使用该软件搭建在线服务,为您的用户提供服务,我不对该服务的用户数量做任何限制。
4.3 您可以对该软件源代码进行修改以适应在线服务的要求,您做的改动无需对外发布。
4.4 您使用该软件搭建在线服务时,必须以明确的方式告知您的用户该服务是基于该软件搭建的。
4.5 您使用该软件搭建在线服务为您的任一用户部署的任一应用必须保留该软件所有的标志。
4.6 您使用该软件搭建在线服务为您的任一用户部署的任一应用不得以任何方式隐藏或遮掩该软件任一标志。
4.7 您使用该软件搭建在线服务时,不得为您的用户提供去除、隐藏或遮掩该软件任一标志的功能。
五、无改动发布或集成该软件
5.1 我欢迎并感谢您将该软件发布在您的个人网站、企业官网或者其他的第三方网站。
5.2 我欢迎并感谢您将该软件集成在其他系统中一起发布,比如云服务镜像、操作系统发行版等。
5.3 您在发布或者集成该软件的时候,不得对该软件源码做任何改动。
5.4 您在发布或者集成该软件的时候,须保留该软件的所有标志。
六、发布基于该软件的衍生作品
6.1 我欢迎并感谢您为该软件开发衍生作品。
6.2 您开发的衍生作品中涉及到对该软件源代码改动的地方,须遵循如下条款:
6.2.1 如修改了该软件的源代码,须依据本协议发布修改后的源代码。
6.2.2 如修改了该软件的源代码,须保留代码里面该软件原有的所有标志。
6.2.3 您可以在代码中追加您自己的标志。
6.2.4 您可以对您开发的衍生作品进行收费。
6.2.5 第三方的用户可在遵循6.2所有条款下可继续在您开发的衍生作品基础上进行修改并发布。
6.3 您开发的衍生作品中独立于本软件开发的代码,可以源代码或二进制的方式进行发布,可免费或收费发布。
6.4 您开发的衍生作品不得以任何方式去除、隐藏或遮掩该软件的任一标志。
七、发布基于该软件API的应用
7.1 我欢迎并感谢您为该软件开发基于API的各种应用,比如客户端软件等。
7.2 您基于该软件API机制开发的应用,可以源代码或者二进制的方式进行发布,我对此没有任何限制。
7.3 您基于该软件API机制开发的应用,授权协议可以自行约定,我对此没有任何限制。
7.4 您基于该软件API机制开发的应用,可以免费或者收费发布,我对此没有任何限制。
八、授权例外
如果上述条款无法满足您使用该软件的要求,可联系我签署额外的合同以获得更灵活的授权许可。
九、合同约束
9.1 如果您违反了该协议的任一条款,该授权协议将自动终止,我保留通过法律手段追究责任的权利。
Z PUBLIC LICENSE 1.2
Authorization
Z PUBLIC LICENSE, also known as ZPL Agreement, is drafted by QingDao Nature Easy Soft Network Technology Co,LTD. (,www.cnezsoft.com).
Anyone can use the agreement to publish open source software, and modify the blank underlined part of the following text of the agreement accordingly.
No other text of the agreement shall be changed. QingDao Nature Easy Soft Network Technology Co,LTD has the final authority to interpret the terms of the agreement.
Preface
ZenTaoPMS (Hereinafter referred to as "the software") developed by Nature EasySoft Network Tecnology Co.ltd, QingDao, China (www.cnezsoft.com) (hereinafter referred to I). I'm entitled to all copyright of the software.
The software is released as open source software. You are authorized to use the software as long as you are in compliance with this agreement.
By installation of the software, you agree that a contractual relationship between you and me is automatically established.
You are obliged to fully comply with all the terms of this agreement unless you choose to stop using the software or you have signed additional contracts with me.
My Contact:
Contact: Mr. Xu
Phone: 4006-8899-23
Email: co@cnezsoft.com
QQ: 1492153927
Address: Qingdao Development Zone, the Oriental Kenzo C 1106
We agree:
Indications of the software include:
Notes, texts, pictures and links showing copyright attribution of the software in the source code and related documentation.
and texts, picture and links on the interface of the software when running.
Excluding
texts, picture and links on the interface of the demo versions of the software.
1. Disclaimer
The software is an open-source software, so you are authorized to use the software without paying a fee. Before you start to use it, please note:
1.1 I do not have any obligation to provide technical support for the software. You can contact me to purchase technical support service.
1.2 I'm not responsible for any liability caused by your using the software directly or indirectly.
1.3 Open source software does not mean it's free of charge, neither does it mean the software does not enjoy copyright.
2. For personal use
2.1 You or your company/organization are authorized to use the software for your internal use for both commercial and non-commercial purposes..
2.2 You or your company/organization are authorized to run the software on any number of computers.
2.3 You or your company/organization are authorized to modify the source code of the software to meet your requirements. You do not need to release the modified codes.
2.4 You or your company/organization must keep all the indications of the software when using it. None of the indications can be removed, hidden or obscured in any way.
3. For customized software
3.1 You are authorized to use the software to deploy various forms of application for your users in any way you like.
3.2 You are authorized to use the software to deploy any number of applications for your users.
3.3 You are authorized to modify the source code to meet your user's requirements without releasing the modified codes.
3.4 You are authorized to provide the modified codes to your users in either source code form or binary.
3.5 You must keep all the indications of the software when providing applications to your users.
3.6 None of the indications of the software may be removed, hidden or obscured in any way when you provide applications to your users.
4. Online service
4.1 You are authorized to use the software to build your online service for your users in any way you like.
4.2 You are authorized to use the software to build your online service for any number of your users.
4.3 You are authorized to modify the source codes of the software to meet your user's requirements on online service without releasing the modified codes.
4.4 You must notify your users clearly that your service is based on the software when you use it to build your online service.
4.5 You must keep all the indications of the software when providing online service to your users.
4.6 You must keep all the indications of the software in any application you make for your users. None of the indications can be hidden or obscured in any way.
4.7 You are forbidden from assisting your users by providing tools for your users to remove, hide or obscure any indication of the software when you use the software to build your online service.
5. Publish or integrate the software without modification
5.1 You are authorized to publish the software on your personal sites, corporate official website or other third-party sites.
5.2 You are authorized to integrate the software with other systems, such as cloud virtual machine images, operating system images and so on.
5.3 Do not modify the source code of the software when you publish or integrate it.
5.4 All indications of the software must be kept the same when you publish or integrate the software.
6. Publish derived work based on the software
6.1 You are authorized to develop derived work based on the software.
6.2 The modified codes of the software in your derived work must follow the following terms:
6.2.1 The source codes must be released if you make any modification to the software.
6.2.2 All indications of the software must be kept the same.
6.2.3 You are entitled to add your indications to the modified codes.
6.2.4 You are entitled to charge fees for the derived work you developed based on the software.
6.2.5 You agree to authorize third party users to modify and release the derived work in compliance with 6.2.
6.3 If the codes of the work are independently developed by yourself, You are authorized to release the work in either source code form or binary. You are entitled to charge your users or make it free.
6.4 None of the indications of the software can be removed, hidden or obscured in any way in the derived work you developed.
7. Publish applications based on API of the software
7.1 You are authorized to develop your applications based on the API of the software, for example, client software.
7.2 You are authorized to publish applications you developed based on the API in either source code form or binary.
7.3 You are authorized to use your own license to release applications you developed based on the API.
7.4 You are entitled to release applications you developed based on the API either free or with a charge.
8. Exceptions
If the terms above do not meet your requirements when using the software, please contact me for a more flexible license.
9. Termination
9.1 Violation of any of the terms of the agreement will result in immediate termination of this license. I reserve all rights to take legal actions in case of dispute.
The source code of this repository is covered by the following dual licenses
(1) Z PUBLIC LICENSE(ZPL) v1.2
(2) Affero General Public License(AGPL)
The default license agreement is the ZPL.
Or you can choose to use our source code under the AGPL.
+1 -1
View File
@@ -1 +1 @@
17.0.beta2
17.0
+1 -1
View File
@@ -16,7 +16,7 @@ if(!class_exists('config')){class config{}}
if(!function_exists('getWebRoot')){function getWebRoot(){}}
/* 基本设置。Basic settings. */
$config->version = '17.0.beta2'; // ZenTaoPHP的版本。 The version of ZenTaoPHP. Don't change it.
$config->version = '17.0'; // ZenTaoPHP的版本。 The version of ZenTaoPHP. Don't change it.
$config->liteVersion = '1.0'; // 迅捷版版本。 The version of Lite.
$config->charset = 'UTF-8'; // ZenTaoPHP的编码。 The encoding of ZenTaoPHP.
$config->cookieLife = time() + 2592000; // Cookie的生存时间。The cookie life time.
Regular → Executable
+13 -8
View File
@@ -149,8 +149,10 @@ $filter->todo->export = new stdclass();
$filter->upgrade->license = new stdclass();
$filter->user->login = new stdclass();
$filter->user->edit = new stdclass();
$filter->user->export = new stdclass();
$filter->webhook->bind = new stdclass();
$filter->user->ajaxgetmore = new stdclass();
$filter->user->export = new stdclass();
$filter->repo->ajaxsynccommit = new stdclass();
$filter->repo->apigetrepobyurl = new stdclass();
$filter->search->index = new stdclass();
@@ -320,7 +322,8 @@ $filter->testtask->default->cookie['preProductID'] = 'int';
$filter->todo->export->cookie['checkedItem'] = 'reg::checked';
$filter->user->login->cookie['keepLogin'] = 'equal::on';
$filter->user->login->cookie['keepLogin'] = 'equal::on';
$filter->user->export->cookie['checkedItem'] = 'reg::any';
$filter->block->default->get['hash'] = 'reg::md5';
$filter->block->main->get['blockid'] = 'code';
@@ -359,13 +362,15 @@ $filter->sso->logout->get['token'] = 'reg::md5';
$filter->upgrade->license->get['agree'] = 'equal::true';
$filter->user->login->get['account'] = 'account';
$filter->user->login->get['lang'] = 'reg::lang';
$filter->user->login->get['password'] = 'reg::any';
$filter->user->edit->get['from'] = 'reg::word';
$filter->user->ajaxgetmore->get['search'] = 'reg::any';
$filter->user->ajaxgetmore->get['limit'] = 'int';
$filter->user->ajaxgetmore->get['params'] = 'reg::base64';
$filter->user->export->cookie['checkedItem'] = 'reg::checked';
$filter->user->login->cookie['keepLogin'] = 'equal::on';
$filter->user->login->get['account'] = 'account';
$filter->user->login->get['lang'] = 'reg::lang';
$filter->user->login->get['password'] = 'reg::any';
$filter->user->edit->get['from'] = 'reg::word';
$filter->user->ajaxgetmore->get['search'] = 'reg::any';
$filter->user->ajaxgetmore->get['limit'] = 'int';
$filter->user->ajaxgetmore->get['params'] = 'reg::base64';
$filter->git->cat->get['repoUrl'] = 'reg::base64';
$filter->git->diff->get['repoUrl'] = 'reg::base64';
+72
View File
@@ -0,0 +1,72 @@
<?php
function ioncube_event_handler($err_code, $params)
{
$extensionLink = helper::createLink('extension', 'browse');
$expired = "
<html>
<head>
<meta http-equiv='content-type' content='text/html; charset=utf-8' />
<style>
.extension-btn {min-width: 120px; color: #fff; background-color: #0c64eb; border-color: transparent; display: inline-block; padding: 6px 12px; margin-bottom: 0; font-size: 13px; font-weight: 400; line-height: 18px; text-align: center; white-space: nowrap; vertical-align: middle; cursor: pointer; user-select: none; border: 1px solid transparent; border-radius: 4px; transition: .4s cubic-bezier(.175,.885,.32,1);}
a {text-decoration: none;}
</style>
<title>error</title>
</head>
<body style='font-size: 13px;'>
<h2 style='color:red;text-align:center'>您使用的插件已经过期</h2>
<p style='text-align:left; margin: 0px 20%;'>您的插件已过期,如果需要继续使用,请点击下载更新按钮,跳转到禅道官网插件页面进行下载后再更新插件。</p>
<p style='text-align:left; margin: 0px 20%;'>如果不需要继续使用,可以点击卸载插件按钮,跳转到插件页面卸载插件。</p>
<p style='text-align:left; margin: 0px 20%;'>如果您无法操作,请联系系统管理员进行处理。</p>
<p style='text-align:center; margin: 13px 0px;'>
<a href='https://www.zentao.net/extension-browse.html' target='_blank' class='btn btn-wide btn-primary extension-btn' style='margin-right: 20px;'>下载更新</a>
<a href='{$extensionLink}' target='_top' class='btn btn-wide extension-btn' style='background-color: #fff; border-color: #d6dae3; color: #3c4353;'>卸载插件</a>
</p>
<br /> <br /> <br />
<h2 style='color:red;text-align:center'>Your plugin has expired</h2>
<p style='text-align:left; margin: 0px 20%;'>Your plugin has expired. If you need to keep using it, please click the 'Download Update' button to jump to the ZenTao plugin page to download and update.</p>
<p style='text-align:left; margin: 0px 20%;'>If you decide not to use the plugin, please click the 'Delete Plugin' button to jump to the plugins page to delete it.</p>
<p style='text-align:left; margin: 0px 20%;'>If you do not have permission, please contact the system administrator to deal with it.</p>
<p style='text-align:center; margin: 13px 0px;'>
<a href='https://www.zentao.net/extension-browse.html' target='_blank' class='btn btn-wide btn-primary extension-btn' style='margin-right: 20px;'>Download Update</a>
<a href='{$extensionLink}' target='_top' class='btn btn-wide extension-btn' style='background-color: #fff; border-color: #d6dae3; color: #3c4353;'>Delete Plugin</a>
</p>
</body>
</html>
";
$server = "
<html>
<head>
<meta http-equiv='content-type' content='text/html; charset=utf-8' />
<style>
.extension-btn {min-width: 120px; color: #fff; background-color: #0c64eb; border-color: transparent; display: inline-block; padding: 6px 12px; margin-bottom: 0; font-size: 13px; font-weight: 400; line-height: 18px; text-align: center; white-space: nowrap; vertical-align: middle; cursor: pointer; user-select: none; border: 1px solid transparent; border-radius: 4px; transition: .4s cubic-bezier(.175,.885,.32,1);}
a {text-decoration: none;}
</style>
<title>error</title>
</head>
<body style='font-size: 13px;'>
<h2 style='color:red;text-align:center'>错误的IP地址或MAC地址,或错误的域名访问</h2>
<p style='text-align:left; margin: 0px 20%;'>软件授权的IP地址或MAC地址和当前系统的IP地址或MAC地址不一致,请使用最初授权的服务器。或你访问的域名与绑定的域名不一致。</p>
<p style='text-align:center; margin: 13px 0px;'>
<a href='https://www.zentao.net/extension-browse.html' target='_blank' class='btn btn-wide btn-primary extension-btn' style='margin-right: 20px;'>下载更新</a>
<a href='{$extensionLink}' target='_top' class='btn btn-wide extension-btn' style='background-color: #fff; border-color: #d6dae3; color: #3c4353;'>卸载插件</a>
</p>
<br /> <br /> <br />
<h2 style='color:red;text-align:center'>Wrong IP, MAC address, or domains!</h2>
<p style='text-align:left; margin: 0px 20%;'>The IP, MAC address, or the domains of your server is not the same one in your license.</p>
<p style='text-align:center; margin: 13px 0px;'>
<a href='https://www.zentao.net/extension-browse.html' target='_blank' class='btn btn-wide btn-primary extension-btn' style='margin-right: 20px;'>Download Update</a>
<a href='{$extensionLink}' target='_top' class='btn btn-wide extension-btn' style='background-color: #fff; border-color: #d6dae3; color: #3c4353;'>Delete Plugin</a>
</p>
</body>
</html>
";
if($err_code == ION_LICENSE_EXPIRED)
{
echo $expired;
}
elseif($err_code == ION_LICENSE_SERVER_INVALID)
{
echo $server;
}
exit;
}
File diff suppressed because it is too large Load Diff
+2
View File
@@ -6112,10 +6112,12 @@ CREATE TABLE IF NOT EXISTS `zt_im_chat` (
`ownedBy` varchar(30) NOT NULL DEFAULT '',
`editedBy` varchar(30) NOT NULL DEFAULT '',
`editedDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`mergedDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`lastActiveTime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`lastMessage` int(11) unsigned NOT NULL DEFAULT 0,
`dismissDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`pinnedMessages` text NOT NULL DEFAULT '',
`mergedChats` text NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `gid` (`gid`),
KEY `name` (`name`),
+42 -1
View File
@@ -1,3 +1,45 @@
2022-06-02 17.0
修复的bug
22948 执行需求列表中列名称显示遮挡
22898 项目关闭导致该项目下执行所属项目自动变更了
22904 导入测试用例模板没有区分必填项
22935 添加阶段后二级导航缺失
22936 执行下的任务页面中标签换行显示了
23043 登录后插件过期提示弹窗中的“点击查看”建议改为“查看”
23086 分支的需求无法关联到瀑布项目阶段的子阶段
23061 经典模式开源版升级旗舰版没有重新登录页面有报错
23060 项目执行列表中燃尽图未正常显示
23059 执行列表中燃尽图未正常显示
23054 markdown文档浏览和保存后显示不一样
23027 无法左键直接打开markdown连接
23016 markdown使用表格时浏览和保存后页面不一致
22926 国际版全局设置中用户需求和研发需求无法更改名称
22894 新增强制评审人时有异常弹窗
22531 Bug转需求页面返回链接不对
完成的需求
32968 专业研发看板中版本提交测试单时跳转到测试的测试单中进行创建
32935 专业研发看板项目的二级导航中增加看板列表和版本菜单
32934 专业研发看板的二级导航中增加看板和版本菜单
32926 Scrum看板中泳道按分组条件展示时可以正常显示卡片和进行相关操作
32887 专业研发看板的执行中增加版本管理
32880 需求详情页中增加来源备注字段的显示
32879 专业研发看板项目中增加版本管理
32809 需求评审流程开启时可以设置不需要评审的名单
31393 专业研发看板中泳道按分组条件展示时可以正常显示卡片和进行相关操作
30852 执行的需求列表增加高级表格和自定义列功能
32948 执行关联需求的时候点击需求可以在弹窗里面查看
32947 项目团队列表中增加团队成员数量显示
32183 执行Bug列表中产品下拉组件的交互调整
32182 项目用例列表中产品下拉组件的交互调整
32181 项目Bug列表中产品下拉组件的交互调整
27721 插件过期提示优化
26364 执行任务列表中任务名称前面的模块标签对齐显示
14374 项目关联需求的时候点击需求可以在弹窗里面查看
14141 插件到期前增加提示
13316 执行团队列表中增加团队成员数量显示
32972 实现导出用户模板功能
32440 实现导出用户功能
15620 实现导入用户功能
2022-05-26 17.0.beta2
修复的bug
22755 导入用例确认页面相关研发需求没有必填项标识
@@ -87,7 +129,6 @@
14704 版本关联需求页面默认选中测试完毕的需求
14703 发布关联需求页面默认选中测试完毕的需求
30544 后台自定义的必填项中Bug提交页面增加所属执行的选项
30442 Bug增加转任务的操作功能
29842 多人任务支持并行模式
29766 已关闭的空间可以进行激活操作
29765 已关闭的看板可以进行激活操作
+15 -7
View File
@@ -1,3 +1,11 @@
禅道项目管理软件使用 Z PUBLIC LICENSE(以下简称ZPL) 1.2 和 Affero General Public License(以下简称AGPL) 双授权协议。
默认使用ZPL协议,您也可以选择遵守AGPL协议使用我们的软件。
ZenTao Project Management Software is licensed under the Z PUBLIC LICENSE (ZPL) 1.2 and the Affero General Public License (AGPL).
The ZPL is applied by default, or you can choose to use our software under the AGPL.
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Z PUBLIC LICENSE 1.2
许可
@@ -18,7 +26,7 @@ Z PUBLIC LICENSE 由青岛易软天创网络科技有限公司(www.easycorp.cn
电话: 4006-8899-23
Email co@zentao.net
QQ 1492153927
地址: 青岛开发区长江路232号国贸中心C座2单元2902室
地址: 青岛开发区长江路金石国际广场A座3205
约定:
@@ -35,8 +43,8 @@ QQ 1492153927
该软件是以开放源代码的方式发行,您使用该软件无需任何费用,因此在使用该软件前,您须知晓:
1.1 我没有对该软件提供任何技术支持的义务,您可联系我购买商业的技术支持。
1.2 我对因使用该软件而产生直接或间接的任何问题不负任何责任。
1.1 我没有对该软件提供任何技术支持的义务,您可联系我购买商业的技术支持。
1.2 我对因使用该软件而产生直接或间接的任何问题不负任何责任。
1.3 开源不等于免费,开源不等于无版权,开源软件的发展需要您我共同的努力。
二、自用该软件
@@ -77,10 +85,10 @@ QQ 1492153927
6.1 我欢迎并感谢您为该软件开发衍生作品。
6.2 您开发的衍生作品中涉及到对该软件源代码改动的地方,须遵循如下条款:
6.2.1 如修改了该软件的源代码,须依据本协议发布修改后的源代码。
6.2.2 如修改了该软件的源代码,须保留代码里面该软件原有的所有标志。
6.2.3 您可以在代码中追加您自己的标志。
6.2.4 您可以对您开发的衍生作品进行收费。
6.2.1 如修改了该软件的源代码,须依据本协议发布修改后的源代码。
6.2.2 如修改了该软件的源代码,须保留代码里面该软件原有的所有标志。
6.2.3 您可以在代码中追加您自己的标志。
6.2.4 您可以对您开发的衍生作品进行收费。
6.2.5 第三方的用户可在遵循6.2所有条款下可继续在您开发的衍生作品基础上进行修改并发布。
6.3 您开发的衍生作品中独立于本软件开发的代码,可以源代码或二进制的方式进行发布,可免费或收费发布。
@@ -1,3 +1,3 @@
<script>
$('#mainMenu .dropdown-menu li:last-child').remove();
$('#mainMenu .dropdown-menu #importBug').remove();
</script>
@@ -0,0 +1,3 @@
<script>
$('#itemContent .btn-toolbar a:last-child').remove();
</script>
@@ -0,0 +1,3 @@
<script>
$('#itemContent .btn-toolbar a:last-child').remove();
</script>
+3 -1
View File
@@ -159,7 +159,9 @@ public function getCardGroupByExecution($executionID, $browseType = 'all', $orde
if($cell->type == 'task')
{
$cardData['name'] = $object->name;
$cardData['name'] = $object->name;
$cardData['status'] = $object->status;
$cardData['left'] = $object->left;
}
else
{
+1 -5
View File
@@ -153,12 +153,10 @@
common::printIcon('story', 'assignTo', "storyID=$story->id", $story, 'button', '', '', 'iframe showinonlybody', true);
common::printIcon('story', 'close', "storyID=$story->id", $story, 'button', '', '', 'iframe showinonlybody', true);
common::printIcon('story', 'activate', "storyID=$story->id", $story, 'button', '', '', 'iframe showinonlybody', true);
if(isset($this->config->maxVersion) and $this->app->tab == 'project' and common::hasPriv('story', 'importToLib')) echo html::a('#importToLib', "<i class='icon icon-assets'></i> " . $this->lang->story->importToLib, '', 'class="btn" data-toggle="modal"');
if($this->config->edition == 'max' and $this->app->tab == 'project' and common::hasPriv('story', 'importToLib')) echo html::a('#importToLib', "<i class='icon icon-assets'></i> " . $this->lang->story->importToLib, '', 'class="btn" data-toggle="modal"');
if($from == 'execution' and strpos('draft,closed', $story->status) === false) common::printIcon('task', 'create', "execution=$param&storyID=$story->id&moduleID=$story->module", $story, 'button', 'plus', '', 'showinonlybody');
echo $this->buildOperateMenu($story, 'view');
echo "<div class='divider'></div>";
common::printIcon('story', 'edit', "storyID=$story->id", $story);
common::printIcon('story', 'create', "productID=$story->product&branch=$story->branch&moduleID=$story->module&storyID=$story->id&executionID=0&bugID=0&planID=0&todoID=0&extra=&type=$story->type", $story, 'button', 'copy', '', '', '', "data-width='1050'");
@@ -247,7 +245,6 @@
<th><?php echo $lang->story->assignedTo;?></th>
<td><?php if($story->assignedTo) echo zget($users, $story->assignedTo) . $lang->at . $story->assignedDate;?></td>
</tr>
<?php if(!$checkSuperReviewed):?>
<tr>
<th><?php echo $lang->story->reviewers;?></th>
<td>
@@ -262,7 +259,6 @@
?>
</td>
</tr>
<?php endif;?>
<tr>
<th><?php echo $lang->story->reviewedDate;?></th>
<td><?php if($story->reviewedBy) echo $story->reviewedDate;?></td>
@@ -55,7 +55,7 @@
}
$colspan = count($visibleFields) + 3;
?>
<form method='post' class='load-indicator batch-actions-form form-ajax' enctype='multipart/form-data' id="batchCreateForm">
<form method='post' class='batch-actions-form' target='hiddenwin' enctype='multipart/form-data' id="batchCreateForm">
<div class="table-responsive">
<table class="table table-form" id="tableBody">
<thead>
+36 -33
View File
@@ -617,16 +617,19 @@ class baseRouter
*/
public function setSuperVars()
{
$URI = $_SERVER['REQUEST_URI'];
if(strpos($URI, '?') !== false)
if(isset($_SERVER['REQUEST_URI']))
{
$parsedURL = parse_url($URI);
if(isset($parsedURL['query']))
$URI = $_SERVER['REQUEST_URI'];
if(strpos($URI, '?') !== false)
{
parse_str($parsedURL['query'], $parsedQuery);
foreach($parsedQuery as $key => $value)
$parsedURL = parse_url($URI);
if(isset($parsedURL['query']))
{
if(!isset($_GET[$key])) $_GET[$key] = $value;
parse_str($parsedURL['query'], $parsedQuery);
foreach($parsedQuery as $key => $value)
{
if(!isset($_GET[$key])) $_GET[$key] = $value;
}
}
}
}
@@ -2075,6 +2078,19 @@ class baseRouter
$defaultParams[$name] = $default;
}
/**
* 根据PATH_INFO或者GET方式设置请求的参数。
* Set params according PATH_INFO or GET.
*/
if($this->config->requestType != 'GET')
{
$this->setParamsByPathInfo($defaultParams);
}
else
{
$this->setParamsByGET($defaultParams);
}
if ('cli' === PHP_SAPI)
{
if ($this->params)
@@ -2088,19 +2104,6 @@ class baseRouter
}
else
{
/**
* 根据PATH_INFO或者GET方式设置请求的参数。
* Set params according PATH_INFO or GET.
*/
if($this->config->requestType != 'GET')
{
$this->setParamsByPathInfo($defaultParams);
}
else
{
$this->setParamsByGET($defaultParams);
}
if($this->config->framework->filterParam == 2)
{
$_GET = validater::filterParam($_GET, 'get');
@@ -2493,13 +2496,16 @@ class baseRouter
$modulePath = $this->getExtensionRoot() . 'saas' . DS;
if(file_exists($modulePath . $path)) return $modulePath . $path;
/* 1. 如果通用版本里有此模块,优先使用。 If module is in the open edition, use it. */
$modulePath = $this->getModuleRoot($appName);
/* 1. 最后尝试在定制开发中寻找。 Finally, try to find the module in the custom dir. */
$modulePath = $this->getExtensionRoot() . 'custom' . DS;
if(file_exists($modulePath . $path)) return $modulePath . $path;
/* 2. 尝试查找喧喧是否有此模块。 Try to find the module in other editon. */
$modulePath = $this->getExtensionRoot() . 'xuan' . DS;
if(file_exists($modulePath . $path)) return $modulePath . $path;
/* 2. 如果设置过vision,尝试在vision中查找。 If vision is set, try to find the module in the vision. */
if($this->config->vision != 'rnd')
{
$modulePath = $this->getExtensionRoot() . $this->config->vision . DS;
if(file_exists($modulePath . $path)) return $modulePath . $path;
}
/* 3. 尝试查找商业版本是否有此模块。 Try to find the module in other editon. */
if($this->config->edition != 'open')
@@ -2508,15 +2514,12 @@ class baseRouter
if(file_exists($modulePath . $path)) return $modulePath . $path;
}
/* 4. 如果设置过vision,尝试在vision中查找。 If vision is set, try to find the module in the vision. */
if($this->config->vision != 'rnd')
{
$modulePath = $this->getExtensionRoot() . $this->config->vision . DS;
if(file_exists($modulePath . $path)) return $modulePath . $path;
}
/* 4. 尝试查找喧喧是否有此模块。 Try to find the module in other editon. */
$modulePath = $this->getExtensionRoot() . 'xuan' . DS;
if(file_exists($modulePath . $path)) return $modulePath . $path;
/* 5. 最后尝试在定制开发中寻找。 Finally, try to find the module in the custom dir. */
$modulePath = $this->getExtensionRoot() . 'custom' . DS;
/* 5. 如果通用版本里有此模块,优先使用。 If module is in the open edition, use it. */
$modulePath = $this->getModuleRoot($appName);
if(file_exists($modulePath . $path)) return $modulePath . $path;
return '';
+3 -1
View File
@@ -501,6 +501,8 @@ class baseHTML
$gobackList = isset($_COOKIE['goback']) ? json_decode($_COOKIE['goback'], true) : array();
$gobackLink = isset($gobackList[$tab]) ? $gobackList[$tab] : '';
if(strpos($misc, 'data-app') === false) $misc .= " data-app='" . $tab . "'";
/* If the link of the referer is not the link of the current page or the link of the index, the cookie and gobackLink will be updated. */
if(!preg_match("/(m=|\/)(index|search|$currentModule)(&f=|-)(index|buildquery|$currentMethod)(&|-|\.)?/", strtolower($refererLink)))
{
@@ -509,7 +511,7 @@ class baseHTML
setcookie('goback', json_encode($gobackList), $config->cookieLife, $config->webRoot, '', $config->cookieSecure, false);
}
return "<a href='{$gobackLink}' class='btn btn-back $class' data-app='$tab' $misc>{$label}</a>";
return "<a href='{$gobackLink}' class='btn btn-back $class' $misc>{$label}</a>";
}
/**
+1
View File
@@ -359,6 +359,7 @@ class gitlab
if($parent == '.') $parent = '/';
if($parent == '') $parent = '/';
$list = $this->tree($parent, 0);
$file = new stdclass();
foreach($list as $node) if($node->path == $entry) $file = $node;
+1 -1
View File
@@ -949,7 +949,7 @@ class actionModel extends model
->beginIF($libs and !$this->app->user->admin)->andWhere("IF(objectType != 'doclib', '1=1', objectID " . helper::dbIN(array_keys($libs)) . ') ')->fi()
->beginIF($actionCondition)->andWhere("($actionCondition)")->fi()
/* Filter out client login/logout actions. */
->andWhere('action')->notin('disconnectxuanxuan,loginxuanxuan')
->andWhere('action')->notin('disconnectxuanxuan,reconnectxuanxuan,loginxuanxuan,logoutxuanxuan')
->orderBy($orderBy)
->page($pager)
->fetchAll();
+2
View File
@@ -0,0 +1,2 @@
.c-leftStories {width: 95px;}
.c-leftTasks, .c-consumed, .c-leftBugs {width: 85px;}
+8
View File
@@ -109,6 +109,14 @@ config.cannotPlaceInRight = '<?php echo $lang->block->cannotPlaceInRight; ?>';
var module = '<?php echo $module?>';
var useGuest = <?php echo $useGuest ? 'true' : 'false';?>;
<?php $remind = $this->loadModel('misc')->getPluginRemind();?>
<?php if(!empty($remind)):?>
var myModalTrigger = new $.zui.ModalTrigger({title:'<?php echo $lang->misc->expiredTipsTitle;?>', custom: function(){return <?php echo json_encode($remind);?>}, width:'600px'});
var result = myModalTrigger.show();
$('#pluginButton').click(function(){myModalTrigger.close()});
$('#cancelButton').click(function(){myModalTrigger.close()});
<?php endif;?>
<?php /* Check annual remind */ ?>
$(function()
{
+4 -4
View File
@@ -14,11 +14,11 @@
<th class='c-status'><?php echo $lang->project->status;?></th>
<?php if($longBlock):?>
<th class='c-number'><?php echo $lang->project->teamCount;?></th>
<th class='c-number text-right'><?php echo $lang->task->consumed;?></th>
<th class='c-number text-right c-consumed'><?php echo $lang->task->consumed;?></th>
<th class='c-budget text-right'><?php echo $lang->project->budget;?></th>
<th class='c-number'><?php echo $lang->project->leftStories;?></th>
<th class='c-number'><?php echo $lang->project->leftTasks;?></th>
<th class='c-number'><?php echo $lang->project->leftBugs;?></th>
<th class='c-number c-leftStories'><?php echo $lang->project->leftStories;?></th>
<th class='c-number c-leftTasks'><?php echo $lang->project->leftTasks;?></th>
<th class='c-number c-leftBugs'><?php echo $lang->project->leftBugs;?></th>
<?php endif;?>
</tr>
</thead>
+6 -3
View File
@@ -266,15 +266,18 @@ class branch extends control
if(empty($product) or $product->type == 'normal') return;
$branches = $this->loadModel('branch')->getList($productID, $projectID, $param, 'order', null, $withMainBranch);
$branchOption = array();
$branchTagOption = array();
foreach($branches as $branchInfo)
{
$branchOption[$branchInfo->id] = $branchInfo->name;
$branchTagOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '');
}
if(!isset($branchTagOption[$oldBranch]))
{
$branch = $this->branch->getById($oldBranch, $productID, '');
$branchTagOption[$oldBranch] = $oldBranch == BRANCH_MAIN ? $branch : ($branch->name . ($branch->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : ''));
}
return print(html::select('branch', strpos($param, 'active') !== false ? $branchOption : $branchTagOption, $oldBranch, "class='form-control' onchange='loadBranch(this)'"));
return print(html::select('branch', $branchTagOption, $oldBranch, "class='form-control' onchange='loadBranch(this)'"));
}
/**
+50 -27
View File
@@ -324,12 +324,12 @@ class bug extends control
if($this->app->tab == 'execution')
{
if(isset($output['executionID'])) $this->loadModel('execution')->setMenu($output['executionID']);
$execution = $this->dao->findById((int)$output['executionID'])->from(TABLE_EXECUTION)->fetch();
$execution = $this->dao->findById($this->session->execution)->from(TABLE_EXECUTION)->fetch();
if($execution->type == 'kanban')
{
$this->loadModel('kanban');
$regionPairs = $this->kanban->getRegionPairs($execution->id, 0, 'execution');
$regionID = isset($output['regionID']) ? $output['regionID'] : key($regionPairs);
$regionID = !empty($output['regionID']) ? $output['regionID'] : key($regionPairs);
$lanePairs = $this->kanban->getLanePairsByRegion($regionID, 'bug');
$laneID = isset($output['laneID']) ? $output['laneID'] : key($lanePairs);
@@ -411,8 +411,10 @@ class bug extends control
$execution = $this->loadModel('execution')->getByID($executionID);
if($executionID == $output['executionID'] and $this->app->tab == 'execution' and $execution->type == 'kanban')
{
$kanbanData = $this->loadModel('kanban')->getRDKanban($executionID, $this->session->execLaneType ? $this->session->execLaneType : 'all');
$kanbanData = json_encode($kanbanData);
$execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
$execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
$kanbanData = $this->loadModel('kanban')->getRDKanban($executionID, $execLaneType, 'id_desc', 0, $execGroupBy);
$kanbanData = json_encode($kanbanData);
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'callback' => "parent.updateKanban($kanbanData, 0)"));
}
else
@@ -707,8 +709,10 @@ class bug extends control
$execution = $this->loadModel('execution')->getByID($executionID);
if($execution->type == 'kanban' and $this->app->tab == 'execution')
{
$kanbanData = $this->loadModel('kanban')->getRDKanban($executionID, $this->session->execLaneType ? $this->session->execLaneType : 'all');
$kanbanData = json_encode($kanbanData);
$execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
$execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
$kanbanData = $this->loadModel('kanban')->getRDKanban($executionID, $execLaneType, 'id_desc', 0, $execGroupBy);
$kanbanData = json_encode($kanbanData);
return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)"));
}
else
@@ -736,9 +740,9 @@ class bug extends control
{
$this->loadModel('kanban');
$regionPairs = $this->kanban->getRegionPairs($executionID, 0, 'execution');
$regionID = isset($output['regionID']) ? $output['regionID'] : key($regionPairs);
$regionID = !empty($output['regionID']) ? $output['regionID'] : key($regionPairs);
$lanePairs = $this->kanban->getLanePairsByRegion($regionID, 'bug');
$laneID = isset($output['laneID']) ? $output['laneID'] : key($lanePairs);
$laneID = !empty($output['laneID']) ? $output['laneID'] : key($lanePairs);
$this->view->executionType = $execution->type;
$this->view->regionID = $regionID;
@@ -957,9 +961,9 @@ class bug extends control
if(isonlybody())
{
$execution = $this->loadModel('execution')->getByID($bug->execution);
if(isset($execution->type) and $execution->type == 'kanban' and $this->app->tab == 'execution' and $kanbanGroup == 'default')
if(isset($execution->type) and $execution->type == 'kanban' and $this->app->tab == 'execution')
{
$kanbanData = $this->loadModel('kanban')->getRDKanban($bug->execution, $this->session->execLaneType ? $this->session->execLaneType : 'all');
$kanbanData = $this->loadModel('kanban')->getRDKanban($bug->execution, $this->session->execLaneType ? $this->session->execLaneType : 'all', 'id_desc', 0, $kanbanGroup);
$kanbanData = json_encode($kanbanData);
return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)"));
@@ -978,8 +982,15 @@ class bug extends control
$executionID = $bug->execution;
$projectID = $bug->project;
$currentModuleID = $bug->module;
$product = $this->loadModel('product')->getByID($productID);
$this->bug->checkBugExecutionPriv($bug);
if(!isset($this->products[$bug->product]))
{
$this->products[$bug->product] = $product->name;
$this->view->products = $this->products;
}
/* Set the menu. */
if($this->app->tab == 'project') $this->loadModel('project')->setMenu($bug->project);
if($this->app->tab == 'execution') $this->loadModel('execution')->setMenu($bug->execution);
@@ -1006,7 +1017,7 @@ class bug extends control
{
$products = array();
$productList = $this->config->CRProduct ? $this->product->getOrderedProducts('all', 40, $bug->project) : $this->product->getOrderedProducts('normal', 40, $bug->project);
foreach($productList as $product) $products[$product->id] = $product->name;
foreach($productList as $productInfo) $products[$productInfo->id] = $productInfo->name;
$this->view->products = $products;
}
@@ -1016,7 +1027,6 @@ class bug extends control
$this->view->position[] = $this->lang->bug->edit;
/* Assign. */
$product = $this->loadModel('product')->getByID($productID);
$allBuilds = $this->loadModel('build')->getBuildPairs($productID, 'all', 'noempty');
if($executionID)
{
@@ -1060,6 +1070,11 @@ class bug extends control
$branchOption[$branchInfo->id] = $branchInfo->name;
$branchTagOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '');
}
if(!isset($branchTagOption[$bug->branch]))
{
$bugBranch = $this->branch->getById($bug->branch, $bug->product, '');
$branchTagOption[$bug->branch] = $bug->branch == BRANCH_MAIN ? $bugBranch : ($bugBranch->name . ($bugBranch->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : ''));
}
$moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'bug', $startModuleID = 0, $bug->branch);
if(!isset($moduleOptionMenu[$bug->module])) $moduleOptionMenu += $this->tree->getModulesName($bug->module);
@@ -1271,9 +1286,9 @@ class bug extends control
{
$bug = $this->bug->getById($bugID);
$execution = $this->loadModel('execution')->getByID($bug->execution);
if(isset($execution->type) and $execution->type == 'kanban' and $this->app->tab == 'execution' and $kanbanGroup == 'default')
if(isset($execution->type) and $execution->type == 'kanban' and $this->app->tab == 'execution')
{
$kanbanData = $this->loadModel('kanban')->getRDKanban($bug->execution, $this->session->execLaneType ? $this->session->execLaneType : 'all');
$kanbanData = $this->loadModel('kanban')->getRDKanban($bug->execution, $this->session->execLaneType ? $this->session->execLaneType : 'all', 'id_desc', 0, $kanbanGroup);
$kanbanData = json_encode($kanbanData);
return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)"));
@@ -1480,9 +1495,11 @@ class bug extends control
$execution = $this->loadModel('execution')->getByID($bug->execution);
if(isset($execution->type) and $execution->type == 'kanban' and $this->app->tab == 'execution')
{
$regionID = isset($output['regionID']) ? $output['regionID'] : 0;
$kanbanData = $this->loadModel('kanban')->getRDKanban($bug->execution, $this->session->execLaneType ? $this->session->execLaneType : 'all', 'id_desc', $regionID);
$kanbanData = json_encode($kanbanData);
$regionID = !empty($output['regionID']) ? $output['regionID'] : 0;
$execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
$execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
$kanbanData = $this->loadModel('kanban')->getRDKanban($bug->execution, $execLaneType, 'id_desc', $regionID, $execGroupBy);
$kanbanData = json_encode($kanbanData);
return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData, $regionID)"));
}
@@ -1572,9 +1589,11 @@ class bug extends control
{
if(isset($execution->type) and $execution->type == 'kanban' and $this->app->tab == 'execution')
{
$regionID = isset($output['regionID']) ? $output['regionID'] : 0;
$kanbanData = $this->loadModel('kanban')->getRDKanban($bug->execution, $this->session->execLaneType ? $this->session->execLaneType : 'all', 'id_desc', $regionID);
$kanbanData = json_encode($kanbanData);
$regionID = !empty($output['regionID']) ? $output['regionID'] : 0;
$execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
$execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
$kanbanData = $this->loadModel('kanban')->getRDKanban($bug->execution, $execLaneType, 'id_desc', $regionID, $execGroupBy);
$kanbanData = json_encode($kanbanData);
return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData, $regionID)"));
}
@@ -1671,9 +1690,11 @@ class bug extends control
$execution = $this->loadModel('execution')->getByID($bug->execution);
if(isset($execution->type) and $execution->type == 'kanban' and $this->app->tab == 'execution')
{
$regionID = isset($output['regionID']) ? $output['regionID'] : 0;
$kanbanData = $this->loadModel('kanban')->getRDKanban($bug->execution, $this->session->execLaneType ? $this->session->execLaneType : 'all', 'id_desc', $regionID);
$kanbanData = json_encode($kanbanData);
$regionID = !empty($output['regionID']) ? $output['regionID'] : 0;
$execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
$execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
$kanbanData = $this->loadModel('kanban')->getRDKanban($bug->execution, $execLaneType, 'id_desc', $regionID, $execGroupBy);
$kanbanData = json_encode($kanbanData);
return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData, $regionID)"));
}
@@ -1729,9 +1750,11 @@ class bug extends control
$execution = $this->loadModel('execution')->getByID($bug->execution);
if(isset($execution->type) and $execution->type == 'kanban' and $this->app->tab == 'execution')
{
$regionID = isset($output['regionID']) ? $output['regionID'] : 0;
$kanbanData = $this->loadModel('kanban')->getRDKanban($bug->execution, $this->session->execLaneType ? $this->session->execLaneType : 'all', 'id_desc', $regionID);
$kanbanData = json_encode($kanbanData);
$regionID = !empty($output['regionID']) ? $output['regionID'] : 0;
$execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
$execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
$kanbanData = $this->loadModel('kanban')->getRDKanban($bug->execution, $execLaneType, 'id_desc', $regionID, $execGroupBy);
$kanbanData = json_encode($kanbanData);
return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData, $regionID)"));
}
@@ -2293,7 +2316,7 @@ class bug extends control
public function ajaxGetDropMenu($productID, $module, $method, $extra = '')
{
$products = array();
if(!empty($extra)) $products = $this->product->getProducts($extra, $this->config->CRProduct ? 'all' : 'noclosed', 'program desc, line desc, ');
if(!empty($extra)) $products = $this->product->getProducts($extra, 'all', 'program desc, line desc, ');
$this->view->link = $this->product->getProductLink($module, $method, $extra);
$this->view->productID = $productID;
+4 -3
View File
@@ -485,7 +485,7 @@ function loadProjectBuilds(projectID)
if(page == 'create')
{
var link = createLink('build', 'ajaxGetProjectBuilds', 'projectID=' + projectID + '&productID=' + productID + '&varName=openedBuild&build=' + oldOpenedBuild + "&branch=" + branch);
var link = createLink('build', 'ajaxGetProjectBuilds', 'projectID=' + projectID + '&productID=' + productID + '&varName=openedBuild&build=&branch=' + branch);
$.get(link, function(data)
{
if(!data) data = '<select id="openedBuild" name="openedBuild" class="form-control" multiple=multiple></select>';
@@ -629,7 +629,8 @@ function loadProductBranches(productID, param)
$('#branch').next('.picker').remove();
var branchStatus = page == 'create' ? 'active' : 'all';
var param = "productID=" + productID + "&oldBranch=0&param=" + branchStatus;
var oldBranch = page == 'edit' ? bugBranch : 0;
var param = "productID=" + productID + "&oldBranch=" + oldBranch + "&param=" + branchStatus;
if(typeof(tab) != 'undefined' && (tab == 'execution' || tab == 'project')) param += "&projectID=" + objectID;
$.get(createLink('branch', 'ajaxGetBranches', param), function(data)
{
@@ -713,7 +714,7 @@ function notice()
if(typeof(branch) == 'undefined') branch = 0;
var link = createLink('release', 'create', 'productID=' + $('#product').val() + '&branch=' + branch);
if(config.onlybody != 'yes') link += link.indexOf('?') >= 0 ? '&onlybody=yes' : '?onlybody=yes';
html += '<a href="' + link + '" data-toggle="modal" data-type="iframe" style="padding-right:5px">' + createBuild + '</a> ';
html += '<a href="' + link + '" data-toggle="modal" data-type="iframe" style="padding-right:5px">' + createRelease + '</a> ';
html += '<a href="javascript:loadProductBuilds(' + $('#product').val() + ')">' + refresh + '</a>';
}
else
+5
View File
@@ -2,3 +2,8 @@ $(document).ready(function()
{
limitIframeLevel();
});
$('#tostory').click(function()
{
if(!confirm(confrimToStory)) return false;
});
+1
View File
@@ -208,6 +208,7 @@ $lang->bug->executionAccessDenied = "You access to the {$lang->executionCommon}
$lang->bug->stepsNotEmpty = "The reproduction step cannot be empty.";
$lang->bug->confirmUnlinkBuild = "Replacing the solution version will disassociate the bug from the old version. Are you sure you want to disassociate the bug from %s?";
$lang->bug->noSwitchBranch = 'The linked module of Bug%s is not in the current branch. It will be omitted.';
$lang->bug->confirmToStory = 'The bug will be closed automatically after transferring to requirements, and the reason for closing is that the bug has been converted to requirements status.';
/* Template. */
$lang->bug->tplStep = "<p>[Steps]</p><br/>";
+1
View File
@@ -208,6 +208,7 @@ $lang->bug->executionAccessDenied = "您无权访问该Bug所属的{$lang->execu
$lang->bug->stepsNotEmpty = "重现步骤不能为空。";
$lang->bug->confirmUnlinkBuild = "更换解决版本将取消与旧版本的关联,您确定取消该bug与%s的关联吗?";
$lang->bug->noSwitchBranch = 'Bug%s所属模块不在当前分支下,将自动忽略。';
$lang->bug->confirmToStory = '转需求后Bug将自动关闭,关闭原因为转为需求。';
/* 模板。*/
$lang->bug->tplStep = "<p>[步骤]</p><br/>";
+17 -2
View File
@@ -482,6 +482,7 @@ class bugModel extends model
->beginIF($projectID)->andWhere('project')->eq($projectID)->fi()
->andWhere('execution')->in(array_keys($executions))
->andWhere('deleted')->eq(0)
->beginIF(!$this->app->user->admin)->andWhere('project')->in('0,' . $this->app->user->view->projects)->fi()
->orderBy($orderBy)->page($pager)->fetchAll();
}
@@ -2378,6 +2379,7 @@ class bugModel extends model
->beginIF($modules)->andWhere('t1.module')->in($modules)->fi()
->beginIF($projectID)->andWhere('t1.project')->eq($projectID)->fi()
->andWhere('t1.deleted')->eq(0)
->beginIF(!$this->app->user->admin)->andWhere('t1.project')->in('0,' . $this->app->user->view->projects)->fi()
->orderBy($orderBy)->page($pager)->fetchAll();
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'bug');
@@ -2406,6 +2408,7 @@ class bugModel extends model
->beginIF($projectID)->andWhere('project')->eq($projectID)->fi()
->andWhere('execution')->in(array_keys($executions))
->andWhere('deleted')->eq(0)
->beginIF(!$this->app->user->admin)->andWhere('project')->in('0,' . $this->app->user->view->projects)->fi()
->orderBy($orderBy)->page($pager)->fetchAll();
}
@@ -2430,6 +2433,7 @@ class bugModel extends model
->beginIF($projectID)->andWhere('project')->eq($projectID)->fi()
->andWhere('execution')->in(array_keys($executions))
->andWhere('deleted')->eq(0)
->beginIF(!$this->app->user->admin)->andWhere('project')->in('0,' . $this->app->user->view->projects)->fi()
->orderBy($orderBy)->page($pager)->fetchAll();
}
@@ -2454,6 +2458,7 @@ class bugModel extends model
->beginIF($projectID)->andWhere('project')->eq($projectID)->fi()
->andWhere('execution')->in(array_keys($executions))
->andWhere('deleted')->eq(0)
->beginIF(!$this->app->user->admin)->andWhere('project')->in('0,' . $this->app->user->view->projects)->fi()
->orderBy($orderBy)->page($pager)->fetchAll();
}
@@ -2479,6 +2484,7 @@ class bugModel extends model
->beginIF($projectID)->andWhere('project')->eq($projectID)->fi()
->andWhere('execution')->in(array_keys($executions))
->andWhere('deleted')->eq(0)
->beginIF(!$this->app->user->admin)->andWhere('project')->in('0,' . $this->app->user->view->projects)->fi()
->orderBy($orderBy)->page($pager)->fetchAll();
}
@@ -2505,6 +2511,7 @@ class bugModel extends model
->beginIF($branch !== 'all')->andWhere('branch')->in($branch)->fi()
->beginIF($modules)->andWhere('module')->in($modules)->fi()
->beginIF($projectID)->andWhere('project')->eq($projectID)->fi()
->beginIF(!$this->app->user->admin)->andWhere('project')->in('0,' . $this->app->user->view->projects)->fi()
->orderBy($orderBy)->page($pager)->fetchAll();
}
@@ -2534,6 +2541,7 @@ class bugModel extends model
->andWhere('deleted')->eq(0)
->andWhere('deadline')->ne('0000-00-00')
->andWhere('deadline')->lt(helper::today())
->beginIF(!$this->app->user->admin)->andWhere('project')->in('0,' . $this->app->user->view->projects)->fi()
->orderBy($orderBy)->page($pager)->fetchAll();
}
@@ -2563,6 +2571,7 @@ class bugModel extends model
->beginIF($status == 'toclosed')->andWhere('status')->eq('resolved')->fi()
->beginIF($projectID)->andWhere('project')->eq($projectID)->fi()
->andWhere('deleted')->eq(0)
->beginIF(!$this->app->user->admin)->andWhere('project')->in('0,' . $this->app->user->view->projects)->fi()
->orderBy($orderBy)->page($pager)
->fetchAll();
}
@@ -2590,6 +2599,7 @@ class bugModel extends model
->beginIF($projectID)->andWhere('project')->eq($projectID)->fi()
->andWhere('openedDate')->lt($lastEditedDate)
->andWhere('deleted')->eq(0)
->beginIF(!$this->app->user->admin)->andWhere('project')->in('0,' . $this->app->user->view->projects)->fi()
->andWhere('status')->ne('closed')->orderBy($orderBy)->page($pager)->fetchAll();
}
@@ -2614,6 +2624,7 @@ class bugModel extends model
->beginIF($projectID)->andWhere('project')->eq($projectID)->fi()
->andWhere('execution')->in(array_keys($executions))
->andWhere('deleted')->eq(0)
->beginIF(!$this->app->user->admin)->andWhere('project')->in('0,' . $this->app->user->view->projects)->fi()
->orderBy($orderBy)->page($pager)->fetchAll();
}
@@ -2642,6 +2653,7 @@ class bugModel extends model
->andWhere('t2.version > t1.storyVersion')
->andWhere('t1.execution')->in(array_keys($executions))
->andWhere('t1.deleted')->eq(0)
->beginIF(!$this->app->user->admin)->andWhere('t1.project')->in('0,' . $this->app->user->view->projects)->fi()
->orderBy($orderBy)
->page($pager)
->fetchAll();
@@ -2672,6 +2684,7 @@ class bugModel extends model
->andWhere('deleted')->eq(0)
->andWhere('status')->ne('closed')
->andWhere('id')->in($actionIDList)
->beginIF(!$this->app->user->admin)->andWhere('project')->in('0,' . $this->app->user->view->projects)->fi()
->orderBy($sort)
->page($pager)
->fetchAll();
@@ -2726,8 +2739,9 @@ class bugModel extends model
$bugQuery = $this->getBugQuery($this->session->bugQuery);
/* If search criteria don't have products, append the selected product from the top left dropdown-menu. */
if(is_array($productIDList)) $productIDList = implode(',', $productIDList);
if(strpos($bugQuery, '`product` =') === false) $bugQuery .= ' AND `product` in (' . $productIDList . ')';
if(strpos($bugQuery, '`product`') === false) $bugQuery .= ' AND `product` IN (' . $productIDList . ')';
$allBranch = "`branch` = 'all'";
if($branch !== 'all' and strpos($bugQuery, '`branch` =') === false) $bugQuery .= " AND `branch` in('0','$branch')";
@@ -2745,6 +2759,7 @@ class bugModel extends model
->fi()
->andWhere('deleted')->eq(0)
->beginIF(!$this->app->user->admin)->andWhere('project')->in('0,' . $this->app->user->view->projects)->fi()
->orderBy($orderBy)->page($pager)->fetchAll();
return $bugs;
}
@@ -3313,7 +3328,7 @@ class bugModel extends model
if($type == 'view') $menu .= $this->buildMenu('bug', 'activate', $params, $bug, $type, '', '', "text-success iframe showinonlybody", true);
if($type == 'view' && $this->app->tab != 'product')
{
$menu .= $this->buildMenu('bug', 'toStory', $toStoryParams, $bug, $type, $this->lang->icons['story'], '', '', '', "data-app='qa'", $this->lang->bug->toStory);
$menu .= $this->buildMenu('bug', 'toStory', $toStoryParams, $bug, $type, $this->lang->icons['story'], '', '', '', "data-app='product' id='tostory'", $this->lang->bug->toStory);
$menu .= $this->buildMenu('bug', 'createCase', $convertParams, $bug, $type, 'sitemap');
}
if($type == 'view')
+1 -1
View File
@@ -17,7 +17,7 @@ foreach($products as $product)
}
$productsPinYin = common::convert2Pinyin($productNames);
$linkHtml = $this->product->setParamsForLink($module, $link, $projectID, 0);
$productsHtml = html::a($linkHtml, $this->lang->bug->allProduct, '', "class='text-important' title='{$this->lang->bug->allProduct}' data-key='" . zget($productsPinYin, $this->lang->bug->allProduct, '') . "' data-app='{$this->app->tab}'");
$productsHtml = count($products) > 1 ? html::a($linkHtml, $this->lang->bug->allProduct, '', "class='text-important' title='{$this->lang->bug->allProduct}' data-key='" . zget($productsPinYin, $this->lang->bug->allProduct, '') . "' data-app='{$this->app->tab}'") : '';
foreach($products as $product)
{
+1
View File
@@ -27,6 +27,7 @@ js::set('oldResolvedBuild' , $bug->resolvedBuild);
js::set('systemMode' , $config->systemMode);
js::set('confirmUnlinkBuild' , sprintf($lang->bug->confirmUnlinkBuild, zget($resolvedBuilds, $bug->resolvedBuild)));
js::set('tab' , $this->app->tab);
js::set('bugBranch' , $bug->branch);
if($this->app->tab == 'execution') js::set('objectID', $bug->execution);
if($this->app->tab == 'project') js::set('objectID', $bug->project);
?>
+1
View File
@@ -13,6 +13,7 @@
<?php include '../../common/view/header.html.php';?>
<?php include '../../common/view/kindeditor.html.php';?>
<?php js::set('sysurl', common::getSysUrl());?>
<?php js::set('confrimToStory', $lang->bug->confirmToStory);?>
<?php $browseLink = $app->session->bugList ? $app->session->bugList : inlink('browse', "productID=$bug->product");?>
<?php if(strpos($_SERVER["QUERY_STRING"], 'isNotice=1') === false):?>
<div id="mainMenu" class="clearfix">
+1
View File
@@ -1 +1,2 @@
#branch_chosen .chosen-single {border-left: 0px;}
#noProduct {display: inline-block; width: 110%;}
+5 -3
View File
@@ -544,13 +544,15 @@ class buildModel extends model
if($type == 'browse')
{
$executionID = $tab == 'execution' ? $extraParams['executionID'] : $build->execution;
$execution = $this->loadModel('execution')->getByID($executionID);
$testtaskApp = (!empty($execution->type) and $execution->type == 'kanban') ? 'data-app="qa"' : "data-app='{$tab}'";
if(common::hasPriv('build', 'linkstory') and common::canBeChanged('build', $build)) $menu .= $this->buildMenu('build', 'view', "{$params}&type=story&link=true", $build, $type, 'link', '', '', '', "data-app={$tab}", $this->lang->build->linkStory);
$menu .= $this->buildMenu('testtask', 'create', "product=$build->product&execution={$executionID}&build=$build->id", $build, $type, 'bullhorn', '', '', '', "data-app='{$tab}'");
$menu .= $this->buildMenu('testtask', 'create', "product=$build->product&execution={$executionID}&build=$build->id", $build, $type, 'bullhorn', '', '', '', $testtaskApp);
if($tab == 'execution') $menu .= $this->buildMenu('execution', 'bug', "execution={$extraParams['executionID']}&productID={$extraParams['productID']}&orderBy=status&build=$build->id", $build, $type, '', '', '', '', $this->lang->execution->viewBug);
if($tab == 'project') $menu .= $this->buildMenu('build', 'view', "{$params}&type=generatedBug", $build, $type, 'bug', '', '', '', "data-app='project'", $this->lang->project->bug);
if($tab == 'execution' and !empty($execution->type) and $execution->type != 'kanban') $menu .= $this->buildMenu('execution', 'bug', "execution={$extraParams['executionID']}&productID={$extraParams['productID']}&orderBy=status&build=$build->id", $build, $type, '', '', '', '', $this->lang->execution->viewBug);
if($tab == 'project' or empty($execution->type) or $execution->type == 'kanban') $menu .= $this->buildMenu('build', 'view', "{$params}&type=generatedBug", $build, $type, 'bug', '', '', '', "data-app='$tab'", $this->lang->project->bug);
$menu .= $this->buildMenu('build', 'edit', $params, $build, $type);
$menu .= $this->buildMenu('build', 'delete', $params, $build, $type, 'trash', 'hiddenwin');
+1 -1
View File
@@ -46,7 +46,7 @@ $lang->collapseMenu = 'Collapse';
$lang->preference = 'Preference';
$lang->tutorialAB = 'Tutorial';
$lang->runInfo = "<div class='row'><div class='u-1 a-center' id='debugbar'>Time %s MS, Memory %s KB, Query %s. </div></div>";
$lang->agreement = "I have read and agreed to the terms and conditions of <a href='http://zpl.pub/page/zplv12.html' target='_blank'> Z PUBLIC LICENSE 1.2 </a>. <span class='text-danger'>Without authorization, I should not remove, hide or cover any logos/links of ZenTao.</span>";
$lang->agreement = "I have read and agreed to the terms and conditions. <span class='text-danger'>Without authorization, I should not remove, hide or cover any logos/links of ZenTao.</span>";
$lang->designedByAIUX = "<a href='https://api.zentao.net/goto.php?item=aiux' class='link-aiux' target='_blank'><i class='icon icon-aiux'></i> AIUX</a>";
$lang->reset = 'Reset';
+9 -3
View File
@@ -291,9 +291,15 @@ $lang->waterfall->menu->design['subMenu']->ads = array('link' => "{$lang->d
$lang->waterfall->menu->design['subMenu']->bysearch = array('link' => '<a href="javascript:;" class="querybox-toggle"><i class="icon-search icon"></i> ' . $lang->searchAB . '</a>');
/* Kanban project menu. */
$lang->kanban->menu = new stdclass();
$lang->kanban->menuOrder = array();
$lang->kanban->dividerMenu = '';
$lang->kanbanProject = new stdclass();
$lang->kanbanProject->menu = new stdclass();
$lang->kanbanProject->menu->index = array('link' => "{$lang->kanban->common}|project|index|project=%s");
$lang->kanbanProject->menu->build = array('link' => "{$lang->build->common}|project|build|project=%s");
$lang->kanbanProject->menuOrder = array();
$lang->kanbanProject->menuOrder[5] = 'index';
$lang->kanbanProject->menuOrder[10] = 'build';
$lang->kanbanProject->dividerMenu = '';
/* Execution menu. */
$lang->execution->homeMenu = new stdclass();
+1 -1
View File
@@ -46,7 +46,7 @@ $lang->collapseMenu = '收起导航';
$lang->preference = '个性化设置';
$lang->tutorialAB = '新手引导';
$lang->runInfo = "<div class='row'><div class='u-1 a-center' id='debugbar'>时间: %s 毫秒, 内存: %s KB, 查询: %s. </div></div>";
$lang->agreement = "已阅读并同意<a href='http://zpl.pub/page/zplv12.html' target='_blank'>《Z PUBLIC LICENSE授权协议1.2》</a>。<span class='text-danger'>未经许可,不得去除、隐藏或遮掩禅道软件的任何标志及链接。</span>";
$lang->agreement = "已阅读并同意。<span class='text-danger'>未经许可,不得去除、隐藏或遮掩禅道软件的任何标志及链接。</span>";
$lang->designedByAIUX = "<a href='https://api.zentao.net/goto.php?item=aiux' class='link-aiux' target='_blank'><i class='icon icon-aiux'></i> 艾体验设计</a>";
$lang->reset = '重填';
+8 -2
View File
@@ -1703,8 +1703,8 @@ EOD;
$executionList = $app->dbh->query("SELECT id,name,parent FROM " . TABLE_EXECUTION . " WHERE `project` = '{$object->project}' AND `deleted` = '0' $userCondition $orderBy")->fetchAll();
foreach($executionList as $execution)
{
if($execution->id == $executionID) continue;
if(isset($executionPairs[$execution->parent])) unset($executionPairs[$execution->parent]);
if($execution->id == $executionID) continue;
$executionPairs[$execution->id] = $execution->name;
}
@@ -2289,7 +2289,7 @@ EOD;
'my' => array('changepassword'),
'message' => array('ajaxgetmessage'),
);
if(!empty($this->app->user->modifyPassword) and (!isset($beforeValidMethods[$module]) or !in_array($method, $beforeValidMethods[$module]))) return print(js::locate(helper::createLink('my', 'changepassword', '', '', true)));
if(!empty($this->app->user->modifyPassword) and (!isset($beforeValidMethods[$module]) or !in_array($method, $beforeValidMethods[$module]))) return print(js::locate(helper::createLink('my', 'changepassword')));
if($this->isOpenMethod($module, $method)) return true;
if(!$this->loadModel('user')->isLogon() and $this->server->php_auth_user) $this->user->identifyByPhpAuth();
if(!$this->loadModel('user')->isLogon() and $this->cookie->za) $this->user->identifyByCookie();
@@ -2404,6 +2404,12 @@ EOD;
$rights = $app->user->rights['rights'];
$acls = $app->user->rights['acls'];
/* White list of import method. */
if(in_array($module, array('user', 'task', 'story', 'bug', 'testcase')) and $method == 'showimport')
{
if(isset($rights[$module]['import']) and commonModel::hasDBPriv($object, $module, 'import')) return true;
}
if(isset($rights[$module][$method]))
{
if(!commonModel::hasDBPriv($object, $module, $method)) return false;
+16 -1
View File
@@ -67,7 +67,22 @@
<style>.comment .comment-content{width: 98%}</style>
<?php endif;?>
<div class='article-content comment'>
<div class='comment-content'><?php echo strip_tags($action->comment) == $action->comment ? nl2br($action->comment) : $action->comment;?></div>
<div class='comment-content'>
<?php
if(strpos($action->comment, '<pre class="prettyprint lang-html">') !== false)
{
$before = explode('<pre class="prettyprint lang-html">', $action->comment);
$after = explode('</pre>', $before[1]);
$htmlCode = $after[0];
$text = $before[0] . htmlspecialchars($htmlCode) . $after[1];
echo $text;
}
else
{
echo strip_tags($action->comment) == $action->comment ? nl2br($action->comment) : $action->comment;
}
?>
</div>
</div>
<?php if($canEditComment):?>
<form method='post' class='comment-edit-form' action='<?php echo $this->createLink('action', 'editComment', "actionID=$action->id")?>'>
+3
View File
@@ -73,6 +73,9 @@ class company extends control
/* Get users. */
$users = $this->company->getUsers($browseType, $type, $queryID, $deptID, $sort, $pager);
/* Process the sql, get the conditon partion, save it to session. */
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'user', true);
/* Remove passwd. */
foreach($users as $user) unset($user->password);
+2
View File
@@ -5,3 +5,5 @@
#userList tbody td {white-space: nowrap; overflow: hidden;}
.space-sm + .text-center {display: none;}
.c-role, .c-company, .c-visits {width: 90px;}
.btn-group a.btn-primary {border-right: 1px solid rgba(255,255,255,0.3);}
+24 -12
View File
@@ -29,19 +29,31 @@ js::set('confirmDelete', $lang->user->confirmDelete);
<a class="btn btn-link querybox-toggle" id='bysearchTab'><i class="icon icon-search muted"></i> <?php echo $lang->user->search;?></a>
</div>
<div class='btn-toolbar pull-right'>
<?php common::printLink('user', 'batchCreate', "dept={$deptID}", "<i class='icon icon-plus'></i> " . $lang->user->batchCreate, '', "class='btn btn-secondary'");?>
<?php if(commonModel::isTutorialMode()):?>
<?php
if(commonModel::isTutorialMode())
{
$wizardParams = helper::safe64Encode("dept=$deptID");
$link = $this->createLink('tutorial', 'wizard', "module=user&method=create&params=$wizardParams");
echo html::a($link, "<i class='icon icon-plus'></i> {$lang->user->create}", '', "class='btn btn-primary create-user-btn'");
}
else
{
common::printLink('user', 'create', "dept={$deptID}", "<i class='icon icon-plus'></i> " . $lang->user->create, '', "class='btn btn-primary create-user-btn'");
}
$wizardParams = helper::safe64Encode("dept=$deptID");
$link = $this->createLink('tutorial', 'wizard', "module=user&method=create&params=$wizardParams");
echo html::a($link, "<i class='icon icon-plus'></i> {$lang->user->create}", '', "class='btn btn-primary create-user-btn'");
?>
<?php else:?>
<?php if(common::hasPriv('user', 'create') and common::hasPriv('user', 'batchCreate')):?>
<?php
$createUserLink = $this->createLink('user', 'create', "dept={$deptID}");
$batchCreateUserLink = $this->createLink('user', 'batchCreate', "dept={$deptID}");
?>
<div class='btn-group dropdown'>
<?php common::printLink('user', 'create', "dept={$deptID}", "<i class='icon icon-plus'></i> " . $lang->user->create, '', "class='btn btn-primary create-user-btn'");?>
<button type='button' class='btn btn-primary dropdown-toggle' data-toggle='dropdown'><span class='caret'></span></button>
<ul class='dropdown-menu'>
<li><?php echo html::a($createUserLink, $lang->user->create);?></li>
<li><?php echo html::a($batchCreateUserLink, $lang->user->batchCreate);?></li>
</ul>
</div>
<?php else:?>
<?php common::printLink('user', 'batchCreate', "dept={$deptID}", "<i class='icon icon-plus'></i> " . $lang->user->batchCreate, '', "class='btn btn-secondary'");?>
<?php common::printLink('user', 'create', "dept={$deptID}", "<i class='icon icon-plus'></i> " . $lang->user->create, '', "class='btn btn-primary'");?>
<?php endif;?>
<?php endif;?>
</div>
</div>
<div id='mainContent' class='main-row fade'>
@@ -93,7 +105,7 @@ js::set('confirmDelete', $lang->user->confirmDelete);
<tr>
<td class='c-id'>
<?php if($canBatchEdit):?>
<?php echo html::checkbox('users', array($user->account => '')) . sprintf('%03d', $user->id);?>
<?php echo html::checkbox('users', array($user->id => '')) . sprintf('%03d', $user->id);?>
<?php else:?>
<?php printf('%03d', $user->id);?>
<?php endif;?>
+19 -6
View File
@@ -76,10 +76,14 @@ class custom extends control
if($module == 'story')
{
$this->view->depts = $this->loadModel('dept')->getDeptPairs();
$this->view->forceReviewAll = zget($this->config->$module, 'forceReviewAll', 'false');
$this->view->forceReview = zget($this->config->$module, 'forceReview', '');
$this->view->forceReviewRoles = zget($this->config->$module, 'forceReviewRoles', '');
$this->view->forceReviewDepts = zget($this->config->$module, 'forceReviewDepts', '');
$this->view->forceNotReview = zget($this->config->$module, 'forceNotReview', '');
$this->view->forceNotReviewRoles = zget($this->config->$module, 'forceNotReviewRoles', '');
$this->view->forceNotReviewDepts = zget($this->config->$module, 'forceNotReviewDepts', '');
}
$this->view->users = $module == 'story' ? $this->user->getCanCreateStoryUsers() : $this->user->getPairs('noclosed|nodeleted');
@@ -124,15 +128,23 @@ class custom extends control
elseif($module == 'story' and $field == 'review')
{
$data = fixer::input('post')
->setDefault('forceReviewAll', 0)
->setDefault('forceReviewDepts', '')
->setDefault('forceNotReviewDepts', '')
->join('forceReview', ',')
->join('forceReviewRoles', ',')
->join('forceReviewDepts', ',')
->join('forceReviewAll', ',')
->setIF(isset($this->post->forceReviewAll), 'forceReviewAll', 1)
->join('forceNotReview', ',')
->join('forceNotReviewRoles', ',')
->join('forceNotReviewDepts', ',')
->get();
foreach($data as $key => $value)
{
if($key == 'needReview') continue;
if(strpos($key, 'Not') and $data->needReview == 0) $data->$key = '';
if(!strpos($key, 'Not') and $data->needReview == 1) $data->$key = '';
}
$this->loadModel('setting')->setItems("system.$module@{$this->config->vision}", $data);
}
elseif($module == 'story' and $field == 'reviewRules')
@@ -145,7 +157,8 @@ class custom extends control
$review = fixer::input('post')->get();
if($review->needReview) $data = fixer::input('post')->join('forceNotReview', ',')->remove('forceReview')->get();
if(!$review->needReview) $data = fixer::input('post')->join('forceReview', ',')->remove('forceNotReview')->get();
if(!isset($data->forceReview)) $data->forceReview = '';
if(!isset($data->forceReview)) $data->forceReview = '';
if(!isset($data->forceNotReview)) $data->forceNotReview = '';
$this->loadModel('setting')->setItems("system.$module", $data);
$reviewCase = isset($review->reviewCase) ? $review->reviewCase : 0;
@@ -549,7 +562,7 @@ class custom extends control
{
if($_POST)
{
$this->loadModel('setting')->setItem("system.common.CRKanban@{$this->config->vision}", $this->post->kanban);
$this->loadModel('setting')->setItem("system.common.CRKanban", $this->post->kanban);
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'reload'));
}
+1 -1
View File
@@ -1 +1 @@
.thWidth {width: 80px !important;}
.thWidth {width: 90px !important;}
+36 -26
View File
@@ -38,13 +38,12 @@ EOT;
</style>
<?php if($module == 'story' and $field == 'review'):?>
<style>
.reviewBox > th {width: 95px !important;}
.reviewBox > td {width: 500px !important;}
.table-form>tbody>tr>th {width: 120px !important}
.checkbox-primary {margin-bottom: 0px; width: 82px !important;}
.storyReviewTip {padding-left: 95px;}
.storyReviewTip > div , .storyNotReviewTip > div {padding-left: 120px;}
<?php if($app->getClientLang() != 'zh-cn' and $app->getClientLang() != 'zh-tw'):?>
.reviewBox > th {width: 160px !important;}
.storyReviewTip {padding-left: 160px;}
.table-form>tbody>tr>th {width: 180px !important;}
.storyReviewTip > div , .storyNotReviewTip > div {padding-left: 180px;}
<?php endif;?>
</style>
<?php endif;?>
@@ -108,24 +107,33 @@ EOT;
<?php if($module == 'story'):?>
<tr>
<?php $space = ($app->getClientLang() != 'zh-cn' and $app->getClientLang() != 'zh-tw') ? ' ': '';?>
<td colspan='3'><div class='storyReviewTip'><?php echo sprintf($lang->custom->notice->forceReview, $lang->$module->common) . $lang->custom->notice->storyReviewTip;?></td>
<td colspan='3' class='storyReviewTip<?php if($needReview) echo " hidden"?>'><div><?php echo sprintf($lang->custom->notice->forceReview, $lang->$module->common) . $lang->custom->notice->storyReviewTip;?></td>
<td colspan='3' class='storyNotReviewTip<?php if(!$needReview) echo " hidden"?>'><div><?php echo sprintf($lang->custom->notice->forceNotReview, $lang->$module->common) . $lang->custom->notice->storyReviewTip;?></td>
</tr>
<tr id='userBox'>
<tr id='userBox' class='forceReview<?php if($needReview) echo " hidden"?>'>
<th><?php echo $lang->custom->forceReview . $space . $lang->custom->account;?></th>
<td><?php echo html::select('forceReview[]', $users, $forceReview, "class='form-control chosen' multiple");?></td>
<td>
<?php echo html::checkbox('forceReviewAll', array('1' => $lang->custom->allUsers), $forceReviewAll);?>
<icon class='icon icon-help' data-toggle='popover' data-trigger='focus hover' data-placement='right' data-tip-class='text-muted popover-sm' data-content="<?php echo $lang->custom->notice->selectAllTip;?>"></icon>
</td>
</tr>
<tr id='roleBox'>
<tr id='roleBox' class='forceReview<?php if($needReview) echo " hidden"?>'>
<th><?php echo $lang->custom->forceReview . $space . $lang->custom->role;?></th>
<td><?php echo html::select('forceReviewRoles[]', $lang->user->roleList, $forceReviewRoles, "class='form-control chosen' multiple");?></td>
</tr>
<tr id='deptBox'>
<tr id='deptBox' class='forceReview<?php if($needReview) echo " hidden"?>'>
<th><?php echo $lang->custom->forceReview . $space . $lang->custom->dept;?></th>
<td><?php echo html::select('forceReviewDepts[]', $depts, $forceReviewDepts, "class='form-control chosen' multiple");?></td>
</tr>
<tr id='userBox' class='forceNotReview<?php if(!$needReview) echo " hidden"?>'>
<th><?php echo $lang->custom->forceNotReview . $space . $lang->custom->account;?></th>
<td><?php echo html::select('forceNotReview[]', $users, $forceNotReview, "class='form-control chosen' multiple");?></td>
</tr>
<tr id='roleBox' class='forceNotReview<?php if(!$needReview) echo " hidden"?>'>
<th><?php echo $lang->custom->forceNotReview . $space . $lang->custom->role;?></th>
<td><?php echo html::select('forceNotReviewRoles[]', $lang->user->roleList, $forceNotReviewRoles, "class='form-control chosen' multiple");?></td>
</tr>
<tr id='deptBox' class='forceNotReview<?php if(!$needReview) echo " hidden"?>'>
<th><?php echo $lang->custom->forceNotReview . $space . $lang->custom->dept;?></th>
<td><?php echo html::select('forceNotReviewDepts[]', $depts, $forceNotReviewDepts, "class='form-control chosen' multiple");?></td>
</tr>
<?php endif;?>
<?php if($module == 'testcase'):?>
<?php js::set('oldNeedReview', $needReview);?>
@@ -271,31 +279,33 @@ $(function()
{
$('[data-toggle="popover"]').popover();
toggleBox($("input[name^='forceReviewAll']").prop('checked'));
$("input[name^='forceReviewAll']").click(function()
$("input[name='needReview']").change(function()
{
toggleBox($(this).prop('checked'));
});
needReviewChange();
})
/**
* Toggle box.
* When needReview change.
*
* @param bool $checked
* @access public
* @return void
*/
function toggleBox(checked)
function needReviewChange()
{
$('#roleBox').toggleClass('hidden', checked);
$('#deptBox').toggleClass('hidden', checked);
if(checked)
var needReview = $("input[name='needReview']:checked").val();
if(needReview == 1)
{
$('#forceReview').val('').attr('disabled', 'disabled').trigger('chosen:updated');
$('.storyReviewTip').addClass('hidden');
$('.forceReview').addClass('hidden');
$('.storyNotReviewTip').removeClass('hidden');
$('.forceNotReview').removeClass('hidden');
}
else
{
$('#forceReview').removeAttr('disabled', 'disabled').trigger('chosen:updated');
$('.forceReview').removeClass('hidden');
$('.storyReviewTip').removeClass('hidden');
$('.forceNotReview').addClass('hidden');
$('.storyNotReviewTip').addClass('hidden');
}
}
})
+1
View File
@@ -1,5 +1,6 @@
<?php
$config->datatable->moduleAlias['product-browse'] = 'story';
$config->datatable->moduleAlias['execution-story'] = 'story';
$config->datatable->moduleAlias['execution-task'] = 'task';
$config->datatable->moduleAlias['testtask-cases'] = 'testcase';
$config->datatable->moduleAlias['program-project'] = 'project';
+1 -13
View File
@@ -317,7 +317,7 @@ class doc extends control
if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $docID));
$objectID = zget($lib, $lib->type, '');
$params = "type={$lib->type}&objectID=$objectID&libID={$lib->id}&docID=" . $docResult['id'];
$link = isonlybody() ? 'parent' : $this->createLink('doc', 'objectLibs', $params, 'html');
$link = isonlybody() ? 'parent' : $this->createLink('doc', 'objectLibs', $params, 'html') . '#app=' . $this->app->tab;
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $link));
}
@@ -527,12 +527,6 @@ class doc extends control
if(!(defined('RUN_MODE') && RUN_MODE == 'api')) $this->locate($browseLink);
}
if($doc->contentType == 'markdown')
{
$doc->content = commonModel::processMarkdown($doc->content);
$doc->digest = commonModel::processMarkdown($doc->digest);
}
/* Check priv when lib is product or project. */
$lib = $this->doc->getLibByID($doc->lib);
$type = $lib->type;
@@ -998,12 +992,6 @@ class doc extends control
$doc->keywords = str_replace("", ',', $doc->keywords);
$doc->keywords = explode(',', $doc->keywords);
}
if($doc->contentType == 'markdown')
{
$doc->content = commonModel::processMarkdown($doc->content);
$doc->digest = commonModel::processMarkdown($doc->digest);
}
}
if(isset($doc) and ($doc->type == 'text' || $doc->type == 'article'))
+2
View File
@@ -18,6 +18,8 @@
#content .title {max-width: 54%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
#content .detail-content {padding-left: 10px;}
#mainContent .scrollbar-hover {max-height: 2000px; overflow: scroll;}
#content .editor-preview {background: #fff}
#content .CodeMirror{border: none}
#sidebar {width: 275px;}
#sidebar>.cell {width: 100%;}
#sidebar>.sidebar-toggle {left: 3px; right: auto;}
+1 -1
View File
@@ -2307,7 +2307,7 @@ EOT;
{
foreach($rel->snap['modules'] as $module)
{
$this->buildTree($treeMenu, 'api', 0, $rootID, $module, $moduleDocs, $docID, $moduleID);
$this->buildTree($treeMenu, 'api', 0, $rootID, (object)$module, $moduleDocs, $docID, $moduleID);
}
}
else
+19
View File
@@ -100,6 +100,10 @@
echo "</div></div>";
}
}
elseif($doc->contentType == 'markdown')
{
echo "<textarea id='markdownContent' /></textarea>";
}
else
{
echo $doc->content;
@@ -283,3 +287,18 @@
</div>
<?php endif;?>
<?php include '../../common/view/syntaxhighlighter.html.php';?>
<?php if($doc->contentType == 'markdown'):?>
<?php css::import($jsRoot . "markdown/simplemde.min.css");?>
<?php js::import($jsRoot . 'markdown/simplemde.min.js'); ?>
<?php js::set('markdownText', $doc->content);?>
<script>
$(function()
{
var simplemde = new SimpleMDE({element: $("#markdownContent")[0],toolbar:false, status: false});
simplemde.value(markdownText);
simplemde.togglePreview();
$('#content .CodeMirror .editor-preview a').attr('target', '_blank');
})
</script>
<?php endif;?>
+17 -9
View File
@@ -855,6 +855,7 @@ class execution extends control
$this->view->branchGroups = $branchGroups;
$this->view->canBeChanged = common::canModify('execution', $execution); // Determines whether an object is editable.
$this->view->showBranch = $showBranch;
$this->view->storyStages = $this->product->batchGetStoryStage($stories);
$this->display();
}
@@ -883,7 +884,7 @@ class execution extends control
$this->loadModel('datatable');
/* Save session. */
$this->session->set('bugList', $this->app->getURI(true), 'qa');
$this->session->set('bugList', $this->app->getURI(true), 'execution');
$type = strtolower($type);
$queryID = ($type == 'bysearch') ? (int)$param : 0;
@@ -2115,8 +2116,10 @@ class execution extends control
$this->loadModel('story');
if(empty($groupBy)) $groupBy = 'default';
/* Set Session. */
$this->session->set('execLaneType', $browseType);
$this->session->set('execGroupBy', $groupBy);
$this->session->set('storyList', $this->app->getURI(true), 'execution');
$this->lang->execution->menu = new stdclass();
@@ -2197,16 +2200,16 @@ class execution extends control
* @access public
* @return void
*/
public function taskKanban($executionID, $browseType = '', $orderBy = 'order_asc', $groupBy = '')
public function taskKanban($executionID, $browseType = 'all', $orderBy = 'order_asc', $groupBy = '')
{
if(empty($browseType)) $browseType = $this->session->kanbanType ? $this->session->kanbanType : 'all';
if(empty($groupBy)) $groupBy = 'default';
/* Save to session. */
$uri = $this->app->getURI(true);
$this->app->session->set('taskList', $uri, 'execution');
$this->app->session->set('bugList', $uri, 'qa');
$this->app->session->set('kanbanType', $browseType, 'execution');
$this->app->session->set('execLaneType', $browseType);
$this->app->session->set('execGroupBy', $groupBy);
/* Load language. */
$this->app->loadLang('task');
@@ -2853,8 +2856,10 @@ class execution extends control
{
if($this->app->tab == 'execution' and $object->type == 'kanban')
{
$kanbanData = $this->loadModel('kanban')->getRDKanban($objectID, $this->session->execLaneType ? $this->session->execLaneType : 'all');
$kanbanData = json_encode($kanbanData);
$execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
$execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
$kanbanData = $this->loadModel('kanban')->getRDKanban($objectID, $execLaneType, 'id_desc', 0, $execGroupBy);
$kanbanData = json_encode($kanbanData);
return print(js::closeModal('parent', '', "parent.updateKanban($kanbanData)"));
}
@@ -3656,10 +3661,11 @@ class execution extends control
* @param int $productID
* @param string $fromMethod
* @param string $extra
* @param string $param
* @access public
* @return void
*/
public function importPlanStories($executionID, $planID, $productID = 0, $fromMethod = 'story', $extra = '')
public function importPlanStories($executionID, $planID, $productID = 0, $fromMethod = 'story', $extra = '', $param = '')
{
$planStories = $planProducts = array();
$planStory = $this->loadModel('story')->getPlanStories($planID);
@@ -3687,7 +3693,8 @@ class execution extends control
}
$moduleName = 'execution';
$param = "executionID=$executionID";
if(empty($param)) $param = "executionID=$executionID";
if(!empty($param)) $param = str_replace(array(',', ' '), array('&', ''), $param);
if($execution->type == 'project')
{
$moduleName = 'projectstory';
@@ -3700,7 +3707,8 @@ class execution extends control
$lang->executionCommon = $lang->execution->kanban;
include $this->app->getModulePath('', 'execution') . 'lang/' . $this->app->getClientLang() . '.php';
}
if($count != 0) echo js::alert(sprintf($this->lang->execution->haveDraft, $count)) . js::locate($this->createLink($moduleName, in_array($execution->type, array('sprint','project')) ? 'story' : 'kanban', $param));
if($count != 0) echo js::alert(sprintf($this->lang->execution->haveDraft, $count)) . js::locate($this->createLink($moduleName, $fromMethod, $param));
return print(js::locate(helper::createLink($moduleName, $fromMethod, $param)));
}
+7 -5
View File
@@ -4,7 +4,7 @@
.table tbody > tr.table-children.table-child-top {border-top: 2px solid #cbd0db;}
.table tbody > tr.table-children.table-child-bottom {border-bottom: 2px solid #cbd0db;}
.table td.has-child > a:not(.plan-toggle) {max-width: 90%; max-width: calc(100% - 30px); display: inline-block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
.table td.has-child > .plan-toggle {color: #838a9d; position: relative; top: 1px;}
.table td.has-child > .plan-toggle {color: #838a9d; position: relative; top: 1px;overflow: initial !important;}
.table td.has-child > .plan-toggle:hover {color: #006af1; cursor: pointer;}
.table td.has-child > .plan-toggle > .icon {font-size: 16px; display: inline-block; transition: transform .2s; -ms-transform:rotate(-90deg); -moz-transform:rotate(-90deg); -o-transform:rotate(-90deg); -webkit-transform:rotate(-90deg); transform: rotate(-90deg);}
.table td.has-child > .plan-toggle > .icon:before {text-align: left;}
@@ -14,17 +14,19 @@
td.hours {text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
td.flex {display: flex; flex-flow: row nowrap; justify-content: flex-start; align-items: center;}
td.flex span.label-badge {margin-left: 5px; min-width: 50px;}
td.flex span.label-badge.label-light {min-width: 22px;}
td.flex span.project-type-label {margin-left: 5px; min-width: 36px;}
@-moz-document url-prefix() {.main-table tbody > tr.table-children > td:first-child::before {width: 4px;}}
.c-status, .c-date, .c-percent, .c-attribute {width: 80px;}
.c-status, .c-date, .c-percent, .c-attribute, .c-end, .c-begin {width: 80px;}
.c-estimate, .c-consumed, .c-left, .c-pm {width: 70px;}
.c-progress, .c-burn {width: 60px;}
.c-code, .c-percent, .c-realBegan, .c-end, .c-begin, .c-realEnd{width: 100px;}
.c-action {text-align: center;}
.c-progress {width: 40px;}
.c-burn {width: 60px;}
.c-code, .c-percent, .c-realBegan, .c-realEnd{width: 90px;}
.c-name > span {margin-right: 2px;}
td.flex span.project-type-label {min-width: 40px;}
.label-children {min-width:22px !important;}
.c-name > a, .table-children .text-left > a {padding-left: 5px;}
.has-child > span {margin-right: 5px;}
.c-name > .text-ellipsis {text-overflow: clip}
+4 -2
View File
@@ -1,5 +1,7 @@
[lang^='en'] #tipsModal {margin-top: 10%; max-width: 620px;}
[lang^='zh-cn'] #tipsModal {margin-top: 10%; max-width: 530px;}
[lang^='en'] #newTipsModal {margin-top: 10%; max-width: 620px;}
[lang^='zh-cn'] #newTipsModal {margin-top: 10%; max-width: 530px;}
[lang^='zh-cn'] #classicTipsModal {margin-top: 10%; max-width: 420px;}
[lang^='en'] #classicTipsModal {margin-top: 10%; max-width: 450px;}
.chosen-container-single .chosen-single > span {max-width: 100%;}
#copyProjectModal {padding: 0;}
+4 -3
View File
@@ -61,7 +61,7 @@
.region .kanban-item {position: relative}
.kanban-card {padding: 8px 14px !important; min-height: 60px !important; height: auto;}
.kanban-card {min-height: 60px !important; height: auto;}
.cardcolor {width:40px; height: 14px; float: left; margin-left: 5px; margin-right: 5px; margin-top: 2px; border-radius: 2px;}
#cardcolormenu {padding:2px 2px; width: 100px;}
@@ -97,14 +97,15 @@
.kanban-card > .actions > a {display: block; float: left; width: 20px; height: 20px; line-height: 20px; text-align: center; border-radius: 4px; opacity: .7;}
.kanban-card > .actions > a:hover {background-color: rgba(0,0,0,.075); opacity: 1;}
#kanban .kanban-card[data-scale-size="4"] {padding: 8px !important; min-height: 47px !important; height: 47px !important;}
#kanban .kanban-card[data-scale-size="4"] {padding: 10px !important; min-height: 47px !important; height: 47px !important;}
#kanban .kanban-card[data-scale-size="3"] {padding: 3px 4px;}
#kanban .kanban-card[data-scale-size="3"] > .title {white-space: normal; max-height: 100%;}
#kanban .kanban-card[data-scale-size="2"] {padding: 3px 4px; overflow: hidden;}
#kanban .kanban-card[data-scale-size="2"] {padding: 3px 8px; overflow: hidden;}
#kanban .kanban-card[data-scale-size="2"] > .title {display: inline; line-height: 18px; white-space: normal; max-height: 100%; word-break: break-all;}
#kanban .kanban-card[data-scale-size="2"] > .infos {display: inline-block; height: 18px; vertical-align: top; margin: 0;}
#kanban .kanban-card[data-scale-size="2"] > .infos > .info-pri {transform: scale(.8); vertical-align: top;}
#kanban .kanban-card[data-scale-size="2"] > .infos > .avatar {display: inline-block; transform: scale(.8); position: relative; top: -2px; vertical-align: top; margin-right: -1px;}
#kanban .kanban-card[data-scale-size="2"] > .infos > .info-estimate {top: -1px;}
#mainMenu .divider {margin: 10px}
+3 -2
View File
@@ -9,8 +9,9 @@ tbody > tr > td > .btn-icon {margin-right: 4px;}
th.c-pri{width:50px;}
.c-estimate {width: 50px;}
.c-status, .c-user {width: 70px;}
.c-category {width: 80px;}
.c-actions-7 {width: 190px;}
.c-assignedTo {width: 100px;}
.c-category {width: 75px;}
.c-actions-7 {width: 195px;}
.closed-story {color: #e5e5e5;}
.stageBox {height: 180px; overflow: scroll; background: #ffffff;}
#executionStoryForm tbody tr td .label {max-width: 100px; text-overflow: unset;}
+3 -2
View File
@@ -35,14 +35,15 @@
#kanbans .kanban {min-height: initial;}
#kanbans .kanban-lane {min-height: 150px; margin-top: 2px;}
#kanbans .kanban-affixed > .kanban-header {top: 100px;}
#kanbans .kanban-card[data-scale-size="4"] {padding: 8px;}
#kanbans .kanban-card[data-scale-size="4"] {padding: 10px;}
#kanbans .kanban-card[data-scale-size="3"] {padding: 3px 4px;}
#kanbans .kanban-card[data-scale-size="3"] > .title {white-space: normal; max-height: 100%;}
#kanbans .kanban-card[data-scale-size="2"] {padding: 3px 4px; overflow: hidden;}
#kanbans .kanban-card[data-scale-size="2"] {padding: 3px 8px; overflow: hidden;}
#kanbans .kanban-card[data-scale-size="2"] > .title {display: inline; line-height: 18px; white-space: normal; max-height: 100%; word-break: break-all;}
#kanbans .kanban-card[data-scale-size="2"] > .infos {display: inline-block; height: 18px; vertical-align: top; margin: 0;}
#kanbans .kanban-card[data-scale-size="2"] > .infos > .info-pri {transform: scale(.8); vertical-align: top;}
#kanbans .kanban-card[data-scale-size="2"] > .infos > .avatar {display: inline-block; transform: scale(.8); position: relative; top: -2px; vertical-align: top; margin-right: -1px;}
#kanbans .kanban-card[data-scale-size="2"] > .infos > .info-estimate {top: -1px;}
#kanbanScaleControl {width: 100px;}
#kanbanScaleControl .input-group-addon {background: #fff; padding: 5px;}
+70 -48
View File
@@ -51,6 +51,23 @@ function changeStatus(executionStatus)
}
}
/**
* Hide all action.
*
* @access public
* @return void
*/
function hideAction()
{
$('.actions').hide();
$('.action').hide();
$('.avatar').removeAttr('data-toggle');
$('.kanban-group-header').hide();
$(".title").attr("disabled", true).css("pointer-events", "none");
$(".avatar").attr("disabled", true).css("pointer-events", "none");
window.sortableDisabled = true;
}
/**
* Display the kanban in full screen.
*
@@ -68,21 +85,8 @@ function fullScreen()
{
$('#kanbanContainer').addClass('fullscreen')
.on('scroll', tryUpdateKanbanAffix);
$('.actions').hide();
$('.action').hide();
$('.avatar').removeAttr('data-toggle');
$('#kanbanContainer a.iframe').each(function()
{
if($(this).hasClass('iframe'))
{
var href = $(this).attr('href');
$(this).removeClass('iframe');
$(this).attr('href', 'javascript:void(0)');
$(this).attr('href-bak', href);
}
})
$('.kanban-group-header').hide();
$(".title").attr("disabled", true).css("pointer-events", "none");
hideAction();
$.cookie('isFullScreen', 1);
};
@@ -123,17 +127,10 @@ function exitFullScreen()
$('.actions').show();
$('.action').show();
$('.avatar').attr('data-toggle', 'modal');
$('#kanbanContainer a').each(function()
{
var hrefBak = $(this).attr('href-bak');
if(hrefBak)
{
$(this).addClass('iframe');
$(this).attr('href', hrefBak);
}
})
$('.kanban-group-header').show();
$(".title").attr("disabled", false).css("pointer-events", "auto");
$(".avatar").attr("disabled", false).css("pointer-events", "auto");
window.sortableDisabled = false;
$.cookie('isFullScreen', 0);
}
@@ -180,7 +177,7 @@ function changeKanbanScaleSize(newScaleSize)
}
else
{
var kanban = $('#kanban').data('zui.kanban');
var kanban = $('#kanban' + executionID).data('zui.kanban');
if(!kanban) return;
kanban.setOptions({cardsPerRow: newScaleSize, cardHeight: getCardHeight()});
}
@@ -193,7 +190,7 @@ function changeKanbanScaleSize(newScaleSize)
/** Get card height */
function getCardHeight()
{
return [59, 59, 62, 62, 47][window.kanbanScaleSize];
return [60, 60, 62, 62, 47][window.kanbanScaleSize];
}
$('#type').change(function()
@@ -272,7 +269,7 @@ function createColumnCreateMenu(options)
var col = $col.data('col');
var items = [];
var laneID = col.$kanbanData.lanes[0].id ? col.$kanbanData.lanes[0].id : 0;
var regionID = col.$kanbanData.region;
var regionID = col.$kanbanData.region == undefined ? 0 : col.$kanbanData.region;
if(col.type == 'backlog')
{
@@ -370,19 +367,22 @@ function findDropColumns($element, $root)
if(!kanbanRules) return $root.find('.kanban-lane-col:not([data-type="' + col.type + '"])');
var colRules = kanbanRules[col.type];
var groupID = $col.closest('.kanban-board').data().id;
var colRules = kanbanRules[col.type];
var groupID = $col.closest('.kanban-board').data().id;
var oldLaneID = $col.closest('.kanban-lane').data().id;
return $root.find('.kanban-lane-col').filter(function()
{
if(!colRules) return false;
if(colRules === true) return true;
if($.cookie('isFullScreen') == 1) return false;
var $newCol = $(this);
var newCol = $newCol.data();
var newGroupID = $newCol.closest('.kanban-board').data().id;
var $newCol = $(this);
var newCol = $newCol.data();
var newGroupID = $newCol.closest('.kanban-board').data().id;
var newLaneID = $newCol.closest('.kanban-lane').data().id;
var canDropHere = colRules.indexOf(newCol.type) > -1 && newGroupID === groupID;
if(groupBy && groupBy != 'default' && canDropHere) canDropHere = oldLaneID === newLaneID;
if(canDropHere) $newCol.addClass('can-drop-here');
return canDropHere;
});
@@ -429,7 +429,8 @@ function renderUserAvatar(user, objectType, objectID, size, objectStatus)
if(objectType == 'story' && !priv.canAssignStory) return $noPrivAvatar;
if(objectType == 'bug' && !priv.canAssignBug) return $noPrivAvatar;
return objectStatus == 'closed' ? '' : $('<a class="avatar has-text ' + avatarSizeClass + ' avatar-circle iframe" title="' + user.realname + '" href="' + link + '"/>').avatar({user: user}).attr('data-toggle', 'modal').attr('data-width', '80%');
var realname = user.realname ? user.realname : user.account;
return objectStatus == 'closed' ? '' : $('<a class="avatar has-text ' + avatarSizeClass + ' avatar-circle iframe" title="' + realname + '" href="' + link + '"/>').avatar({user: user}).attr('data-toggle', 'modal').attr('data-width', '80%');
}
/**
@@ -515,6 +516,8 @@ function renderStoryItem(item, $item, col)
}
}
if($.cookie('isFullScreen') == 1) hideAction();
return $item.attr('data-type', 'story').addClass('kanban-item-story');
}
@@ -579,6 +582,8 @@ function renderBugItem(item, $item, col)
}
}
if($.cookie('isFullScreen') == 1) hideAction();
return $item.attr('data-type', 'bug').addClass('kanban-item-bug');
}
@@ -644,6 +649,8 @@ function renderTaskItem(item, $item, col)
$item.attr('data-type', 'task').addClass('kanban-item-task');
if($.cookie('isFullScreen') == 1) hideAction();
return $item;
}
@@ -690,8 +697,6 @@ function renderCount($count, count, column)
*/
function renderHeaderCol($column, column, $header, kanbanData)
{
if(groupBy != 'default') return;
/* Render group header. */
var privs = kanbanData.actions;
var columnPrivs = kanbanData.columns[0].actions;
@@ -703,7 +708,7 @@ function renderHeaderCol($column, column, $header, kanbanData)
$actions = $column.children('.actions');
}
if(privs.includes('sortGroup'))
if(groupBy == 'default' && privs.includes('sortGroup'))
{
var groups = regions[column.region].groups;
if($header.closest('.kanban').data('zui.kanban'))
@@ -777,6 +782,8 @@ function renderLaneName($lane, lane, $kanban, columns, kanban)
*/
function updateRegion(regionID, regionData = [])
{
if(groupBy != 'default') regionID = executionID;
if(!regionID) return false;
var $region = $('#kanban'+ regionID).kanban();
@@ -784,7 +791,8 @@ function updateRegion(regionID, regionData = [])
if(!$region.length) return false;
if(!regionData) regionData = regions[regionID];
$region.data('zui.kanban').render(regionData.groups);
var data = groupBy == 'default' ? regionData.groups : regionData;
$region.data('zui.kanban').render(data);
resetRegionHeight('open');
return true;
}
@@ -832,8 +840,6 @@ var kanbanActionHandlers =
*/
function handleKanbanAction(action, $element, event, kanban)
{
if(groupBy && groupBy != 'default') return false;
$('.kanban').attr('data-action-enabled', action);
var handler = kanbanActionHandlers[action];
if(handler) handler($element, event, kanban);
@@ -860,6 +866,8 @@ function changeCardColType(cardID, fromColID, toColID, fromLaneID, toLaneID, car
var showIframe = false;
var moveCard = false;
regionID = regionID ? regionID : 0;
/* Task lane. */
if(cardType == 'task')
{
@@ -989,7 +997,8 @@ function changeCardColType(cardID, fromColID, toColID, fromLaneID, toLaneID, car
url: link,
success: function(data)
{
updateRegion(regionID, data[regionID]);
data = groupBy == 'default' ? data[regionID] : data[groupBy];
updateRegion(regionID, data);
},
error: function(xhr, status, error)
{
@@ -1018,6 +1027,8 @@ function changeCardColType(cardID, fromColID, toColID, fromLaneID, toLaneID, car
function deleteCard(objectType, objectID, regionID)
{
$.zui.ContextMenu.hide();
regionID = regionID ? regionID : 0;
setTimeout(function()
{
var objectLang = objectType + 'Lang';
@@ -1034,7 +1045,8 @@ function deleteCard(objectType, objectID, regionID)
url: url,
success: function(data)
{
updateRegion(regionID, data[regionID]);
data = groupBy == 'default' ? data[regionID] : data[groupBy];
updateRegion(regionID, data);
}
});
}, 200)
@@ -1053,7 +1065,7 @@ function updateKanban(kanbanData, regionID = 0)
setTimeout(function()
{
$.zui.closeModal();
if(regionID)
if(regionID && groupBy == 'default')
{
updateRegion(regionID, kanbanData[regionID]);
}
@@ -1062,7 +1074,8 @@ function updateKanban(kanbanData, regionID = 0)
$('#kanban').children('.region').children("div[id^='kanban']").each(function()
{
var regionID = $(this).attr('data-id');
updateRegion(regionID, kanbanData[regionID]);
var data = groupBy == 'default' ? kanbanData[regionID] : kanbanData[groupBy]
updateRegion(regionID, data);
});
}
}, 200);
@@ -1091,7 +1104,8 @@ function shiftCard(objectID, fromColID, toColID, fromLaneID, toLaneID, regionID)
url: link,
success: function(data)
{
updateRegion(regionID, data[regionID]);
data = groupBy == 'default' ? data[regionID] : data[groupBy];
updateRegion(regionID, data);
},
error: function(xhr, status, error)
{
@@ -1230,9 +1244,10 @@ function initKanban($kanban)
onRenderLaneName: renderLaneName,
onRenderHeaderCol: renderHeaderCol,
onRenderCount: renderCount,
droppable: groupBy == 'default' ? {target: findDropColumns, finish:handleFinishDrop} : false,
droppable: {target: findDropColumns, finish:handleFinishDrop},
virtualize: true,
virtualCardList: true
virtualCardList: true,
virtualRenderOptions: {container: $(window).add($('#kanbanContainer'))}
});
$kanban.on('click', '.action-cancel', hideKanbanAction);
@@ -1317,8 +1332,9 @@ $(function()
var planID = $('#plan').val();
if(planID)
{
var vars = $('.linkStoryByPlanButton').data('lane') != null ? '&extra=laneID='+ $('.linkStoryByPlanButton').data('lane') + ',columnID=' + $('.linkStoryByPlanButton').data('col') : '';
location.href = createLink('execution', 'importPlanStories', 'executionID=' + executionID + '&planID=' + planID + '&productID=0&fromMethod=kanban' + vars);
var vars = $('.linkStoryByPlanButton').data('lane') != null ? '&extra=laneID='+ $('.linkStoryByPlanButton').data('lane') + ',columnID=' + $('.linkStoryByPlanButton').data('col') : '';
var param = "&param=executionID=" + executionID + ",browseType=" + browseType + ",orderBy=id_asc,groupBy=" + groupBy;
location.href = createLink('execution', 'importPlanStories', 'executionID=' + executionID + '&planID=' + planID + '&productID=0&fromMethod=kanban' + vars + param);
$.closeModal();
}
});
@@ -1400,6 +1416,10 @@ $(function()
return $ele.parent().children('.kanban-lane');
}
},
before: function()
{
return !window.sortableDisabled;
},
start: function(e)
{
if(sortType == 'region')
@@ -1417,6 +1437,8 @@ $(function()
},
finish: function(e)
{
if(!e.changed) return;
var url = '';
var orders = [];
+2
View File
@@ -1,5 +1,7 @@
$(function()
{
if($('#storyList thead th.c-title').width() < 150) $('#storyList thead th.c-title').width(150);
$('#storyList').on('sort.sortable', function(e, data)
{
var list = '';
+31 -29
View File
@@ -45,7 +45,8 @@ function renderUserAvatar(user, objectType, objectID, size, objectStatus)
if(objectType == 'story' && !priv.canAssignStory) return $noPrivAvatar;
if(objectType == 'bug' && !priv.canAssignBug) return $noPrivAvatar;
return objectStatus == 'closed' ? '' : $('<a class="avatar has-text ' + avatarSizeClass + ' avatar-circle iframe" title="' + user.realname + '" href="' + link + '"/>').avatar({user: user});
var realname = user.realname ? user.realname : user.account;
return objectStatus == 'closed' ? '' : $('<a class="avatar has-text ' + avatarSizeClass + ' avatar-circle iframe" title="' + realname + '" href="' + link + '"/>').avatar({user: user});
}
/**
@@ -131,6 +132,7 @@ function renderStoryItem(item, $item, col)
}
}
if($.cookie('isFullScreen') == 1) hideAllAction();
return $item.attr('data-type', 'story').addClass('kanban-item-story');
}
@@ -195,6 +197,7 @@ function renderBugItem(item, $item, col)
}
}
if($.cookie('isFullScreen') == 1) hideAllAction();
return $item.attr('data-type', 'bug').addClass('kanban-item-bug');
}
@@ -260,6 +263,7 @@ function renderTaskItem(item, $item, col)
$item.attr('data-type', 'task').addClass('kanban-item-task');
if($.cookie('isFullScreen') == 1) hideAllAction();
return $item;
}
@@ -388,6 +392,24 @@ function createKanban(kanbanID, data, options)
$kanban.kanban($.extend({data: data, calcColHeight: calcColHeight, displayCards: displayCards}, options));
}
/**
* Hide all actions.
*
* @access public
* @return void
*/
function hideAllAction()
{
$('.actions').hide();
$(".title, .avatar.iframe").attr("disabled", true).css("pointer-events", "none");
}
/**
* Display the kanban in full screen.
*
* @access public
* @return void
*/
function fullScreen()
{
$('#kanbans .kanban-header').addClass('headerTop');
@@ -398,17 +420,7 @@ function fullScreen()
var afterEnterFullscreen = function()
{
$('#kanbanContainer').addClass('scrollbar-hover');
$('.actions').hide();
$('#kanbanContainer a.iframe').each(function()
{
if($(this).hasClass('iframe'))
{
var href = $(this).attr('href');
$(this).removeClass('iframe');
$(this).attr('href', 'javascript:void(0)');
$(this).attr('href-bak', href);
}
})
hideAllAction();
$.cookie('isFullScreen', 1);
}
@@ -447,15 +459,7 @@ function exitFullScreen()
$('#kanbans .kanban-header').removeClass('headerTop');
$('#kanbanContainer').removeClass('scrollbar-hover');
$('.actions').show();
$('#kanbanContainer a').each(function()
{
var hrefBak = $(this).attr('href-bak');
if(hrefBak)
{
$(this).addClass('iframe');
$(this).attr('href', hrefBak);
}
})
$(".title, .avatar.iframe").attr("disabled", false).css("pointer-events", "auto");
$.cookie('isFullScreen', 0);
}
@@ -505,11 +509,11 @@ if(!window.kanbanDropRules)
},
task:
{
'wait': ['developing', 'developed', 'canceled', 'closed'],
'wait': ['developing', 'developed', 'canceled'],
'developing': ['developed', 'pause', 'canceled'],
'developed': ['developing', 'closed'],
'pause': ['developing'],
'canceled': ['developing'],
'canceled': ['developing', 'closed'],
'closed': ['developing'],
}
}
@@ -783,7 +787,6 @@ var kanbanActionHandlers =
*/
function handleKanbanAction(action, $element, event, kanban)
{
if(groupBy && groupBy != 'default') return false;
$('.kanban').attr('data-action-enabled', action);
var handler = kanbanActionHandlers[action];
if(handler) handler($element, event, kanban);
@@ -1062,7 +1065,7 @@ $(function()
cardsPerRow: window.kanbanScaleSize,
virtualize: true,
onAction: handleKanbanAction,
virtualRenderOptions: {container: '#kanbanContainer>.panel-body'},
virtualRenderOptions: {container: '#kanbanContainer>.panel-body,#kanbanContainer'},
virtualCardList: true,
droppable:
{
@@ -1071,11 +1074,9 @@ $(function()
},
onRenderHeaderCol: renderHeaderCol,
onRenderLaneName: renderLaneName,
onRenderCount: renderColumnCount
onRenderCount: renderColumnCount,
};
if(groupBy != 'default') commonOptions.droppable = false;
/* Create kanban */
if(groupBy == 'default')
{
@@ -1141,7 +1142,8 @@ $(function()
var planID = $('#plan').val();
if(planID)
{
location.href = createLink('execution', 'importPlanStories', 'executionID=' + executionID + '&planID=' + planID + '&productID=0&fromMethod=kanban');
var param = "&param=executionID=" + executionID + ",browseType=" + browseType + ",orderBy=id_asc,groupBy=" + groupBy;
location.href = createLink('execution', 'importPlanStories', 'executionID=' + executionID + '&planID=' + planID + '&productID=0&fromMethod=taskKanban&extra=' + param);
}
});
+34
View File
@@ -2,3 +2,37 @@ function checkUserDept(userID)
{
alert(noAccess);
}
/**
* Delete memeber of project team.
*
* @param int $executionID
* @param int $userID
* @access public
* @return void
*/
function deleteMember(executionID, userID)
{
bootbox.confirm(confirmUnlinkMember, function(result)
{
if(!result) return true;
var unlinkURL = createLink('execution', 'unlinkMember', 'executionID=' + executionID + '&userID=' + userID + '&confirm=yes');
unlinkMember(unlinkURL);
});
}
/**
* Unlink member from project.
*
* @param unlinkURL $unlinkURL
* @access public
* @return void
*/
function unlinkMember(unlinkURL)
{
$.get(unlinkURL, function(data)
{
data = JSON.parse(data);
if(data.result == 'success') window.location.reload();
});
}
+38 -1
View File
@@ -1,3 +1,40 @@
/**
* Expand or collapse text.
*
* @access public
* @return void
*/
function limitText()
{
var fullText;
var limitText;
var $text = $(this);
var options = $.extend({limitSize: 40, suffix: '…'}, $text.data());
var text = $text.text();
if(text.length > options.limitSize)
{
fullText = $text.html();
limitText = text.substring(0, options.limitSize) + options.suffix;
$text.text(limitText).addClass('limit-text-on');
var $toggleBtn = options.toggleBtn ? $(options.toggleBtn) : $text.next('.text-limit-toggle');
$toggleBtn.text($toggleBtn.data('textExpand'));
$toggleBtn.on('click', function()
{
var isLimitOn = $text.toggleClass('limit-text-on').hasClass('limit-text-on');
if(isLimitOn) $text.text(limitText);
else $text.html(fullText);
$toggleBtn.text($toggleBtn.data(isLimitOn ? 'textExpand' : 'textCollapse'));
});
}
else
{
(options.toggleBtn ? $(options.toggleBtn) : $text.next('.text-limit-toggle')).hide();
}
$text.removeClass('hidden');
};
$.fn.textLimit = function(){return this.each(limitText);};
$(function()
{
var $taskTree = $('#taskTree').tree(
@@ -157,7 +194,7 @@ $(function()
$itemContent.on('click', function(event)
{
if(!$(event.target).closest('a[data-app]').length) event.stopPropagation();
if(!$(event.target).closest('a[data-app]').length || $(event.target).closest('a').hasClass('iframe')) event.stopPropagation();
});
$taskTree.on('click', stopPropagation);
$(window).on('resize scroll', adjustSidePosition);
+1 -1
View File
@@ -292,6 +292,7 @@ $lang->execution->statusSelects['finishedbyme'] = 'FinishedByMe';
$lang->execution->statusSelects['done'] = 'Done';
$lang->execution->statusSelects['closed'] = 'Closed';
$lang->execution->statusSelects['cancel'] = 'Cancelled';
$lang->execution->statusSelects['delayed'] = 'Delayed';
$lang->execution->groups[''] = 'View by Groups';
$lang->execution->groups['story'] = 'Group by Story';
@@ -468,7 +469,6 @@ $lang->execution->featureBar['task']['unclosed'] = $lang->execution->unclose
$lang->execution->featureBar['task']['assignedtome'] = $lang->execution->assignedToMe;
$lang->execution->featureBar['task']['myinvolved'] = $lang->execution->myInvolved;
$lang->execution->featureBar['task']['assignedbyme'] = $lang->execution->assignedByMe;
$lang->execution->featureBar['task']['delayed'] = 'Delayed';
$lang->execution->featureBar['task']['needconfirm'] = 'Changed';
$lang->execution->featureBar['task']['status'] = $lang->execution->statusSelects[''];
+1 -1
View File
@@ -292,6 +292,7 @@ $lang->execution->statusSelects['finishedbyme'] = '我完成';
$lang->execution->statusSelects['done'] = '已完成';
$lang->execution->statusSelects['closed'] = '已关闭';
$lang->execution->statusSelects['cancel'] = '已取消';
$lang->execution->statusSelects['delayed'] = '已延期';
$lang->execution->groups[''] = '分组查看';
$lang->execution->groups['story'] = "{$lang->SRCommon}分组";
@@ -468,7 +469,6 @@ $lang->execution->featureBar['task']['unclosed'] = $lang->execution->unclose
$lang->execution->featureBar['task']['assignedtome'] = $lang->execution->assignedToMe;
$lang->execution->featureBar['task']['myinvolved'] = $lang->execution->myInvolved;
$lang->execution->featureBar['task']['assignedbyme'] = $lang->execution->assignedByMe;
$lang->execution->featureBar['task']['delayed'] = '已延期';
$lang->execution->featureBar['task']['needconfirm'] = "{$lang->SRCommon}变更";
$lang->execution->featureBar['task']['status'] = $lang->execution->statusSelects[''];
+4 -1
View File
@@ -67,6 +67,9 @@ class executionModel extends model
if($execution and $execution->type == 'kanban')
{
$this->lang->execution->menu = new stdclass();
$this->lang->execution->menu->kanban = array('link' => "{$this->lang->kanban->common}|execution|kanban|executionID=%s");
$this->lang->execution->menu->build = array('link' => "{$this->lang->build->common}|execution|build|executionID=%s");
$this->lang->execution->dividerMenu = '';
$this->lang->execution->accessDenied = str_replace($this->lang->executionCommon, $this->lang->execution->kanban, $this->lang->execution->accessDenied);
}
@@ -3799,7 +3802,7 @@ class executionModel extends model
$storyItem->color = $story->color;
$storyItem->pri = $story->pri;
$storyItem->storyId = $story->id;
$storyItem->openedBy = $users[$story->openedBy];
$storyItem->openedBy = zget($users, $story->openedBy);
$storyItem->assignedTo = zget($users, $story->assignedTo);
$storyItem->url = helper::createLink('story', 'view', "storyID=$story->id&version=$story->version&from=execution&param=$executionID");
$storyItem->taskCreateUrl = helper::createLink('task', 'batchCreate', "executionID={$executionID}&story={$story->id}");
+5 -5
View File
@@ -97,7 +97,7 @@
<th class='c-end'><?php common::printOrderLink('end', $orderBy, $vars, $lang->execution->end);?></th>
<th class='c-realBegan'><?php common::printOrderLink('realBegan', $orderBy, $vars, $lang->execution->realBegan);?></th>
<th class='c-realEnd'><?php common::printOrderLink('realEnd', $orderBy, $vars, $lang->execution->realEnd);?></th>
<th class='c-action'><?php echo $lang->actions;?></th>
<th class='c-actions-6 text-center'><?php echo $lang->actions;?></th>
<?php else:;?>
<th class='c-begin'><?php common::printOrderLink('begin', $orderBy, $vars, $lang->execution->begin);?></th>
<th class='c-end'><?php common::printOrderLink('end', $orderBy, $vars, $lang->execution->end);?></th>
@@ -139,7 +139,7 @@
<?php
$executionLink = $execution->projectModel == 'kanban' ? html::a($this->createLink('execution', 'kanban', 'executionID=' . $execution->id), $executionName, '', "class='text-ellipsis'") : html::a($this->createLink('execution', 'task', 'execution=' . $execution->id), $executionName, '', "class='text-ellipsis'");
if($onlyChildStage) echo "<span class='label label-badge label-light label-children'>{$lang->programplan->childrenAB}</span> ";
echo !empty($execution->children) ? $execution->name : $executionLink;
echo !empty($execution->children) ? "<span class='text-ellipsis'>$execution->name</span>" : $executionLink;
if(isset($execution->delay)) echo "<span class='label label-danger label-badge'>{$lang->execution->delayed}</span> ";
?>
<?php if(!empty($execution->children)):?>
@@ -169,7 +169,7 @@
<td><?php echo helper::isZeroDate($execution->end) ? '' : $execution->end;?></td>
<td><?php echo helper::isZeroDate($execution->realBegan) ? '' : $execution->realBegan;?></td>
<td><?php echo helper::isZeroDate($execution->realEnd) ? '' : $execution->realEnd;?></td>
<td class="c-actions text-center c-actions">
<td class="c-actions text-center">
<?php
common::printIcon('execution', 'start', "executionID={$execution->id}", $execution, 'list', '', '', 'iframe', true);
$class = !empty($execution->children) ? 'disabled' : '';
@@ -230,10 +230,10 @@
<?php endif;?>
<?php printf('%03d', $child->id);?>
</td>
<td class='text-left' title='<?php echo $child->name?>'>
<td class='text-left c-name flex' title='<?php echo $child->name?>'>
<?php
echo "<span class='label label-badge label-light' title='{$lang->programplan->children}'>{$lang->programplan->childrenAB}</span>";
echo html::a($this->createLink('execution', 'task', 'execution=' . $child->id), $child->name);
echo html::a($this->createLink('execution', 'task', 'execution=' . $child->id), $child->name, '', "class='text-ellipsis'");
if(isset($child->delay)) echo "<span class='label label-danger label-badge'>{$lang->execution->delayed}</span> ";
?>
</td>
+2 -1
View File
@@ -15,7 +15,8 @@
<?php $defaultURL = $this->createLink('execution', 'task', "execution=$executionID");?>
<?php include '../../common/view/header.html.php';?>
<body>
<div class='modal-dialog' id='tipsModal'>
<?php $tipsModal = $this->config->systemMode == 'new' ? 'newTipsModal' : 'classicTipsModal';?>
<div class='modal-dialog' id='<?php echo $tipsModal;?>'>
<div class='modal-header'>
<a href='<?php echo $defaultURL;?>' class='close'><i class="icon icon-close"></i></a>
<h4 class='modal-title' id='myModalLabel'><?php echo $lang->execution->tips;?></h4>
+5 -1
View File
@@ -25,11 +25,15 @@
</div>
<form class='load-indicator main-form form-ajax' method='post' target='hiddenwin' id='dataform'>
<table class='table table-form'>
<?php if($config->systemMode == 'new' and isset($project) and $project->model == 'scrum'):?>
<?php if($config->systemMode == 'new' and isset($project)):?>
<?php if($project->model == 'scrum'):?>
<tr>
<th class='w-120px'><?php echo $lang->execution->projectName;?></th>
<td><?php echo html::select('project', $allProjects, $execution->project, "class='form-control chosen' onchange='changeProject(this.value)' required");?></td><td></td>
</tr>
<?php elseif($project->model == 'kanban'):?>
<?php echo html::hidden('project', $project->id);?>
<?php endif;?>
<?php endif;?>
<tr>
<th class='w-120px'><?php echo $lang->execution->name;?></th>
+1 -1
View File
@@ -66,7 +66,7 @@
$class = common::hasPriv('execution', 'importBug') ? '' : "class=disabled";
$misc = common::hasPriv('execution', 'importBug') ? "class='import'" : "class=disabled";
$link = common::hasPriv('execution', 'importBug') ? $this->createLink('execution', 'importBug', "execution=$execution->id") : '#';
echo "<li $class>" . html::a($link, $lang->execution->importBug, '', $misc) . "</li>";
echo "<li $class id='importBug'>" . html::a($link, $lang->execution->importBug, '', $misc) . "</li>";
?>
</ul>
</div>
+10 -5
View File
@@ -138,15 +138,16 @@ js::set('hasTaskButton', $hasTaskButton);
<div class='btn-toolbar pull-right'>
<?php
echo html::a('javascript:fullScreen()', "<i class='icon-fullscreen muted'></i> " . $lang->kanban->fullScreen, '', "class='btn btn-link'");
$actions = '';
$printSettingBtn = (common::hasPriv('kanban', 'createRegion') or (common::hasPriv('kanban', 'setLaneHeight')) or (common::hasPriv('kanban', 'setColumnWidth')) or common::hasPriv('execution', 'edit') or common::hasPriv('execution', 'close') or common::hasPriv('execution', 'delete') or !empty($executionActions));
$actions = '';
$printCreateRegion = (common::hasPriv('kanban', 'createRegion') and $groupBy == 'default');
$printSettingBtn = ($printCreateRegion or (common::hasPriv('kanban', 'setLaneHeight')) or (common::hasPriv('kanban', 'setColumnWidth')) or common::hasPriv('execution', 'edit') or common::hasPriv('execution', 'close') or common::hasPriv('execution', 'delete') or !empty($executionActions));
if($printSettingBtn)
{
$actions .= html::a('javascript:;', "<i class='icon icon-cog-outline'></i>" . $lang->kanban->setting, '', "data-toggle='dropdown' class='btn btn-link'");
$actions .= "<ul id='kanbanActionMenu' class='dropdown-menu pull-right'>";
$width = $this->app->getClientLang() == 'en' ? '750' : '650';
if(common::hasPriv('kanban', 'createRegion')) $actions .= '<li>' . html::a(helper::createLink('kanban', 'createRegion', "kanbanID=$execution->id&from=execution", '', true), '<i class="icon icon-plus"></i>' . $lang->kanban->createRegion, '', "class='iframe btn btn-link text-left'") . '</li>';
if($printCreateRegion) $actions .= '<li>' . html::a(helper::createLink('kanban', 'createRegion', "kanbanID=$execution->id&from=execution", '', true), '<i class="icon icon-plus"></i>' . $lang->kanban->createRegion, '', "class='iframe btn btn-link text-left'") . '</li>';
if(common::hasPriv('kanban', 'setLaneHeight')) $actions .= '<li>' . html::a(helper::createLink('kanban', 'setLaneHeight', "kanbanID=$execution->id&from=execution", '', true), '<i class="icon icon-size-height"></i>' . $lang->kanban->laneHeight, '', "class='iframe btn btn-link text-left' data-width=$width") . '</li>';
if(common::hasPriv('kanban', 'setColumnWidth')) $actions .= '<li>' . html::a(helper::createLink('kanban', 'setColumnWidth', "kanbanID=$execution->id&from=execution", '', true), '<i class="icon icon-size-width"></i>' . $lang->kanban->columnWidth, '', "class='iframe btn btn-link text-left' data-width='400'") . '</li>';
$kanbanActions = '';
@@ -232,8 +233,12 @@ js::set('hasTaskButton', $hasTaskButton);
</div>
<?php else:?>
<div class='panel' id='kanbanContainer'>
<div class='panel-body region'>
<div id='kanban' class='kanban'></div>
<div class='panel-body'>
<div id='kanban'>
<div class='region'>
<div id='kanban<?php echo $execution->id;?>' data-id='<?php echo $execution->id;?>' class='kanban'></div>
</div>
</div>
</div>
</div>
<?php endif;?>
+3 -3
View File
@@ -55,7 +55,7 @@
<tbody>
<?php $storyCount = 0;?>
<?php foreach($allStories as $story):?>
<?php $storyLink = $this->createLink('story', 'view', "storyID=$story->id");?>
<?php $storyLink = $this->createLink('story', 'view', "storyID=$story->id", '', true);?>
<tr>
<td class='cell-id'>
<?php echo html::checkbox('stories', array($story->id => sprintf('%03d', $story->id)));?>
@@ -65,10 +65,10 @@
<td class='text-left nobr' title="<?php echo $story->title?>">
<?php
if($story->parent > 0) echo "<span class='label'>{$lang->story->childrenAB}</span>";
echo html::a($storyLink, $story->title);
echo html::a($storyLink, $story->title, '', "class='iframe' data-width='80%'");
?>
</td>
<td class='text-left' title='<?php echo $products[$story->product]->name?>'><?php echo html::a($this->createLink('product', 'browse', "productID=$story->product&branch=$story->branch"), $products[$story->product]->name, '_blank');?></td>
<td class='text-left' title='<?php echo $products[$story->product]->name?>'><?php echo html::a($this->createLink('product', 'browse', "productID=$story->product&branch=$story->branch"), $products[$story->product]->name);?></td>
<td class='c-module text-left' title='<?php echo zget($modules, $story->module, '')?>'><?php echo zget($modules, $story->module, '')?></td>
<td class='text-ellipsis' title='<?php echo $story->planTitle;?>'><?php echo $story->planTitle;?></td>
<td><?php echo zget($lang->story->stageList, $story->stage);?></td>
+29 -3
View File
@@ -24,12 +24,13 @@
<div class='detail-title'><?php echo $lang->execution->linkedProducts;?></div>
<div class='detail-content row'>
<?php $i = 0;?>
<?php $attr = $execution->grade == 2 ? "disabled='disabled'" : '';?>
<?php $attr = '';?>
<?php foreach($allProducts as $productID => $productName):?>
<?php if(isset($linkedProducts[$productID])):?>
<?php foreach($linkedBranches[$productID] as $branchID):?>
<?php if(($execution->grade < 2 and in_array($productID, $unmodifiableProducts) and in_array($branchID, $unmodifiableBranches)) and !empty($linkedStoryIDList[$productID][$branchID])) $attr = "disabled='disabled'";?>
<?php if($execution->grade < 2 and (!(in_array($productID, $unmodifiableProducts) and in_array($branchID, $unmodifiableBranches)) or empty($linkedStoryIDList[$productID][$branchID]))) $attr = '';?>
<?php if((in_array($productID, $unmodifiableProducts) and in_array($branchID, $unmodifiableBranches)) and !empty($linkedStoryIDList[$productID][$branchID])) $attr = "disabled='disabled'";?>
<?php if((!(in_array($productID, $unmodifiableProducts) and in_array($branchID, $unmodifiableBranches)) or empty($linkedStoryIDList[$productID][$branchID]))) $attr = '';?>
<?php if($execution->grade == '2' and ($linkedProducts[$productID]->type == 'normal' or $branchID == 0)) $attr = "disabled='disabled'";?>
<?php $title = (in_array($productID, $unmodifiableProducts) and in_array($branchID, $unmodifiableBranches) and !empty($linkedStoryIDList[$productID][$branchID])) ? sprintf($lang->execution->notAllowRemoveProducts, $linkedStoryIDList[$productID][$branchID]) : $productName;?>
<?php $checked = 'checked';?>
<div class='col-sm-4'>
@@ -75,6 +76,31 @@
<?php if(common::canModify('execution', $execution)) echo html::submitButton();?>
</div>
<?php endif;?>
<?php if($execution->grade == 2):?>
<div class='detail'>
<div class='detail-title'><?php echo $lang->execution->unlinkedProducts;?></div>
<div class='detail-content row'>
<?php foreach($allProducts as $productID => $productName):?>
<?php if(isset($linkedProducts[$productID]) and $linkedProducts[$productID]->type != 'normal'):?>
<div class='col-sm-4'>
<div class='product<?php echo isset($branchGroups[$productID]) ? ' has-branch' : ''?>'>
<div class="checkbox-primary" title='<?php echo $productName;?>'>
<?php echo "<input type='checkbox' name='products[$i]' value='$productID' id='products{$productID}'>";?>
<label class='text-ellipsis checkbox-inline' for='<?php echo 'products' . $productID;?>'><?php echo $productName;?></label>
</div>
<?php if(isset($branchGroups[$productID])) echo html::select("branch[$i]", $branchGroups[$productID], '', "class='form-control chosen'");?>
</div>
</div>
<?php $i++;?>
<?php endif;?>
<?php endforeach;?>
</div>
</div>
<div class="detail text-center form-actions">
<?php echo html::hidden("post", 'post');?>
<?php if(common::canModify('execution', $execution)) echo html::submitButton();?>
</div>
<?php endif;?>
</form>
</div>
</div>
+43 -138
View File
@@ -161,6 +161,9 @@
</div>
<div class="main-col">
<div id='queryBox' data-module='executionStory' class='cell <?php if($type =='bysearch') echo 'show';?>'></div>
<div class="table-header fixed-right">
<nav class="btn-toolbar pull-right setting"></nav>
</div>
<?php if(empty($stories)):?>
<div class="table-empty-tip">
<p>
@@ -172,48 +175,41 @@
</div>
<?php else:?>
<form class='main-table table-story skip-iframe-modal' method='post' id='executionStoryForm'>
<div class="table-header fixed-right">
<nav class="btn-toolbar pull-right"></nav>
</div>
<table class='table tablesorter has-sort-head' id='storyList'>
<?php
$datatableId = $this->moduleName . ucfirst($this->methodName);
$useDatatable = (isset($config->datatable->$datatableId->mode) and $config->datatable->$datatableId->mode == 'datatable');
$vars = "executionID={$execution->id}&orderBy=%s&type=$type&param=$param&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}";
if($useDatatable) include '../../common/view/datatable.html.php';
$setting = $this->datatable->getSetting('execution');
$widths = $this->datatable->setFixedFieldWidth($setting);
$columns = 0;
$checkObject = new stdclass();
$checkObject->execution = $execution->id;
$totalEstimate = 0;
$canBatchEdit = common::hasPriv('story', 'batchEdit');
$canBatchClose = common::hasPriv('story', 'batchClose');
$canBatchChangeStage = common::hasPriv('story', 'batchChangeStage');
$canBatchUnlink = common::hasPriv('execution', 'batchUnlinkStory');
$canBatchToTask = common::hasPriv('story', 'batchToTask', $checkObject);
$canBatchAction = ($canBeChanged and ($canBatchEdit or $canBatchClose or $canBatchChangeStage or $canBatchUnlink or $canBatchToTask));
?>
<?php if(!$useDatatable) echo '<div class="table-responsive">';?>
<table class='table tablesorter has-sort-head<?php if($useDatatable) echo ' datatable';?>' id='storyList' data-fixed-left-width='<?php echo $widths['leftWidth']?>' data-fixed-right-width='<?php echo $widths['rightWidth']?>'>
<thead>
<tr>
<?php
$checkObject = new stdclass();
$checkObject->execution = $execution->id;
$totalEstimate = 0;
$canBatchEdit = common::hasPriv('story', 'batchEdit');
$canBatchClose = common::hasPriv('story', 'batchClose');
$canBatchChangeStage = common::hasPriv('story', 'batchChangeStage');
$canBatchUnlink = common::hasPriv('execution', 'batchUnlinkStory');
$canBatchToTask = common::hasPriv('story', 'batchToTask', $checkObject);
$canBatchAction = ($canBeChanged and ($canBatchEdit or $canBatchClose or $canBatchChangeStage or $canBatchUnlink or $canBatchToTask));
foreach($setting as $key => $value)
{
if($value->show)
{
$this->datatable->printHead($value, $orderBy, $vars, $canBatchAction);
$columns ++;
}
}
?>
<?php $vars = "executionID={$execution->id}&orderBy=%s&type=$type&param=$param&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}"; ?>
<th class='c-id {sorter:false}'>
<?php if($canBatchAction):?>
<div class="checkbox-primary check-all" title="<?php echo $lang->selectAll?>">
<label></label>
</div>
<?php endif;?>
<?php common::printOrderLink('id', $orderBy, $vars, $lang->idAB);?>
</th>
<?php if($canOrder):?>
<th class='c-sort {sorter:false}'><?php common::printOrderLink('order', $orderBy, $vars, $lang->execution->orderAB);?></th>
<?php endif;?>
<th class='c-pri {sorter:false}' title=<?php echo $lang->execution->pri;?>><?php common::printOrderLink('pri', $orderBy, $vars, $lang->priAB);?></th>
<th class='c-name {sorter:false}'><?php common::printOrderLink('title', $orderBy, $vars, $lang->execution->storyTitle);?></th>
<th class='c-category {sorter:false}'><?php common::printOrderLink('category', $orderBy, $vars, $lang->story->category);?></th>
<th class='c-user {sorter:false}'> <?php common::printOrderLink('openedBy', $orderBy, $vars, $lang->openedByAB);?></th>
<th class='c-assignedTo {sorter:false} text-center'> <?php common::printOrderLink('assignedTo', $orderBy, $vars, $lang->assignedToAB);?></th>
<th class='c-estimate {sorter:false} text-right'> <?php common::printOrderLink('estimate', $orderBy, $vars, $lang->story->estimateAB);?></th>
<th class='c-status {sorter:false}'> <?php common::printOrderLink('status', $orderBy, $vars, $lang->statusAB);?></th>
<th class='c-stage {sorter:false}'> <?php common::printOrderLink('stage', $orderBy, $vars, $lang->story->stageAB);?></th>
<th title='<?php echo $lang->story->taskCount?>' class='c-count'><?php echo $lang->story->taskCountAB;?></th>
<th title='<?php echo $lang->story->bugCount?>' class='c-count'><?php echo $lang->story->bugCountAB;?></th>
<th title='<?php echo $lang->story->caseCount?>' class='c-count'><?php echo $lang->story->caseCountAB;?></th>
<th class='c-actions-7 text-center {sorter:false}'><?php echo $lang->actions;?></th>
</tr>
</thead>
<tbody id='storyTableList' class='sortable'>
@@ -223,110 +219,16 @@
$totalEstimate += $story->estimate;
?>
<tr id="story<?php echo $story->id;?>" data-id='<?php echo $story->id;?>' data-order='<?php echo $story->order ?>' data-estimate='<?php echo $story->estimate?>' data-cases='<?php echo zget($storyCases, $story->id, 0)?>'>
<td class='cell-id'>
<?php if($canBatchAction):?>
<?php echo html::checkbox('storyIdList', array($story->id => '')) . html::a(helper::createLink('story', 'view', "storyID=$story->id&version=$story->version&from=execution&param=$execution->id"), sprintf('%03d', $story->id), null, "data-app='execution'");?>
<?php else:?>
<?php printf('%03d', $story->id);?>
<?php endif;?>
</td>
<?php if($canOrder):?>
<td class='sort-handler c-sort'><i class='icon-move'></i></td>
<?php endif;?>
<td class='c-pri'><span class='label-pri <?php echo 'label-pri-' . $story->pri?>' title='<?php echo zget($lang->story->priList, $story->pri, $story->pri);?>'><?php echo zget($lang->story->priList, $story->pri, $story->pri);?></span></td>
<td class='c-name' title="<?php echo $story->title?>">
<?php if($showBranch) $showBranch = isset($this->config->execution->story->showBranch) ? $this->config->execution->story->showBranch : 1;?>
<?php if(isset($branchGroups[$story->product][$story->branch]) and $showBranch) echo "<span class='label label-outline label-badge' title={$branchGroups[$story->product][$story->branch]}>" . $branchGroups[$story->product][$story->branch] . '</span>';?>
<?php if(!empty($story->module) and isset($modulePairs[$story->module])) echo "<span class='label label-gray label-badge'>{$modulePairs[$story->module]}</span> ";?>
<?php if($story->parent > 0) echo "<span class='label'>{$lang->story->childrenAB}</span>";?>
<?php echo html::a($storyLink,$story->title, null, "style='color: $story->color' data-app='execution'");?>
</td>
<td class='c-category' title='<?php echo zget($lang->story->categoryList, $story->category);?>'><?php echo zget($lang->story->categoryList, $story->category);?></td>
<td class='c-user' title='<?php echo zget($users, $story->openedBy);?>'><?php echo zget($users, $story->openedBy);?></td>
<td class='c-assignedTo <?php if(zget($users, $story->assignedTo) == 'Closed') echo 'closed-story'?> text-center' title='<?php echo zget($users, $story->assignedTo);?>'><?php $this->loadModel('story')->printAssignedHtml($story,$users);?></td>
<td class='c-estimate text-right' title="<?php echo $story->estimate . ' ' . $lang->hourCommon;?>"><?php echo $story->estimate . $config->hourUnit;?></td>
<?php $status = $this->processStatus('story', $story);?>
<td class='c-status' title='<?php echo $status;?>'>
<span class='status-story status-<?php echo $story->status;?>'><?php echo $status;?></span>
</td>
<td class='c-stage'><?php echo $lang->story->stageList[$story->stage];?></td>
<td class='linkbox c-count'>
<?php
$tasksLink = $this->createLink('story', 'tasks', "storyID=$story->id&executionID=$execution->id");
$storyTasks[$story->id] > 0 ? print(html::a($tasksLink, $storyTasks[$story->id], '', 'class="iframe"')) : print(0);
?>
<td class='c-count'>
<?php
$bugsLink = $this->createLink('story', 'bugs', "storyID=$story->id&executionID=$execution->id");
$storyBugs[$story->id] > 0 ? print(html::a($bugsLink, $storyBugs[$story->id], '', 'class="iframe"')) : print(0);
?>
</td>
<td class='c-count'>
<?php
$casesLink = $this->createLink('story', 'cases', "storyID=$story->id&executionID=$execution->id");
$storyCases[$story->id] > 0 ? print(html::a($casesLink, $storyCases[$story->id], '', 'class="iframe"')) : print(0);
?>
</td>
<td class='c-actions'>
<?php
$hasDBPriv = common::hasDBPriv($execution, 'execution');
if($canBeChanged)
{
$param = "executionID={$execution->id}&story={$story->id}&moduleID={$story->module}";
$story->reviewer = isset($story->reviewer) ? $story->reviewer : array();
$story->notReview = isset($story->notReview) ? $story->notReview : array();
if(common::hasPriv('story', 'review'))
{
$reviewDisabled = in_array($app->user->account, $story->notReview) and ($story->status == 'draft' or $story->status == 'changed') ? '' : 'disabled';
$story->from = 'execution';
common::printIcon('story', 'review', "story={$story->id}&from=story", $story, 'list', 'search', '', $reviewDisabled, false, "data-group=execution");
}
if(common::hasPriv('story', 'recall'))
{
$recallDisabled = empty($story->reviewedBy) and strpos('draft,changed', $story->status) !== false and !empty($story->reviewer) ? '' : 'disabled';
common::printIcon('story', 'recall', "story={$story->id}", $story, 'list', 'undo', 'hiddenwin', $recallDisabled, '', '', $lang->story->recall);
}
$lang->task->create = $lang->execution->wbs;
$toTaskDisabled = strpos('draft,closed', $story->status) !== false ? 'disabled' : '';
if(commonModel::isTutorialMode())
{
$wizardParams = helper::safe64Encode($param);
echo html::a($this->createLink('tutorial', 'wizard', "module=task&method=create&params=$wizardParams"), "<i class='icon-plus'></i>",'', "class='btn btn-task-create' title='{$lang->execution->wbs}' data-app='execution'");
}
else
{
if($hasDBPriv) common::printIcon('task', 'create', $param, '', 'list', 'plus', '', 'btn-task-create ' . $toTaskDisabled);
}
$lang->task->batchCreate = $lang->execution->batchWBS;
if($hasDBPriv) common::printIcon('task', 'batchCreate', "executionID={$execution->id}&story={$story->id}", '', 'list', 'pluses', '', $toTaskDisabled);
$lang->testcase->batchCreate = $lang->testcase->create;
if($productID and $hasDBPriv and common::hasPriv('testcase', 'create'))
{
echo html::a($this->createLink('testcase', 'create', "productID=$story->product&branch=$story->branch&moduleID=$story->module&form=&param=0&storyID=$story->id"), '<i class="icon-testcase-create icon-sitemap"></i>', '', "class='btn' title='{$lang->testcase->create}' data-app='qa'");
}
if($canBeChanged and common::hasPriv('execution', 'storyEstimate', $execution))
{
common::printIcon('execution', 'storyEstimate', "executionID=$execution->id&storyID=$story->id", '', 'list', 'estimate', '', 'iframe', true, "data-width='600px'");
}
if($canBeChanged and common::hasPriv('execution', 'unlinkStory', $execution))
{
common::printIcon('execution', 'unlinkStory', "executionID=$execution->id&storyID=$story->id&confirm=no", '', 'list', 'unlink', 'hiddenwin');
}
}
?>
</td>
<?php foreach($setting as $key => $value)
{
$this->story->printCell($value, $story, $users, '', $storyStages, $modulePairs, $storyTasks, $storyBugs, $storyCases, $useDatatable ? 'datatable' : 'table', '', $execution, $showBranch);
}
?>
</tr>
<?php endforeach;?>
</tbody>
</table>
<?php if(!$useDatatable) echo '</div>';?>
<div class='table-footer'>
<?php if($canBatchAction):?>
<div class="checkbox-primary check-all"><label><?php echo $lang->selectAll?></label></div>
@@ -491,6 +393,9 @@ function handleLinkButtonClick()
var xxcUrl = "xxc:openInApp/zentao-integrated/" + encodeURIComponent(window.location.href.replace(/.display=card/, '').replace(/\.xhtml/, '.html'));
window.open(xxcUrl);
}
<?php if(!empty($useDatatable)):?>
$(function(){$('#executionStoryForm').table();})
<?php endif;?>
</script>
<?php if(commonModel::isTutorialMode()): ?>
<style>
+6 -4
View File
@@ -15,7 +15,10 @@
<?php js::set('noAccess', $lang->user->error->noAccess)?>
<div id='mainMenu' class='clearfix'>
<div class='btn-toolbar pull-left'>
<span class='btn btn-link btn-active-text'><span class='text'><?php echo $lang->execution->team;?></span></span>
<span class='btn btn-link btn-active-text'>
<span class='text'><?php echo $lang->execution->team;?></span>
<span class="label label-light label-badge"><?php echo count($teamMembers);?></span>
</span>
</div>
<div class='btn-toolbar pull-right'>
<?php
@@ -91,8 +94,7 @@
<?php
if (common::hasPriv('execution', 'unlinkMember', $member))
{
$unlinkURL = $this->createLink('execution', 'unlinkMember', "executionID=$execution->id&userID=$member->userID&confirm=yes");
echo html::a("javascript:ajaxDelete(\"$unlinkURL\", \"mainContent\", confirmUnlinkMember)", '<i class="icon-green-execution-unlinkMember icon-unlink"></i>', '', "class='btn' title='{$lang->execution->unlinkMember}'");
echo html::a("javascript:deleteMember($execution->id, $member->userID)", '<i class="icon-green-execution-unlinkMember icon-unlink"></i>', '', "class='btni' title='{$lang->execution->unlinkMember}'");
}
?>
</td>
@@ -102,7 +104,7 @@
</tbody>
</table>
<div class='table-footer'>
<div class='table-statistic'><?php echo $lang->team->totalHours . '' . "<strong>$totalHours{$lang->execution->workHour}</strong>";?></div>
<div class='table-statistic'><?php echo $lang->team->totalHours . '' . "<strong>$totalHours{$lang->execution->workHour}" . sprintf($lang->project->teamMembersCount, count($teamMembers)) . "</strong>";?></div>
</div>
</form>
<?php endif;?>
+3 -3
View File
@@ -5,7 +5,7 @@
<?php if($execution->lifetime != 'ops') echo html::a($this->createLink('execution', 'linkstory', "executionID=$executionID"), $lang->execution->linkStory, '', "class='btn' data-app='execution'");?>
<?php echo html::a($this->createLink('task', 'create', "execution=$executionID"), $lang->execution->createTask, '', "class='btn' data-app='execution'");?>
<?php echo html::a($this->createLink('execution', 'task', "executionID=$executionID"), $lang->execution->goback, '', "class='btn' data-app='execution'");?>
<?php echo html::a('javascript:void(0)', $lang->execution->gobackExecution, '', "class='btn' onclick='closeTipModal($projectID)' data-app='project'");?>
<?php if($this->config->systemMode == 'new') echo html::a('javascript:void(0)', $lang->execution->gobackExecution, '', "class='btn' onclick='closeTipsModal($projectID)' data-app='project'");?>
</div>
</div>
<script>
@@ -16,9 +16,9 @@
* @access public
* @return void
*/
function closeTipModal(projectID)
function closeTipsModal(projectID)
{
$(".close").trigger("click");
$.apps.close('execution');
var link = createLink('project', 'execution', 'status=all&projectID=' + projectID);
$.apps.open(link, 'project');
}
+2
View File
@@ -41,10 +41,12 @@
<div class="detail-content">
<table class="table table-data">
<tbody>
<?php if($this->config->vision != 'lite'):?>
<tr>
<th class='w-100px'><?php echo $lang->story->product;?></th>
<td><?php echo html::a($this->createLink('product', 'view', "productID=$story->product"), $product->name, '', "data-app='product'");?></td>
</tr>
<?php endif;?>
<?php if($product->type != 'normal'):?>
<tr>
<th><?php echo sprintf($lang->product->branch, zget($lang->product->branchName, $product->type));?></th>
+33
View File
@@ -910,4 +910,37 @@ class extensionModel extends model
return $expiredDate;
}
/**
* Get plugins that are about to expire or have expired.
*
* @param bool $category
* @access public
* @return array
*/
public function getExpiringPlugins($category = false)
{
$extensions = $this->getLocalExtensions('installed');
$plugins = $category ? array('expiring' => array(), 'expired' => array()) : array();
$today = helper::today();
foreach($extensions as $extension)
{
$expiredDate = $this->getExpireDate($extension);
if(!empty($expiredDate))
{
$dateDiff = helper::diffDate($expiredDate, $today);
if($category)
{
if($dateDiff == 30 or $dateDiff == 14 or ($dateDiff <= 7 and $dateDiff >= 0)) $plugins['expiring'][] = $extension->name;
if($dateDiff <= -1) $plugins['expired'][] = $extension->name;
}
else
{
if($dateDiff == 30 or $dateDiff == 14 or $dateDiff <= 7) $plugins[] = $extension->name;
}
}
}
return $plugins;
}
}
-1
View File
@@ -742,7 +742,6 @@ class fileModel extends model
$content = $this->pasteImage($data->$editorID, $uid);
if($content) $data->$editorID = $content;
$data->$editorID = htmlspecialchars_decode($data->$editorID);
$data->$editorID = preg_replace("/ src=\"$readLinkReg\" /", ' src="' . $imgURL . '" ', $data->$editorID);
$data->$editorID = preg_replace("/ src=\"" . htmlSpecialString($readLinkReg) . "\" /", ' src="' . $imgURL . '" ', $data->$editorID);
+1 -1
View File
@@ -87,7 +87,7 @@ $lang->install->sessionFail = 'Bearbeiten Sie die PHP ini Datei um den session.
$lang->install->mkdirWin = '<p>%s Verzeichnis muss erstellt werden.<br /> Befehl <code>mkdir %s</code> zur Erstellung.</p>';
$lang->install->chmodWin = ' "%s" Berechtigung muss geändert werden.';
$lang->install->mkdirLinux = '<p>%s Verzeichnis muss erstellt werden.<br /> Befehl <code>mkdir -p %s</code> zur Erstellung.</p>';
$lang->install->chmodLinux = ' "%s" Berechtigung muss geändert werden.<br /> Befehl <code>chmod o=rwx -R %s</code> für die Anpassung.';
$lang->install->chmodLinux = ' "%s" Berechtigung muss geändert werden.<br /> Befehl <code>chmod 777 -R %s</code> für die Anpassung.';
$lang->install->timezone = 'Set Timezone';
$lang->install->defaultLang = 'Standard Sprache';
+2 -2
View File
@@ -23,7 +23,7 @@ $lang->install->start = 'Start';
$lang->install->keepInstalling = 'Continue installing this version';
$lang->install->seeLatestRelease = 'View the latest version';
$lang->install->welcome = 'Thanks for choosing ZenTao!';
$lang->install->license = 'ZenTao is under Z PUBLIC LICENSE(ZPL) 1.2';
$lang->install->license = 'License';
$lang->install->desc = <<<EOT
ZenTao ALM is an open source software released under <a href='http://zpl.pub/page/zplv12.html' target='_blank'>Z Public License</a>. It integrates with Product Management, Project Management, Test Management, Document Management, CI Management, etc. ZenTao is a perfect choice for managing software development projects.
@@ -116,7 +116,7 @@ $lang->install->sessionFail = 'Edit the php.ini file to set session.save_path.'
$lang->install->mkdirWin = '<p>%s directory has to be created.<br /> Run <code>mkdir %s</code> to create it.</p>';
$lang->install->chmodWin = ' "%s" privilege has to be changed.';
$lang->install->mkdirLinux = '<p>%s directory has to be created.<br /> Run <code>mkdir -p %s</code> to create it.</p>';
$lang->install->chmodLinux = ' "%s" permison has to be changed.<br /> Run <code>chmod o=rwx -R %s</code> to change it.';
$lang->install->chmodLinux = ' "%s" permison has to be changed.<br /> Run <code>chmod 777 -R %s</code> to change it.';
$lang->install->timezone = 'Set Timezone';
$lang->install->defaultLang = 'Default Language';
+1 -1
View File
@@ -87,7 +87,7 @@ $lang->install->sessionFail = 'Editez le fichier php.ini pour définier session
$lang->install->mkdirWin = '<p>Le répertoire %s doit être créé.<br /> Exécutez <code>mkdir %s</code> pour le créer.</p>';
$lang->install->chmodWin = ' "%s" privilege has to be changed.';
$lang->install->mkdirLinux = '<p>Le répertoire %s doit être créé.<br /> Exécutez <code>mkdir -p %s</code> pour le créer.</p>';
$lang->install->chmodLinux = 'Les permission de "%s" doivent être modifiées.<br /> Exécutez <code>chmod o=rwx -R %s</code> pour les changer.';
$lang->install->chmodLinux = 'Les permission de "%s" doivent être modifiées.<br /> Exécutez <code>chmod 777 -R %s</code> pour les changer.';
$lang->install->timezone = 'Set Timezone';
$lang->install->defaultLang = 'Langue par défaut';
+1 -1
View File
@@ -87,7 +87,7 @@ $lang->install->sessionFail = 'Sửa the php.ini file to set session.save_path.
$lang->install->mkdirWin = '<p>%s directory has to be created.<br /> Run <code>mkdir %s</code> to create it.</p>';
$lang->install->chmodWin = ' "%s" privilege has to be changed.';
$lang->install->mkdirLinux = '<p>%s directory has to be created.<br /> Run <code>mkdir -p %s</code> to create it.</p>';
$lang->install->chmodLinux = ' "%s" permison has to be changed.<br /> Run <code>chmod o=rwx -R %s</code> to change it.';
$lang->install->chmodLinux = ' "%s" permison has to be changed.<br /> Run <code>chmod 777 -R %s</code> to change it.';
$lang->install->timezone = 'Thiết lập Timezone';
$lang->install->defaultLang = 'Ngôn ngữ mặc định';
+2 -2
View File
@@ -23,7 +23,7 @@ $lang->install->start = '开始安装';
$lang->install->keepInstalling = '继续安装当前版本';
$lang->install->seeLatestRelease = '看看最新的版本';
$lang->install->welcome = '欢迎使用禅道项目管理软件!';
$lang->install->license = '禅道项目管理软件使用 Z PUBLIC LICENSE(ZPL) 1.2 授权协议';
$lang->install->license = '禅道项目管理软件授权协议';
$lang->install->desc = <<<EOT
禅道项目管理软件(ZenTaoPMS)是一款国产的,基于<a href='http://zpl.pub' target='_blank'>ZPL</a>协议,开源免费的项目管理软件,它集产品管理、项目管理、测试管理于一体,同时还包含了事务管理、组织管理等诸多功能,是中小型企业项目管理的首选。
@@ -116,7 +116,7 @@ $lang->install->sessionFail = '修改PHP配置文件,设置session.save_path
$lang->install->mkdirWin = '<p>需要创建目录%s。命令为:<br /> mkdir %s</p>';
$lang->install->chmodWin = '需要修改目录 "%s" 的权限。';
$lang->install->mkdirLinux = '<p>需要创建目录%s。<br /> 命令为:<br /> mkdir -p %s</p>';
$lang->install->chmodLinux = '需要修改目录 "%s" 的权限。<br />命令为:<br />chmod o=rwx -R %s';
$lang->install->chmodLinux = '需要修改目录 "%s" 的权限。<br />命令为:<br />chmod 777 -R %s';
$lang->install->timezone = '时区设置';
$lang->install->defaultLang = '默认语言';
+1 -1
View File
@@ -116,7 +116,7 @@ $lang->install->sessionFail = '修改PHP配置檔案,設置session.save_path
$lang->install->mkdirWin = '<p>需要創建目錄%s。命令為:<br /> mkdir %s</p>';
$lang->install->chmodWin = '需要修改目錄 "%s" 的權限。';
$lang->install->mkdirLinux = '<p>需要創建目錄%s。<br /> 命令為:<br /> mkdir -p %s</p>';
$lang->install->chmodLinux = '需要修改目錄 "%s" 的權限。<br />命令為:<br />chmod o=rwx -R %s';
$lang->install->chmodLinux = '需要修改目錄 "%s" 的權限。<br />命令為:<br />chmod 777 -R %s';
$lang->install->timezone = '時區設置';
$lang->install->defaultLang = '預設語言';
+1
View File
@@ -634,6 +634,7 @@ class installModel extends model
if($this->config->edition == 'open') $this->dao->update(TABLE_USER)->set('deleted')->eq('0')->where('deleted')->eq('1')->exec();
}
$config = new stdclass();
$config->module = 'common';
$config->owner = 'system';
$config->section = 'global';
+21 -10
View File
@@ -1479,9 +1479,10 @@ class kanban extends control
$this->loadModel($objectType)->delete(constant($table), $objectID);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$kanbanID = $this->kanban->getKanbanIDByregion($regionID);
$kanbanID = $regionID ? $this->kanban->getKanbanIDByregion($regionID) : $this->session->execution;
$browseType = $this->config->vision == 'lite' ? 'task' : $this->session->execLaneType;
$kanbanGroup = $this->kanban->getRDKanban($kanbanID, $browseType);
$groupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
$kanbanGroup = $this->kanban->getRDKanban($kanbanID, $browseType, 'id_desc', 0, $groupBy);
return print(json_encode($kanbanGroup));
}
@@ -1508,9 +1509,11 @@ class kanban extends control
if($from == 'RDKanban')
{
if(dao::isError()) return $this->sendError(dao::getError());
$regionID = $column->region;
$kanbanData = $this->loadModel('kanban')->getRDKanban($executionID, $this->session->execLaneType ? $this->session->execLaneType : 'all', 'id_desc', $regionID);
$kanbanData = json_encode($kanbanData);
$regionID = $column->region;
$execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
$execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
$kanbanData = $this->loadModel('kanban')->getRDKanban($executionID, $execLaneType, 'id_desc', $regionID, $execGroupBy);
$kanbanData = json_encode($kanbanData);
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'callback' => "parent.updateKanban($kanbanData, $regionID)"));
}
@@ -1589,9 +1592,11 @@ class kanban extends control
if($from == 'RDKanban')
{
if(dao::isError()) return $this->sendError(dao::getError());
$regionID = $column->region;
$kanbanData = $this->loadModel('kanban')->getRDKanban($executionID, $this->session->execLaneType ? $this->session->execLaneType : 'all', 'id_desc', $regionID);
$kanbanData = json_encode($kanbanData);
$execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
$execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
$regionID = $execGroupBy == 'default' ? $column->region : 0;
$kanbanData = $this->loadModel('kanban')->getRDKanban($executionID, $execLaneType, 'id_desc', $regionID, $execGroupBy);
$kanbanData = json_encode($kanbanData);
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'callback' => "parent.updateKanban($kanbanData, $regionID)"));
}
@@ -1711,12 +1716,18 @@ class kanban extends control
*/
public function ajaxMoveCard($cardID = 0, $fromColID = 0, $toColID = 0, $fromLaneID = 0, $toLaneID = 0, $executionID = 0, $browseType = 'all', $groupBy = '', $regionID = 0, $orderBy = '')
{
$fromCell = $this->dao->select('id, cards')->from(TABLE_KANBANCELL)
$fromCell = $this->dao->select('id, cards, lane')->from(TABLE_KANBANCELL)
->where('kanban')->eq($executionID)
->andWhere('lane')->eq($fromLaneID)
->andWhere('`column`')->eq($fromColID)
->beginIF(!$groupBy or $groupBy == 'default')->andWhere('lane')->eq($fromLaneID)->fi()
->beginIF($groupBy and $groupBy != 'default')
->andWhere('type')->eq($browseType)
->andWhere('cards')->like("%,$cardID,%")
->fi()
->fetch();
if($groupBy and $groupBy != 'default') $fromLaneID = $toLaneID = $fromCell->lane;
$toCell = $this->dao->select('id, cards')->from(TABLE_KANBANCELL)
->where('kanban')->eq($executionID)
->andWhere('lane')->eq($toLaneID)
+40 -26
View File
@@ -19,6 +19,22 @@ function loadMore(type, regionID)
});
}
/**
* Hide kanban actions.
*
* @access public
* @return void
*/
function hideAllAction()
{
$('.actions').hide();
$('.action').hide();
$('.kanban-group-header').hide();
$(".title").attr("disabled", true).css("pointer-events", "none");
$('.kanban-col.kanban-header-col').css('padding', '0px 0px 0px 0px');
window.sortableDisabled = true;
}
/**
* Display the kanban in full screen.
*
@@ -36,12 +52,7 @@ function fullScreen()
{
$('#kanbanContainer').addClass('fullscreen')
.on('scroll', tryUpdateKanbanAffix);
$('.actions').hide();
$('.action').hide();
$('.kanban-group-header').hide();
$(".title").attr("disabled", true).css("pointer-events", "none");
$('.kanban-col.kanban-header-col').css('padding', '0px 0px 0px 0px');
window.sortableDisabled = true;
hideAllAction();
$.cookie('isFullScreen', 1);
};
@@ -219,7 +230,7 @@ function renderLaneName($lane, lane, $kanban, columns, kanban)
$lane.parent().toggleClass('sort', canSort);
if(!$lane.children('.actions').length && (canSet || canDelete) && (CRKanban || kanbanInfo.status != 'closed') )
if(!$lane.children('.actions').length && (canSet || canDelete) && (CRKanban || kanbanInfo.status != 'closed'))
{
$([
'<div class="actions" title="' + kanbanLang.more + '">',
@@ -229,6 +240,7 @@ function renderLaneName($lane, lane, $kanban, columns, kanban)
'</div>'
].join('')).appendTo($lane);
}
if($.cookie('isFullScreen') == 1) hideAllAction();
}
/**
@@ -414,6 +426,7 @@ function renderKanbanItem(item, $item)
$title.css('color', '');
$title.children('.label-finish').hide();
}
if($.cookie('isFullScreen') == 1) hideAllAction();
}
/**
@@ -1269,25 +1282,26 @@ function initKanban($kanban)
$kanban.kanban(
{
data: region.groups,
maxColHeight: 510,
calcColHeight: calcColHeight,
fluidBoardWidth: fluidBoard,
minColWidth: 285,
maxColWidth: 285,
cardHeight: 60,
displayCards: displayCards,
createColumnText: kanbanLang.createColumn,
addItemText: '',
itemRender: renderKanbanItem,
onAction: handleKanbanAction,
onRenderKanban: adjustAddBtnPosition,
onRenderLaneName: renderLaneName,
onRenderHeaderCol: renderHeaderCol,
onRenderCount: renderCount,
sortable: handleSortCards,
virtualize: true,
virtualCardList: true,
data: region.groups,
maxColHeight: 510,
calcColHeight: calcColHeight,
fluidBoardWidth: fluidBoard,
minColWidth: 285,
maxColWidth: 285,
cardHeight: 60,
displayCards: displayCards,
createColumnText: kanbanLang.createColumn,
addItemText: '',
itemRender: renderKanbanItem,
onAction: handleKanbanAction,
onRenderKanban: adjustAddBtnPosition,
onRenderLaneName: renderLaneName,
onRenderHeaderCol: renderHeaderCol,
onRenderCount: renderCount,
sortable: handleSortCards,
virtualize: true,
virtualRenderOptions: {container: $(window).add($('#kanbanContainer'))},
virtualCardList: true,
droppable:
{
target: findDropColumns,
+55 -10
View File
@@ -1451,16 +1451,33 @@ class kanbanModel extends model
if($browseType == 'bug') $cardList = $this->loadModel('bug')->getExecutionBugs($executionID);
if($browseType == 'task') $cardList = $this->loadModel('execution')->getKanbanTasks($executionID, "id");
/* Get objects cards menus. */
if($browseType == 'story') $storyCardMenu = $this->getKanbanCardMenu($executionID, $cardList, 'story');
if($browseType == 'bug') $bugCardMenu = $this->getKanbanCardMenu($executionID, $cardList, 'bug');
if($browseType == 'task') $taskCardMenu = $this->getKanbanCardMenu($executionID, $cardList, 'task');
$lanes = $this->getLanes4Group($executionID, $browseType, $groupBy, $cardList);
if(empty($lanes)) return array();
$columns = $this->dao->select('t1.*, t2.`type` as columnType, t2.limit, t2.name as columnName, t2.color')->from(TABLE_KANBANCELL)->alias('t1')
$execution = $this->loadModel('execution')->getByID($executionID);
$columns = $this->dao->select('t1.*, GROUP_CONCAT(t1.cards) as cards, t2.`type` as columnType, t2.limit, t2.name as columnName, t2.color')->from(TABLE_KANBANCELL)->alias('t1')
->leftJoin(TABLE_KANBANCOLUMN)->alias('t2')->on('t1.`column` = t2.id')
->leftJoin(TABLE_KANBANLANE)->alias('t3')->on('t1.lane = t3.id')
->leftJoin(TABLE_KANBANREGION)->alias('t4')->on('t1.kanban = t4.kanban')
->where('t1.kanban')->eq($executionID)
->andWhere('t1.`type`')->eq($browseType)
->beginIF(isset($execution->type) and $execution->type == 'kanban')
->andWhere('t3.deleted')->eq(0)
->andWhere('t4.deleted')->eq(0)
->fi()
->groupBy('columnType')
->orderBy('column_asc')
->fetchAll('columnType');
$cardGroup = array();
$actions = array('setColumn', 'setWIP');
foreach($columns as $column)
{
if(empty($column->cards)) continue;
@@ -1482,6 +1499,7 @@ class kanbanModel extends model
$laneData['name'] = (($groupBy == 'pri' or $groupBy == 'severity') and $laneID) ? $this->lang->$browseType->$groupBy . ':' . $lane->name : $lane->name;
$laneData['color'] = $lane->color;
$laneData['order'] = $lane->order;
$laneData['type'] = $browseType;
$laneData['defaultCardType'] = $browseType;
/* Construct kanban column data. */
@@ -1492,6 +1510,13 @@ class kanbanModel extends model
if(in_array($column->columnType, array('testing', 'tested'))) $parentColumn = 'test';
if(in_array($column->columnType, array('fixing', 'fixed'))) $parentColumn = 'resolving';
/* Judge column action priv. */
$column->actions = array();
foreach($actions as $action)
{
if($this->isClickable($column, $action)) $column->actions[] = $action;
}
$columnData[$column->column]['id'] = $column->column;
$columnData[$column->column]['type'] = $column->columnType;
$columnData[$column->column]['name'] = $column->columnName;
@@ -1500,6 +1525,7 @@ class kanbanModel extends model
$columnData[$column->column]['laneType'] = $browseType;
$columnData[$column->column]['asParent'] = in_array($column->columnType, array('develop', 'test', 'resolving')) ? true : false;
$columnData[$column->column]['parentType'] = $parentColumn;
$columnData[$column->column]['actions'] = $column->actions;
$cardOrder = 1;
$objects = zget($cardGroup, $column->columnType, array());
@@ -1519,6 +1545,10 @@ class kanbanModel extends model
$cardData['deadline'] = $browseType == 'story' ? '' : $object->deadline;
$cardData['severity'] = $browseType == 'bug' ? $object->severity : '';
if($lane->type == 'story') $cardData['menus'] = $storyCardMenu[$object->id];
if($lane->type == 'bug') $cardData['menus'] = $bugCardMenu[$object->id];
if($lane->type == 'task') $cardData['menus'] = $taskCardMenu[$object->id];
if($browseType == 'task')
{
$cardData['name'] = $object->name;
@@ -1565,9 +1595,6 @@ class kanbanModel extends model
if(!isset($groupByList[$item->$groupBy])) $groupByList[$item->$groupBy] = $item->$groupBy;
}
if(in_array($groupBy, array('module', 'story', 'pri', 'severity'))) $objectPairs[0] = $this->lang->$browseType->$groupBy . ': ' . $this->lang->kanban->noGroup;
if(in_array($groupBy, array('assignedTo', 'type', 'category', 'source'))) $objectPairs[''] = $this->lang->$browseType->$groupBy . ': ' . $this->lang->kanban->noGroup;
if(in_array($groupBy, array('module', 'story', 'assignedTo')))
{
if($groupBy == 'module')
@@ -1596,17 +1623,22 @@ class kanbanModel extends model
}
else
{
unset($this->lang->$browseType->{$groupBy . 'List'}[0]);
unset($this->lang->$browseType->{$groupBy . 'List'}['']);
$objectPairs += $this->lang->$browseType->{$groupBy . 'List'};
}
if(in_array($groupBy, array('module', 'story', 'pri', 'severity'))) $objectPairs[0] = $this->lang->$browseType->$groupBy . ': ' . $this->lang->kanban->noGroup;
if(in_array($groupBy, array('assignedTo', 'source'))) $objectPairs[] = $this->lang->$browseType->$groupBy . ': ' . $this->lang->kanban->noGroup;
$laneColor = 0;
$order = 1;
foreach($objectPairs as $objectID => $objectName)
foreach($objectPairs as $objectType => $objectName)
{
if(!isset($groupByList[$objectID]) and $objectID) continue;
if(!isset($groupByList[$objectType]) and $objectType and !in_array($objectType, array('feature', 'design'))) continue;
$lane = new stdclass();
$lane->id = $groupBy . $objectID;
$lane->id = $groupBy . $objectType;
$lane->type = $browseType;
$lane->execution = $executionID;
$lane->name = $objectName;
@@ -1616,7 +1648,7 @@ class kanbanModel extends model
$order += 1;
$laneColor += 1;
if($laneColor == count($this->config->kanban->laneColorList)) $laneColor = 0;
$lanes[$objectID] = $lane;
$lanes[$objectType] = $lane;
}
return $lanes;
@@ -2032,7 +2064,7 @@ class kanbanModel extends model
$this->dao->insert(TABLE_KANBAN)->data($kanban)
->autoCheck()
->batchCheck($this->config->kanban->create->requiredFields, 'notempty')
->check('name', 'unique')
->check('name', 'unique', "space = {$kanban->space}")
->exec();
if(!dao::isError())
@@ -3115,7 +3147,20 @@ class kanbanModel extends model
*/
public function moveCard($cardID, $fromColID, $toColID, $fromLaneID, $toLaneID, $kanbanID = 0)
{
$fromCellCards = $this->dao->select('cards')->from(TABLE_KANBANCELL)->where('lane')->eq($fromLaneID)->andWhere('`column`')->eq($fromColID)->fetch('cards');
$groupBy = ($this->session->execGroupBy and $this->app->tab == 'execution') ? $this->session->execGroupBy : '';
$fromCell = $this->dao->select('cards, lane')->from(TABLE_KANBANCELL)
->where('`column`')->eq($fromColID)
->beginIF(!$groupBy or $groupBy == 'default')->andWhere('lane')->eq($fromLaneID)->fi()
->beginIF($groupBy and $groupBy != 'default')
->andWhere('type')->eq($this->session->execLaneType)
->andWhere('cards')->like("%,$cardID,%")
->fi()
->fetch();
if($groupBy and $groupBy != 'default') $fromLaneID = $toLaneID = $fromCell->lane;
$fromCellCards = $fromCell->cards;
$toCell = $this->dao->select('*')->from(TABLE_KANBANCELL)->where('lane')->eq($toLaneID)->andWhere('`column`')->eq($toColID)->fetch();
$toCellCards = $this->dao->select('cards')->from(TABLE_KANBANCELL)->where('lane')->eq($toLaneID)->andWhere('`column`')->eq($toColID)->fetch('cards');
+1 -1
View File
@@ -42,7 +42,7 @@
<tr>
<td colspan='2'><?php echo html::checkbox('importObjectList', $lang->kanban->importObjectList, $importObjects);?></td>
</tr>
<tr id='emptyTip' class='hidden'><td colspan='3' style='color: red;'><?php echo $lang->kanban->error->importObjNotEmpty;?></td></tr>
<tr id='emptyTip' class='hidden'><td colspan='2' style='color: red;'><?php echo $lang->kanban->error->importObjNotEmpty;?></td></tr>
<tr>
<td class='form-actions'><?php echo html::submitButton();?></td>
</tr>
+1 -1
View File
@@ -722,7 +722,7 @@ class mailModel extends model
}
}
if($objectType == 'meeting') $rooms = $this->loadmodel('meetingroom')->getpairs();
if($objectType == 'meeting') $rooms = $this->loadModel('meetingroom')->getpairs();
if($objectType == 'review') $this->app->loadLang('baseline');
/* Get mail content. */
+10
View File
@@ -12,6 +12,8 @@
$lang->misc = new stdclass();
$lang->misc->common = 'Misc';
$lang->misc->ping = 'Ping';
$lang->misc->view = 'Check';
$lang->misc->cancel = 'Cancel';
$lang->misc->zentao = new stdclass();
$lang->misc->zentao->version = 'Version %s';
@@ -70,6 +72,12 @@ $lang->misc->showAnnual = 'Add Annual Summary';
$lang->misc->annualDesc = 'After version 12.0, Annual Summary can be viewed on 『Report->Annual Summary』 page. <a href="%s" target="_blank" id="showAnnual" class="btn btn-mini btn-primary">See now</a>.';
$lang->misc->remind = 'New feature reminders';
$lang->misc->expiredTipsTitle = 'Dear system administrator, hello:';
$lang->misc->expiredCountTips = 'There are <span class="expired-tips text-blue" data-toggle="tooltip" data-placement="bottom" title="%s">%s plug-ins</span> in the system that will expire soon. To avoid affecting your regular use, please get in touch with the administrator to renew or uninstall them in time.';
$lang->misc->expiredPluginTips = 'Expired plugins are: %s. ';
$lang->misc->expiringPluginTips = 'The plug-ins that will expire are: %s.';
$lang->misc->expiredTipsForAdmin = 'There are %s plug-ins in the current system that will expire soon. To avoid affecting the regular use of the function, please renew or uninstall them in the system background plug-in management as soon as possible.';
$lang->misc->noticeRepair = "<h5>If you are not Administrator, contact your ZenTao Administrator to repair tables.</h5>
<h5>If you are the Administrator, login your ZenTao host and create a file named <span>%s</span>.</h5>
<p>Note:</p>
@@ -95,6 +103,7 @@ $lang->misc->feature->themeDesc = '<p>ZenTao 15.0+ a new "Youth Blue" theme
$lang->misc->feature->visionsDesc = "<p>The concept of interface has been added since 16.5. Users can deal with R & D affairs in <span style='color: #0c60e1'>[R&D]</span> and daily office affairs in <span style='color: #0c60e1'>[Lite]</span>.</p><p>You can view the current interface on the avatar, and click the name of the interface to view and switch other interfaces.</p>";
$lang->misc->feature->visionsImage = 'theme/default/images/main/visions_en.png';
$lang->misc->releaseDate['17.0'] = '2022-06-02';
$lang->misc->releaseDate['17.0.beta2'] = '2022-05-26';
$lang->misc->releaseDate['17.0.beta1'] = '2022-05-06';
$lang->misc->releaseDate['16.5'] = '2022-03-24';
@@ -173,6 +182,7 @@ $lang->misc->releaseDate['7.2.stable'] = '2015-05-22';
$lang->misc->releaseDate['7.1.stable'] = '2015-03-07';
$lang->misc->releaseDate['6.3.stable'] = '2014-11-07';
$lang->misc->feature->all['17.0'][] = array('title' => 'The detail experience is optimized, fix bugs.', 'desc' => '');
$lang->misc->feature->all['17.0.beta2'][] = array('title' => 'The detail experience is optimized, fix bugs.', 'desc' => '');
$lang->misc->feature->all['17.0.beta1'][] = array('title' => "Complete customer's high priority requirements", 'desc' => '');
$lang->misc->feature->all['16.5'][] = array('title' => 'Fix bug.', 'desc' => '');
+10
View File
@@ -12,6 +12,8 @@
$lang->misc = new stdclass();
$lang->misc->common = '杂项';
$lang->misc->ping = '防超时';
$lang->misc->view = '查看';
$lang->misc->cancel = '取消';
$lang->misc->zentao = new stdclass();
$lang->misc->zentao->version = '版本%s';
@@ -70,6 +72,12 @@ $lang->misc->showAnnual = '新增年度总结功能';
$lang->misc->annualDesc = '12.0版本后,新增年度总结功能,可以到『统计->年度总结』页面查看。 是否现在<a href="%s" target="_blank" id="showAnnual" class="btn btn-mini btn-primary">查看</a>';
$lang->misc->remind = '新功能提醒';
$lang->misc->expiredTipsTitle = '尊敬的系统管理员,您好:';
$lang->misc->expiredCountTips = '系统中有<span class="expired-tips text-blue" data-toggle="tooltip" data-placement="bottom" title="%s">%s个插件</span>即将到期,为避免影响您的正常使用,请联系管理员及时续费或卸载。';
$lang->misc->expiredPluginTips = '已到期的插件为:%s。';
$lang->misc->expiringPluginTips = '即将到期的插件为:%s。';
$lang->misc->expiredTipsForAdmin = '当前系统中有%s个插件即将到期,为避免影响功能的正常使用,请尽快到系统后台插件管理中进行续费或卸载处理。';
$lang->misc->noticeRepair = "<h5>普通用户请联系管理员进行修复</h5>
<h5>管理员请登录禅道所在的服务器,创建<span>%s</span>文件。</h5>
<p>注意:</p>
@@ -95,6 +103,7 @@ $lang->misc->feature->themeDesc = "<p>禅道15系列上线了全新的“
$lang->misc->feature->visionsDesc = "<p>从16.5开始增加了界面概念,用户可以在<span style='color:#0c60e1'>[研发综合界面]</span>中处理研发事务、在<span style='color:#0c60e1'>[迅捷界面]</span>处理日常办公事务。</p><p>在头像右侧即可查看当前所处界面,点击当前界面名称可查看和切换其他的界面。</p>";
$lang->misc->feature->visionsImage = 'theme/default/images/main/visions.png';
$lang->misc->releaseDate['17.0'] = '2022-06-02';
$lang->misc->releaseDate['17.0.beta2'] = '2022-05-26';
$lang->misc->releaseDate['17.0.beta1'] = '2022-05-06';
$lang->misc->releaseDate['16.5'] = '2022-03-24';
@@ -173,6 +182,7 @@ $lang->misc->releaseDate['7.2.stable'] = '2015-05-22';
$lang->misc->releaseDate['7.1.stable'] = '2015-03-07';
$lang->misc->releaseDate['6.3.stable'] = '2014-11-07';
$lang->misc->feature->all['17.0'][] = array('title' => '细节体验优化。修复Bug。', 'desc' => '');
$lang->misc->feature->all['17.0.beta2'][] = array('title' => '细节体验优化。修复Bug。', 'desc' => '');
$lang->misc->feature->all['17.0.beta1'][] = array('title' => '完成客户巴高优先级需求。修复Bug。', 'desc' => '');
$lang->misc->feature->all['16.5'][] = array('title' => '修复Bug。', 'desc' => '');
+25
View File
@@ -61,6 +61,31 @@ class miscModel extends model
return $remind;
}
/**
* Get the notification information about plugin expiration.
*
* @access public
* @return void
*/
public function getPluginRemind()
{
$plugins = $this->loadModel('extension')->getExpiringPlugins();
$remind = '';
$today = helper::today();
$showPluginRemind = (empty($this->config->global->showPluginRemind) or $this->config->global->showPluginRemind != $today) ? true : false;
if(!empty($plugins) and $this->app->user->admin and $showPluginRemind)
{
$pluginButton = html::a(helper::createLink('extension', 'browse'), $this->lang->misc->view, '', "id='pluginButton' class='btn btn-primary btn-wide' data-app='admin'");
$cancelButton = html::a('javascript: void(0);', $this->lang->misc->cancel, '', "id='cancelButton' class='btn btn-back btn-wide'");
$remind = '<p>' . sprintf($this->lang->misc->expiredTipsForAdmin, count($plugins)) . '</p>';
$remind .= '<p class="text-right">' . $pluginButton . $cancelButton . '</p>';
$this->loadModel('setting')->setItem("{$this->app->user->account}.common.global.showPluginRemind", $today);
}
return $remind;
}
/**
* Check one click package.
*
+1 -1
View File
@@ -1216,7 +1216,7 @@ class mrModel extends model
$task->repo = (int)$repoID;
$task->mr = (int)$mr;
$task->lines = $this->post->begin . ',' . $this->post->end;
$task->entry = $data->entry;
$task->entry = helper::safe64Decode($data->entry);
$task->v1 = $v1;
$task->v2 = $v2;
$task->desc = $this->loadModel('file')->pasteImage($data->commentText, $this->post->uid);
+1
View File
@@ -152,6 +152,7 @@ js::set('revision', '');
<tr>
<th></th>
<td colspan="3" class='form-actions'>
<?php echo html::hidden('entry');?>
<?php echo html::submitButton($lang->repo->submit, '', 'btn btn-wide btn-primary reviewSubmit');?>
<?php echo html::commonButton($lang->cancel, "onclick='hiddenForm()'", 'btn btn-wide');?>
</td>
+3 -1
View File
@@ -2,6 +2,8 @@
#projectList .c-actions {overflow: visible;}
#projectTableList > tr:last-child .c-actions .dropdown-menu {top: auto; bottom: 100%; margin-bottom: -5px;}
.c-budget {width: 100px;}
.c-date {width: 80px;}
.c-budget {width: 80px;}
.label-warning, .label-info {width: 60px !important;}
.project-type-label.label-outline {width: 50px; min-width: 50px;}
.c-date {width: 120px;}
+2
View File
@@ -0,0 +1,2 @@
.c-begin {width: 120px;}
.c-end {width: 100px;}
+2 -2
View File
@@ -39,8 +39,8 @@
<?php if($status == 'openedbyme'):?>
<th class='c-status'><?php common::printOrderLink('status', $orderBy, $vars, $lang->project->status);?></th>
<?php endif;?>
<th class='c-date'><?php common::printOrderLink('begin', $orderBy, $vars, $lang->project->begin);?></th>
<th class='c-date'><?php common::printOrderLink('end', $orderBy, $vars, $lang->project->end);?></th>
<th class='c-date c-begin'><?php common::printOrderLink('begin', $orderBy, $vars, $lang->project->begin);?></th>
<th class='c-date c-end'><?php common::printOrderLink('end', $orderBy, $vars, $lang->project->end);?></th>
<th class='text-right c-budget'><?php common::printOrderLink('budget', $orderBy, $vars, $lang->project->budget);?></th>
<th class='c-user'><?php common::printOrderLink('PM', $orderBy, $vars, $lang->project->PM);?></th>
<th class='text-center c-actions-6'><?php echo $lang->actions;?></th>
+9 -1
View File
@@ -77,6 +77,14 @@ class productModel extends model
{
$isQaModule = (strpos(',project,execution,', ",{$this->app->tab},") !== false and strpos(',bug,testcase,testtask,ajaxselectstory,', ",{$this->app->rawMethod},") !== false and isset($products[0])) ? true : false;
if(count($products) <= 2 and isset($products[0]))
{
unset($products[0]);
$productID = key($products);
}
if(empty($products)) return;
$this->app->loadLang('product');
if(!$isQaModule and !$productID)
{
@@ -150,7 +158,7 @@ class productModel extends model
{
if(defined('TUTORIAL')) return $productID;
if($productID == 0 and $this->cookie->preProductID) $productID = $this->cookie->preProductID;
if($productID == 0 and $this->cookie->preProductID and isset($products[$this->cookie->preProductID])) $productID = $this->cookie->preProductID;
if($productID == 0 and $this->session->product == '') $productID = key($products);
$this->session->set('product', (int)$productID, $this->app->tab);
+4 -3
View File
@@ -52,8 +52,9 @@ class program extends control
}
else
{
$topPrograms = $this->program->getList($status, $orderBy, $pager, 'top');
$programs = $this->program->getList($status, $orderBy, null, 'child', $topPrograms);
/* Get top programs and projects. */
$topObjects = $this->program->getList($status, $orderBy, $pager, 'top');
$programs = $this->program->getList($status, $orderBy, NULL, 'child', array_keys($topObjects));
/* Get summary. */
$topCount = $indCount = 0;
@@ -252,7 +253,7 @@ class program extends control
$this->view->program = $program;
$this->view->parents = $parents;
$this->view->programList = $this->program->getList();
$this->view->budgetUnitList = $this->program->getBudgetUnitList();
$this->view->budgetUnitList = $this->loadModel('project')->getBudgetUnitList();
$this->view->parentProgram = $parentProgram;
$this->view->availableBudget = $this->program->getBudgetLeft($parentProgram) + (float)$program->budget;
+47 -29
View File
@@ -131,34 +131,48 @@ class programModel extends model
* @param string $orderBy
* @param object $pager
* @param string $type top|child
* @param mixed $idList
* @param array $topIdList
* @access public
* @return array
*/
public function getList($status = 'all', $orderBy = 'id_asc', $pager = NULL, $type = '', $idList = '')
public function getList($status = 'all', $orderBy = 'id_asc', $pager = NULL, $type = '', $topIdList = array())
{
$projectIdList = array();
if($type === 'child')
$userViewIdList = trim($this->app->user->view->programs, ',') . ',' . trim($this->app->user->view->projects, ',');
$userViewIdList = array_filter(explode(',', $userViewIdList));
$objectIdList = array();
if($this->app->rawMethod == 'browse')
{
foreach($idList as $topID => $topProgram)
$pathList = $this->dao->select('id,path')->from(TABLE_PROJECT)
->where('type')->in('program,project')
->beginIF(!$this->app->user->admin)->andWhere('id')->in($userViewIdList)->fi()
->andWhere('deleted')->eq(0)
->orderBy('id_asc')
->fetchPairs('id');
foreach($pathList as $path)
{
$projectIdList += $topProgram->type == 'project' ? array($topID => $topID) : $this->dao->select('id')->from(TABLE_PROGRAM)->Where('path')->like(",$topID,%")->fetchPairs('id');
if($type == 'child' and !empty($topIdList))
{
$topID = $this->getTopByPath($path);
if(!in_array($topID, $topIdList)) continue;
}
foreach(explode(',', trim($path, ',')) as $pathID) $objectIdList[$pathID] = $pathID;
}
}
return $this->dao->select('*')->from(TABLE_PROGRAM)
->where('deleted')->eq(0)
->where('type')->in('program,project')
->andWhere('deleted')->eq(0)
->andWhere('vision')->eq($this->config->vision)
->andWhere('((type')->eq('program')
->beginIF(!$this->app->user->admin and $this->app->rawMethod != 'browse')->andWhere('id')->in($this->app->user->view->programs)->fi()
->markRight(1)
->orWhere('(type')->eq('project')
->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->projects)->fi()
->markRight(2)
->beginIF($status != 'all')->andWhere('status')->eq($status)->fi()
->beginIF(!$this->cookie->showClosed)->andWhere('status')->ne('closed')->fi()
->beginIF($this->app->rawMethod == 'browse')
->beginIF($type === 'top')->andWhere('parent')->eq(0)->fi()
->beginIF(!empty($projectIdList))->andWhere('id')->in($projectIdList)->fi()
->beginIF($type === 'child' or !$this->app->user->admin)->andWhere('id')->in($objectIdList)->fi()
->fi()
->beginIF(!$this->app->user->admin and $this->app->rawMethod != 'browse')->andWhere('id')->in($userViewIdList)->fi()
->orderBy($orderBy)
->page($pager)
->fetchAll('id');
@@ -193,15 +207,33 @@ class programModel extends model
}
$query = $this->session->programQuery;
$objectIdList = array();
if(!$this->app->user->admin)
{
$objectIdList = trim($this->app->user->view->programs, ',') . ',' . trim($this->app->user->view->projects, ',');
$objectIdList = array_filter(explode(',', $objectIdList));
asort($objectIdList);
if($this->app->rawMethod == 'browse')
{
$pathList = $this->dao->select('id,path')->from(TABLE_PROJECT)->where('id')->in($objectIdList)->andWhere('deleted')->eq(0)->fetchPairs('id');
foreach($pathList as $path)
{
foreach(explode(',', trim($path, ',')) as $pathID) $objectIdList[$pathID] = $pathID;
}
}
}
$programs = $this->dao->select('*')->from(TABLE_PROGRAM)
->where('deleted')->eq(0)
->andWhere('vision')->eq($this->config->vision)
->andWhere('type')->eq('program')
->beginIF(!$this->app->user->admin and $this->app->rawMethod != 'browse')->andWhere('id')->in($this->app->user->view->programs)->fi()
->beginIF(!$this->cookie->showClosed)->andWhere('status')->ne('closed')->fi()
->beginIF($query)->andWhere($query)->fi()
->beginIF(!$this->app->user->admin)->andWhere('id')->in($objectIdList)->fi()
->orderBy($orderBy)
->fetchAll('id');
return $programs;
}
@@ -1443,20 +1475,6 @@ class programModel extends model
return $stats;
}
/**
* Get budget unit list.
*
* @access public
* @return array
*/
public function getBudgetUnitList()
{
$budgetUnitList = array();
foreach(explode(',', $this->config->project->unitList) as $unit) $budgetUnitList[$unit] = zget($this->lang->project->unitList, $unit, '');
return $budgetUnitList;
}
/**
* Get program team member pairs.
*

Some files were not shown because too many files have changed in this diff Show More