This commit is contained in:
liyuchun
2022-06-07 08:50:23 +00:00
71 changed files with 3650 additions and 7968 deletions
+5 -4
View File
@@ -20,7 +20,8 @@ class taskRecordEstimateEntry extends Entry
*/
public function get($taskID)
{
if($this->config->edition != 'open')
$issetEffort = $this->loadModel('effort') ? true : false;
if($issetEffort)
{
$control = $this->loadController('effort', 'createForObject');
$control->createForObject('task', $taskID);
@@ -36,8 +37,8 @@ class taskRecordEstimateEntry extends Entry
if(isset($data->status) and $data->status == 'fail') return $this->sendError(zget($data, 'code', 400), $data->message);
$effort = array();
if($this->config->edition != 'open' and $data->data->efforts) $effort = $data->data->efforts;
if($this->config->edition == 'open' and $data->data->estimates) $effort = $data->data->estimates;
if($issetEffort and $data->data->efforts) $effort = $data->data->efforts;
if(!$issetEffort and $data->data->estimates) $effort = $data->data->estimates;
$this->send(200, array('effort' => $effort));
}
@@ -51,7 +52,7 @@ class taskRecordEstimateEntry extends Entry
*/
public function post($taskID)
{
if($this->config->edition != 'open')
if($this->loadModel('effort'))
{
$fields = 'id,dates,consumed,left,objectType,objectID,work';
$this->batchSetPost($fields);
+8 -1
View File
@@ -15,7 +15,7 @@ error_reporting(E_ALL ^ E_NOTICE ^ E_STRICT);
define('IN_SHELL', true);
/* Judge the args. */
if($argc != 2) die('Usage: ' . basename(__FILE__) . " <request>\n");
if($argc < 2 or $argc > 3) die('Usage: ' . basename(__FILE__) . " <request>\n");
/* Parse the request into params. */
$request = parse_url(trim($argv[1]));
@@ -33,6 +33,13 @@ include './framework/helper.class.php';
$app = router::createApp('pms', dirname(dirname(__FILE__)), 'router');
$common = $app->loadCommon();
/* Set the PATH_INFO request. */
if(!empty($argv[2]) && $argv[2] == 'pathinfo')
{
$config->requestType = 'PATH_INFO';
$config->requestFix = '-';
}
/* Set the PATH_INFO variable. */
if($config->requestType == 'PATH_INFO')
{
+2
View File
@@ -0,0 +1,2 @@
REPLACE INTO `zt_report` (`code`, `name`, `module`, `sql`, `vars`, `langs`, `params`, `step`, `desc`, `addedBy`, `addedDate`) VALUES
('bug-resolve', '{\"zh-cn\":\"Bug\\u89e3\\u51b3\\u8868\",\"zh-tw\":\"Bug\\u89e3\\u6c7a\\u8868\",\"en\":\"Solved Bugs\"}', ',test', 'select *,if($product=\'\',0,product) as customproduct from TABLE_BUG where deleted=\'0\' and resolution!=\'\' and if($startDate=\'\',1,resolvedDate>=$startDate) and if($endDate=\'\',1,resolvedDate<=$endDate) having customproduct=$product', '{\"varName\":[\"product\",\"startDate\",\"endDate\"],\"showName\":[\"\\u4ea7\\u54c1\",\"\\u89e3\\u51b3\\u65e5\\u671f\\u5f00\\u59cb\",\"\\u89e3\\u51b3\\u65e5\\u671f\\u7ed3\\u675f\"],\"requestType\":[\"select\",\"date\",\"date\"],\"selectList\":[\"product\",\"user\",\"user\"],\"default\":[\"\",\"$MONTHBEGIN\",\"$MONTHEND\"]}', '{\"count\":{\"zh-cn\":\"\\u9700\\u6c42\\u6570\",\"zh-tw\":\"\\u9700\\u6c42\\u6570\",\"en\":\"Stories\"},\"done\":{\"zh-cn\":\"\\u5b8c\\u6210\\u6570\",\"zh-tw\":\"\\u5b8c\\u6210\\u6570\",\"en\":\"Done\"}}', '{\"group1\":\"resolvedBy\",\"isUser\":{\"group1\":[\"1\"]},\"group2\":\"\",\"reportField\":[\"resolution\"],\"reportType\":[\"count\"],\"sumAppend\":[\"\"],\"reportTotal\":[\"1\"],\"percent\":[\"1\"],\"contrast\":[\"crystalTotal\"],\"showAlone\":[\"1\"]}', 2, '{\"zh-cn\":\"\\u5217\\u51fa\\u89e3\\u51b3\\u7684Bug\\u603b\\u6570\\uff0c\\u89e3\\u51b3\\u65b9\\u6848\\u7684\\u5206\\u5e03\\uff0c\\u5360\\u7684\\u6bd4\\u4f8b\\uff08\\u8be5\\u7528\\u6237\\u89e3\\u51b3\\u7684Bug\\u7684\\u6570\\u91cf\\u5360\\u6240\\u6709\\u7684\\u89e3\\u51b3\\u7684Bug\\u7684\\u6570\\u91cf)\\u3002\",\"zh-tw\":\"\\u5217\\u51fa\\u89e3\\u6c7a\\u7684Bug\\u7e3d\\u6578\\uff0c\\u89e3\\u6c7a\\u65b9\\u6848\\u7684\\u5206\\u5e03\\uff0c\\u5360\\u7684\\u6bd4\\u4f8b\\uff08\\u8a72\\u7528\\u6236\\u89e3\\u6c7a\\u7684Bug\\u7684\\u6578\\u91cf\\u5360\\u6240\\u6709\\u7684\\u89e3\\u6c7a\\u7684Bug\\u7684\\u6578\\u91cf)\\u3002\",\"en\":\"List the total number of bugs resolved, the distribution of solutions, and the percentage (the number of bugs solved by this user to the total number of bugs resolved).\"}', 'admin', '2015-07-24 13:44:25');
+1 -1
View File
@@ -6814,7 +6814,7 @@ REPLACE INTO `zt_report` (`code`, `name`, `module`, `sql`, `vars`, `langs`, `par
('user-login', '{\"zh-cn\":\"\\u5458\\u5de5\\u767b\\u5f55\\u6b21\\u6570\\u7edf\\u8ba1\\u8868\",\"zh-tw\":\"\\u54e1\\u5de5\\u767b\\u9304\\u6b21\\u6578\\u7d71\\u8a08\\u8868\",\"en\":\"Login Times\"}', ',staff', 'select actor,LEFT(`date`,10) as `day` from TABLE_ACTION where `action`=\'login\' and if($startDate=\'\',1,`date`>=$startDate) and if($endDate=\'\',1,`date`<=$endDate) order by `date` asc, actor asc', '{\"varName\":[\"startDate\",\"endDate\"],\"showName\":[\"\\u8d77\\u59cb\\u65f6\\u95f4\",\"\\u7ed3\\u675f\\u65f6\\u95f4\"],\"requestType\":[\"date\",\"date\"],\"selectList\":[\"user\",\"user\"],\"default\":[\"$MONTHBEGIN\",\"$MONTHEND\"]}', '{\"count\":{\"zh-cn\":\"\\u9700\\u6c42\\u6570\",\"zh-tw\":\"\\u9700\\u6c42\\u6570\",\"en\":\"Stories\"},\"done\":{\"zh-cn\":\"\\u5b8c\\u6210\\u6570\",\"zh-tw\":\"\\u5b8c\\u6210\\u6570\",\"en\":\"Done\"}}', '{\"group1\":\"actor\",\"isUser\":{\"group1\":[\"1\"]},\"group2\":\"\",\"reportField\":[\"day\"],\"reportType\":[\"count\"],\"sumAppend\":[\"\"],\"reportTotal\":[\"1\"]}', 2, '{\"zh-cn\":\"\\u5b9e\\u73b0\\u5458\\u5de5\\u767b\\u5f55\\u6b21\\u6570\\u7edf\\u8ba1\\u62a5\\u8868\\uff0c\\u6309\\u7167\\u5929\\u7edf\\u8ba1\\u6bcf\\u5929\\u6bcf\\u4e2a\\u4eba\\u7684\\u767b\\u5f55\\u6b21\\u6570\\uff0c\\u4ee5\\u53ca\\u603b\\u6570\\u3002\",\"zh-tw\":\"\\u5be6\\u73fe\\u54e1\\u5de5\\u767b\\u9304\\u6b21\\u6578\\u7d71\\u8a08\\u5831\\u8868\\uff0c\\u6309\\u7167\\u5929\\u7d71\\u8a08\\u6bcf\\u5929\\u6bcf\\u500b\\u4eba\\u7684\\u767b\\u9304\\u6b21\\u6578\\uff0c\\u4ee5\\u53ca\\u7e3d\\u6578\\u3002\",\"en\":\"Realize the employee login times statistics report, counting the login times of each person and the total number by day.\"}', 'admin', '2015-07-24 14:28:11'),
('effort', '{\"zh-cn\":\"\\u65e5\\u5fd7\\u6c47\\u603b\\u8868\",\"zh-tw\":\"\\u65e5\\u8a8c\\u532f\\u7e3d\\u8868\",\"en\":\"Effort Summary\"}', ',staff', 'select t1.account,t1.consumed,t1.`date`,if($dept=\'0\',0,t2.dept) as dept from TABLE_EFFORT as t1 left join TABLE_USER as t2 on t1.account=t2.account where t1.`deleted`=\'0\' and if($startDate=\'\',1,t1.`date`>=$startDate) and if($endDate=\'\',1,t1.`date`<=$endDate) having dept=$dept order by `date` asc', '{\"varName\":[\"dept\",\"startDate\",\"endDate\"],\"showName\":[\"\\u90e8\\u95e8\",\"\\u8d77\\u59cb\\u65f6\\u95f4\",\"\\u7ed3\\u675f\\u65f6\\u95f4\"],\"requestType\":[\"select\",\"date\",\"date\"],\"selectList\":[\"dept\",\"user\",\"user\"],\"default\":[\"\",\"$MONTHBEGIN\",\"$MONTHEND\"]}', '{\"date\":{\"zh-cn\":\"\\u65e5\\u671f\",\"zh-tw\":\"\\u65e5\\u671f\",\"en\":\"Date\"},\"consumed\":{\"zh-cn\":\"\\u6d88\\u8017\\u5de5\\u65f6\",\"zh-tw\":\"\\u6d88\\u8017\\u5de5\\u65f6\",\"en\":\"Cost\"}}', '{\"group1\":\"account\",\"isUser\":{\"group1\":[\"1\"]},\"group2\":\"\",\"reportField\":[\"date\"],\"reportType\":[\"sum\"],\"sumAppend\":[\"consumed\"]}', 2, '{\"zh-cn\":\"\\u67e5\\u770b\\u67d0\\u4e2a\\u65f6\\u95f4\\u6bb5\\u5185\\u7684\\u65e5\\u5fd7\\u60c5\\u51b5\\uff0c\\u53ef\\u4ee5\\u6309\\u7167\\u90e8\\u95e8\\u9009\\u62e9\\u3002\",\"zh-tw\":\"\\u67e5\\u770b\\u67d0\\u500b\\u6642\\u9593\\u6bb5\\u5167\\u7684\\u65e5\\u8a8c\\u60c5\\u6cc1\\uff0c\\u53ef\\u4ee5\\u6309\\u7167\\u90e8\\u9580\\u9078\\u64c7\\u3002\",\"en\":\"You can view the logs of a certain period by department.\"}', 'admin', '2015-07-27 13:53:32'),
('company-dynamic', '{\"zh-cn\":\"\\u516c\\u53f8\\u52a8\\u6001\\u6c47\\u603b\\u8868\",\"zh-tw\":\"\\u516c\\u53f8\\u52d5\\u614b\\u532f\\u7e3d\\u8868\",\"en\":\"Company Dynamics\"}', ',staff', 'select t1.day,t2.userlogin,t3.consumed,t4.storyopen,t5.storyclose,t6.taskopen,t7.taskfinish,t8.bugopen,t9.bugresolve,t1.actions from ztv_dayactions as t1 left join ztv_dayuserlogin as t2 on t1.day=t2.day left join ztv_dayeffort as t3 on t1.day=t3.date left join ztv_daystoryopen as t4 on t1.day=t4.day left join ztv_daystoryclose as t5 on t1.day=t5.day left join ztv_daytaskopen as t6 on t1.day=t6.day left join ztv_daytaskfinish as t7 on t1.day=t7.day left join ztv_daybugopen as t8 on t1.day=t8.day left join ztv_daybugresolve as t9 on t1.day=t9.day where if($startDate=\'\',1,t1.day>=$startDate) and if($endDate=\'\',1,t1.day<=$endDate)', '{\"varName\":[\"startDate\",\"endDate\"],\"showName\":[\"\\u8d77\\u59cb\\u65f6\\u95f4\",\"\\u7ed3\\u675f\\u65f6\\u95f4\"],\"requestType\":[\"date\",\"date\"],\"selectList\":[\"user\",\"user\"],\"default\":[\"$MONTHBEGIN\",\"$MONTHEND\"]}', '{\"day\":{\"zh-cn\":\"\\u65e5\\u671f\",\"zh-tw\":\"\\u65e5\\u671f\",\"en\":\"Date\"},\"userlogin\":{\"zh-cn\":\"\\u767b\\u5f55\\u6b21\\u6570\",\"zh-tw\":\"\\u767b\\u9304\\u6b21\\u6578\",\"en\":\"Login\"},\"consumed\":{\"zh-cn\":\"\\u65e5\\u5fd7\\u5de5\\u65f6\",\"zh-tw\":\"\\u65e5\\u8a8c\\u5de5\\u6642\",\"en\":\"Cost(h)\"},\"storyopen\":{\"zh-cn\":\"\\u65b0\\u589e\\u9700\\u6c42\\u6570\",\"zh-tw\":\"\\u65b0\\u589e\\u9700\\u6c42\\u6578\",\"en\":\"Open Story\"},\"storyclose\":{\"zh-cn\":\"\\u5173\\u95ed\\u9700\\u6c42\\u6570\",\"zh-tw\":\"\\u95dc\\u9589\\u9700\\u6c42\\u6578\",\"en\":\"Closed Story\"},\"taskopen\":{\"zh-cn\":\"\\u65b0\\u589e\\u4efb\\u52a1\\u6570\",\"zh-tw\":\"\\u65b0\\u589e\\u4efb\\u52d9\\u6578\",\"en\":\"Open Task\"},\"taskfinish\":{\"zh-cn\":\"\\u5b8c\\u6210\\u4efb\\u52a1\\u6570\",\"zh-tw\":\"\\u5b8c\\u6210\\u4efb\\u52d9\\u6578\",\"en\":\"Finished Task\"},\"bugopen\":{\"zh-cn\":\"\\u65b0\\u589eBug\\u6570\",\"zh-tw\":\"\\u65b0\\u589eBug\\u6578\",\"en\":\"Open Bug\"},\"bugresolve\":{\"zh-cn\":\"\\u89e3\\u51b3Bug\\u6570\",\"zh-tw\":\"\\u89e3\\u51b3Bug\\u6578\",\"en\":\"Resolved bug\"},\"actions\":{\"zh-cn\":\"\\u52a8\\u6001\\u6570\",\"zh-tw\":\"\\u52d5\\u614b\\u6578\",\"en\":\"Dynamics\"}}', '{\"group1\":\"day\",\"isUser\":{\"group1\":[\"1\"]},\"group2\":\"\",\"reportField\":[\"userlogin\",\"consumed\",\"storyopen\",\"storyclose\",\"taskopen\",\"taskfinish\",\"bugopen\",\"bugresolve\",\"actions\"],\"reportType\":[\"sum\",\"sum\",\"sum\",\"sum\",\"sum\",\"sum\",\"sum\",\"sum\",\"sum\"],\"sumAppend\":[\"userlogin\",\"consumed\",\"storyopen\",\"storyclose\",\"taskopen\",\"taskfinish\",\"bugopen\",\"bugresolve\",\"actions\"]}', 2, '{\"zh-cn\":\"\\u53ef\\u4ee5\\u6307\\u5b9a\\u4e00\\u4e2a\\u65f6\\u671f\\uff0c\\u5217\\u51fa\\u76f8\\u5e94\\u7684\\u6570\\u636e\\uff1a1. \\u6bcf\\u5929\\u7684\\u767b\\u5f55\\u6b21\\u6570\\u30022. \\u6bcf\\u5929\\u7684\\u65e5\\u5fd7\\u5de5\\u65f6\\u91cf\\u30023. \\u6bcf\\u5929\\u65b0\\u589e\\u7684\\u9700\\u6c42\\u6570\\u30024. \\u6bcf\\u5929\\u5173\\u95ed\\u7684\\u9700\\u6c42\\u6570\\u30025. \\u6bcf\\u5929\\u65b0\\u589e\\u7684\\u4efb\\u52a1\\u6570\\u30026. \\u6bcf\\u5929\\u5b8c\\u6210\\u7684\\u4efb\\u52a1\\u6570\\u30027. \\u6bcf\\u5929\\u65b0\\u589e\\u7684Bug\\u6570\\u30028. \\u6bcf\\u5929\\u89e3\\u51b3\\u7684Bug\\u6570\\u30029. \\u6bcf\\u5929\\u7684\\u52a8\\u6001\\u6570\\u3002\",\"zh-tw\":\"\\u53ef\\u4ee5\\u6307\\u5b9a\\u4e00\\u500b\\u6642\\u671f\\uff0c\\u5217\\u51fa\\u76f8\\u61c9\\u7684\\u6578\\u64da\\uff1a1. \\u6bcf\\u5929\\u7684\\u767b\\u9304\\u6b21\\u6578\\u30022. \\u6bcf\\u5929\\u7684\\u65e5\\u8a8c\\u5de5\\u6642\\u91cf\\u30023. \\u6bcf\\u5929\\u65b0\\u589e\\u7684\\u9700\\u6c42\\u6578\\u30024. \\u6bcf\\u5929\\u95dc\\u9589\\u7684\\u9700\\u6c42\\u6578\\u30025. \\u6bcf\\u5929\\u65b0\\u589e\\u7684\\u4efb\\u52d9\\u6578\\u30026. \\u6bcf\\u5929\\u5b8c\\u6210\\u7684\\u4efb\\u52d9\\u6578\\u30027. \\u6bcf\\u5929\\u65b0\\u589e\\u7684Bug\\u6578\\u30028. \\u6bcf\\u5929\\u89e3\\u6c7a\\u7684Bug\\u6578\\u30029. \\u6bcf\\u5929\\u7684\\u52d5\\u614b\\u6578\\u3002\",\"en\":\"The summary of company dynamics\"}', 'admin', '2015-07-27 15:09:42'),
('bug-resolve', '{\"zh-cn\":\"Bug\\u89e3\\u51b3\\u8868\",\"zh-tw\":\"Bug\\u89e3\\u6c7a\\u8868\",\"en\":\"Solved Bugs\"}', ',test', 'select *,if($product=\'\',0,product) as customproduct from TABLE_BUG where deleted=\'0\' and resolution!=\'\' and if($startDate=\'\',1,resolvedDate>=$startDate) and if($endDate=\'\',1,resolvedDate<=$endDate) having customproduct=$product', '{\"varName\":[\"product\",\"startDate\",\"endDate\"],\"showName\":[\"\\u4ea7\\u54c1\",\"\\u89e3\\u51b3\\u65e5\\u671f\\u5f00\\u59cb\",\"\\u89e3\\u51b3\\u65e5\\u671f\\u7ed3\\u675f\"],\"requestType\":[\"select\",\"date\",\"date\"],\"selectList\":[\"product\",\"user\",\"user\"],\"default\":[\"\",\"$MONTHBEGIN\",\"$MONTHEND\"]}', '{\"count\":{\"zh-cn\":\"\\u9700\\u6c42\\u6570\",\"zh-tw\":\"\\u9700\\u6c42\\u6570\",\"en\":\"Stories\"},\"done\":{\"zh-cn\":\"\\u5b8c\\u6210\\u6570\",\"zh-tw\":\"\\u5b8c\\u6210\\u6570\",\"en\":\"Done\"}}', '{\"group1\":\"resolvedBy\",\"isUser\":{\"group1\":[\"1\"]},\"group2\":\"\",\"reportField\":[\"resolution\"],\"reportType\":[\"count\"],\"sumAppend\":[\"\"],\"reportTotal\":[\"1\"],\"percent\":[\"1\"],\"contrast\":[\"crystalTotal\"],\"showAlone\":[\"1\"]}', 2, '{\"zh-cn\":\"List the total number of bugs resolved, the distribution of solutions, and the percentage (the number of bugs solved by this user to the total number of bugs resolved).\",\"zh-tw\":\"\\u5217\\u51fa\\u89e3\\u6c7a\\u7684Bug\\u7e3d\\u6578\\uff0c\\u89e3\\u6c7a\\u65b9\\u6848\\u7684\\u5206\\u5e03\\uff0c\\u5360\\u7684\\u6bd4\\u4f8b\\uff08\\u8a72\\u7528\\u6236\\u89e3\\u6c7a\\u7684Bug\\u7684\\u6578\\u91cf\\u5360\\u6240\\u6709\\u7684\\u89e3\\u6c7a\\u7684Bug\\u7684\\u6578\\u91cf)\\u3002\",\"en\":\"percentage:self resolved \\/ all resolved\"}', 'admin', '2015-07-24 13:44:25'),
('bug-resolve', '{\"zh-cn\":\"Bug\\u89e3\\u51b3\\u8868\",\"zh-tw\":\"Bug\\u89e3\\u6c7a\\u8868\",\"en\":\"Solved Bugs\"}', ',test', 'select *,if($product=\'\',0,product) as customproduct from TABLE_BUG where deleted=\'0\' and resolution!=\'\' and if($startDate=\'\',1,resolvedDate>=$startDate) and if($endDate=\'\',1,resolvedDate<=$endDate) having customproduct=$product', '{\"varName\":[\"product\",\"startDate\",\"endDate\"],\"showName\":[\"\\u4ea7\\u54c1\",\"\\u89e3\\u51b3\\u65e5\\u671f\\u5f00\\u59cb\",\"\\u89e3\\u51b3\\u65e5\\u671f\\u7ed3\\u675f\"],\"requestType\":[\"select\",\"date\",\"date\"],\"selectList\":[\"product\",\"user\",\"user\"],\"default\":[\"\",\"$MONTHBEGIN\",\"$MONTHEND\"]}', '{\"count\":{\"zh-cn\":\"\\u9700\\u6c42\\u6570\",\"zh-tw\":\"\\u9700\\u6c42\\u6570\",\"en\":\"Stories\"},\"done\":{\"zh-cn\":\"\\u5b8c\\u6210\\u6570\",\"zh-tw\":\"\\u5b8c\\u6210\\u6570\",\"en\":\"Done\"}}', '{\"group1\":\"resolvedBy\",\"isUser\":{\"group1\":[\"1\"]},\"group2\":\"\",\"reportField\":[\"resolution\"],\"reportType\":[\"count\"],\"sumAppend\":[\"\"],\"reportTotal\":[\"1\"],\"percent\":[\"1\"],\"contrast\":[\"crystalTotal\"],\"showAlone\":[\"1\"]}', 2, '{\"zh-cn\":\"\\u5217\\u51fa\\u89e3\\u51b3\\u7684Bug\\u603b\\u6570\\uff0c\\u89e3\\u51b3\\u65b9\\u6848\\u7684\\u5206\\u5e03\\uff0c\\u5360\\u7684\\u6bd4\\u4f8b\\uff08\\u8be5\\u7528\\u6237\\u89e3\\u51b3\\u7684Bug\\u7684\\u6570\\u91cf\\u5360\\u6240\\u6709\\u7684\\u89e3\\u51b3\\u7684Bug\\u7684\\u6570\\u91cf)\\u3002\",\"zh-tw\":\"\\u5217\\u51fa\\u89e3\\u6c7a\\u7684Bug\\u7e3d\\u6578\\uff0c\\u89e3\\u6c7a\\u65b9\\u6848\\u7684\\u5206\\u5e03\\uff0c\\u5360\\u7684\\u6bd4\\u4f8b\\uff08\\u8a72\\u7528\\u6236\\u89e3\\u6c7a\\u7684Bug\\u7684\\u6578\\u91cf\\u5360\\u6240\\u6709\\u7684\\u89e3\\u6c7a\\u7684Bug\\u7684\\u6578\\u91cf)\\u3002\",\"en\":\"List the total number of bugs resolved, the distribution of solutions, and the percentage (the number of bugs solved by this user to the total number of bugs resolved).\"}', 'admin', '2015-07-24 13:44:25'),
('project-progress', '{\"zh-cn\":\"\\u9879\\u76ee\\u8fdb\\u5c55\\u8868\",\"zh-tw\":\"\\u9805\\u76ee\\u9032\\u5c55\\u8868\",\"en\":\"Project Progress Report\"}', ',project', 'select t1.id,t4.name as project,t4.id,t1.name as execution,t1.status,t2.number as tasks,round(t2.consumed,2) as consumed,round(t2.`left`,2) as `left`,t3.stories,t2.undone as undoneTask,t3.undone as undoneStory,t2.totalReal from TABLE_EXECUTION as t1 \r\nleft join ztv_executionsummary as t2 on t1.id=t2.execution\r\nleft join ztv_projectstories as t3 on t1.id=t3.execution\r\nleft join TABLE_PROJECT as t4 on t4.id=t1.project\r\nwhere t1.deleted=\'0\' and t1.type in (\'sprint\',\'stage\') and if($project=\'\',1,t4.id=$project) and if($execution=\'\',1,t1.id=$execution) and if($status=\'\' or $status=\'delay\',1,t1.status=$status) and if($status=\'delay\',t1.`end` < curdate(),1)', '{\"varName\":[\"project\",\"execution\",\"status\"],\"showName\":[\"\\u9879\\u76ee\\u5217\\u8868\",\"\\u6267\\u884c\\u5217\\u8868\",\"\\u6267\\u884c\\u72b6\\u6001\"],\"requestType\":[\"select\",\"select\",\"select\"],\"selectList\":[\"project\",\"execution\",\"project.status\"],\"default\":[\"\",\"\",\"\"]}', '{\"stories\":{\"zh-cn\":\"\\u9700\\u6c42\\u6570\",\"zh-tw\":\"\\u9700\\u6c42\\u6570\",\"en\":\"Stories\"},\"tasks\":{\"zh-cn\":\"\\u4efb\\u52a1\\u6570\",\"zh-tw\":\"\\u4efb\\u52a1\\u6570\",\"en\":\"Tasks\"},\"undoneStory\":{\"zh-cn\":\"\\u5269\\u4f59\\u9700\\u6c42\\u6570\",\"zh-tw\":\"\\u5269\\u4f59\\u9700\\u6c42\\u6570\",\"en\":\"Undone Story\"},\"undoneTask\":{\"zh-cn\":\"\\u5269\\u4f59\\u4efb\\u52a1\\u6570\",\"zh-tw\":\"\\u5269\\u4f59\\u4efb\\u52a1\\u6570\",\"en\":\"Undone Task\"},\"consumed\":{\"zh-cn\":\"\\u5df2\\u6d88\\u8017\\u5de5\\u65f6\",\"zh-tw\":\"\\u5df2\\u6d88\\u8017\\u5de5\\u65f6\",\"en\":\"Cost(h)\"},\"left\":{\"zh-cn\":\"\\u5269\\u4f59\\u5de5\\u65f6\",\"zh-tw\":\"\\u5269\\u4f59\\u5de5\\u65f6\",\"en\":\"Left(h)\"},\"consumedPercent\":{\"zh-cn\":\"\\u8fdb\\u5ea6\",\"zh-tw\":\"\\u8fdb\\u5ea6\",\"en\":\"Process\"},\"execution\":{\"zh-cn\":\"\\u6267\\u884c\\u540d\\u79f0\"}}', '{\"group1\":\"project\",\"group2\":\"execution\",\"reportField\":[\"stories\",\"undoneStory\",\"tasks\",\"undoneTask\",\"left\",\"consumed\"],\"reportType\":[\"sum\",\"sum\",\"sum\",\"sum\",\"sum\",\"sum\"],\"sumAppend\":[\"stories\",\"undoneStory\",\"tasks\",\"undoneTask\",\"left\",\"consumed\"],\"percent\":{\"5\":\"1\"},\"contrast\":{\"5\":\"totalReal\"},\"showAlone\":{\"5\":\"1\"}}', 2, '{\"zh-cn\":\"\\u9879\\u76ee\\u7684\\u9700\\u6c42\\u6570\\uff0c\\u4efb\\u52a1\\u6570\\uff0c\\u5df2\\u6d88\\u8017\\u5de5\\u65f6\\uff0c\\u5269\\u4f59\\u5de5\\u65f6\\uff0c\\u5269\\u4f59\\u9700\\u6c42\\u6570\\uff0c\\u5269\\u4f59\\u4efb\\u52a1\\u6570\\uff0c\\u8fdb\\u5ea6\\u3002\",\"zh-tw\":\"\\u9805\\u76ee\\u7684\\u9700\\u6c42\\u6578\\uff0c\\u4efb\\u52d9\\u6578\\uff0c\\u5df2\\u6d88\\u8017\\u5de5\\u6642\\uff0c\\u5269\\u9918\\u5de5\\u6642\\uff0c\\u5269\\u9918\\u9700\\u6c42\\u6578\\uff0c\\u5269\\u9918\\u4efb\\u52d9\\u6578\\uff0c\\u9032\\u5ea6\\u3002\",\"en\":\"Project requirements, tasks, consumed hours, remaining hours, remaining demands, remaining tasks, schedule.\"}', 'admin', '2015-07-23 14:03:06'),
('projectbug-type', '{\"zh-cn\":\"\\u9879\\u76eeBug\\u7c7b\\u578b\\u7edf\\u8ba1\\u8868\",\"zh-tw\":\"\\u9805\\u76eeBug\\u985e\\u578b\\u7d71\\u8a08\\u8868\",\"en\":\"Project Bug Type\"}', ',project,test', 'select t1.id,t3.name as project,t3.id,t1.name as execution,t2.id as bugID,t2.type from TABLE_EXECUTION as t1 \r\nleft join TABLE_BUG as t2 on t1.id=t2.execution\r\nleft join TABLE_PROJECT as t3 on t3.id=t1.project\r\nwhere t1.deleted=\'0\' and t2.deleted=\'0\' and if($project=\'\',1,t3.id=$project) and if($execution=\'\',1,t1.id=$execution)', '{\"varName\":[\"project\",\"execution\"],\"showName\":[\"\\u9879\\u76ee\\u5217\\u8868\",\"\\u6267\\u884c\\u5217\\u8868\"],\"requestType\":[\"select\",\"select\"],\"selectList\":[\"project\",\"execution\"],\"default\":[\"\",\"\"]}', '{\"stories\":{\"zh-cn\":\"\\u9700\\u6c42\\u6570\",\"zh-tw\":\"\\u9700\\u6c42\\u6570\",\"en\":\"Stories\"},\"tasks\":{\"zh-cn\":\"\\u4efb\\u52a1\\u6570\",\"zh-tw\":\"\\u4efb\\u52a1\\u6570\",\"en\":\"Tasks\"},\"undoneStory\":{\"zh-cn\":\"\\u5269\\u4f59\\u9700\\u6c42\\u6570\",\"zh-tw\":\"\\u5269\\u4f59\\u9700\\u6c42\\u6570\",\"en\":\"Undone Story\"},\"undoneTask\":{\"zh-cn\":\"\\u5269\\u4f59\\u4efb\\u52a1\\u6570\",\"zh-tw\":\"\\u5269\\u4f59\\u4efb\\u52a1\\u6570\",\"en\":\"Undone Task\"},\"consumed\":{\"zh-cn\":\"\\u5df2\\u6d88\\u8017\\u5de5\\u65f6\",\"zh-tw\":\"\\u5df2\\u6d88\\u8017\\u5de5\\u65f6\",\"en\":\"Cost(h)\"},\"left\":{\"zh-cn\":\"\\u5269\\u4f59\\u5de5\\u65f6\",\"zh-tw\":\"\\u5269\\u4f59\\u5de5\\u65f6\",\"en\":\"Left(h)\"},\"consumedPercent\":{\"zh-cn\":\"\\u8fdb\\u5ea6\",\"zh-tw\":\"\\u8fdb\\u5ea6\",\"en\":\"Process\"},\"execution\":{\"zh-cn\":\"\\u6267\\u884c\\u540d\\u79f0\"}}', '{\"group1\":\"project\",\"group2\":\"execution\",\"reportField\":[\"type\"],\"reportType\":[\"count\"],\"sumAppend\":[\"\"]}', 2, '{\"zh-cn\":\"\\u6309\\u7167\\u9879\\u76ee\\u7edf\\u8ba1Bug\\u7684\\u7c7b\\u578b\\u5206\\u5e03\\u60c5\\u51b5\\u3002\",\"zh-tw\":\"\\u6309\\u7167\\u9805\\u76ee\\u7d71\\u8a08Bug\\u7684\\u985e\\u578b\\u5206\\u5e03\\u60c5\\u6cc1\\u3002\",\"en\":\"Type distribution of Bug statistics by project.\"}', 'admin', '2015-08-04 13:54:22');
+5
View File
@@ -1,3 +1,8 @@
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
Authorization
+23 -23
View File
@@ -30,8 +30,8 @@ if($config->edition != 'open')
/* Menu order. */
$lang->mainNav->menuOrder = array();
$lang->mainNav->menuOrder[5] = 'my';
$lang->mainNav->menuOrder[20] = 'project';
$lang->mainNav->menuOrder[30] = 'kanban';
$lang->mainNav->menuOrder[10] = 'project';
$lang->mainNav->menuOrder[15] = 'kanban';
$lang->mainNav->menuOrder[35] = 'doc';
$lang->mainNav->menuOrder[45] = 'system';
$lang->mainNav->menuOrder[65] = 'admin';
@@ -75,24 +75,24 @@ $lang->my->dividerMenu = ',calendar,';
$lang->project->target = 'Story';
/* Scrum menu. */
$lang->kanban->menu = new stdclass();
$lang->kanban->menu->index = array('link' => "{$lang->dashboard}|project|index|project=%s");
$lang->kanban->menu->execution = array('link' => "$lang->executionKanban|project|execution|status=all&projectID=%s", 'subModule' => 'execution,task');
$lang->kanban->menu->story = array('link' => "{$lang->project->target}|projectstory|story|projectID=%s", 'subModule' => 'projectstory,tree,story', 'alias' => 'story,track');
$lang->kanban->menu->doc = array('link' => "{$lang->doc->common}|doc|tableContents|type=project&objectID=%s", 'subModule' => 'doc');
$lang->kanban->menu->dynamic = array('link' => "$lang->dynamic|project|dynamic|project=%s");
$lang->kanban->menu->settings = array('link' => "$lang->settings|project|view|project=%s", 'subModule' => 'stakeholder', 'alias' => 'edit,manageproducts,group,managemembers,manageview,managepriv,whitelist,addwhitelist,team');
$lang->kanbanProject->menu = new stdclass();
$lang->kanbanProject->menu->index = array('link' => "{$lang->dashboard}|project|index|project=%s");
$lang->kanbanProject->menu->execution = array('link' => "$lang->executionKanban|project|execution|status=all&projectID=%s", 'subModule' => 'execution,task');
$lang->kanbanProject->menu->story = array('link' => "{$lang->project->target}|projectstory|story|projectID=%s", 'subModule' => 'projectstory,tree,story', 'alias' => 'story,track');
$lang->kanbanProject->menu->doc = array('link' => "{$lang->doc->common}|doc|tableContents|type=project&objectID=%s", 'subModule' => 'doc');
$lang->kanbanProject->menu->dynamic = array('link' => "$lang->dynamic|project|dynamic|project=%s");
$lang->kanbanProject->menu->settings = array('link' => "$lang->settings|project|view|project=%s", 'subModule' => 'stakeholder', 'alias' => 'edit,manageproducts,group,managemembers,manageview,managepriv,whitelist,addwhitelist,team');
$lang->kanban->dividerMenu = ',execution,settings,';
$lang->kanbanProject->dividerMenu = ',execution,settings,';
/* Scrum menu order. */
$lang->kanban->menuOrder = array();
$lang->kanban->menuOrder[5] = 'index';
$lang->kanban->menuOrder[10] = 'execution';
$lang->kanban->menuOrder[15] = 'story';
$lang->kanban->menuOrder[20] = 'doc';
$lang->kanban->menuOrder[25] = 'dynamic';
$lang->kanban->menuOrder[30] = 'settings';
$lang->kanbanProject->menuOrder = array();
$lang->kanbanProject->menuOrder[5] = 'index';
$lang->kanbanProject->menuOrder[10] = 'execution';
$lang->kanbanProject->menuOrder[15] = 'story';
$lang->kanbanProject->menuOrder[20] = 'doc';
$lang->kanbanProject->menuOrder[25] = 'dynamic';
$lang->kanbanProject->menuOrder[30] = 'settings';
$lang->execution->menu = new stdclass();
$lang->execution->menu->kanban = array('link' => "Kanban view|execution|kanban|executionID=%s");
@@ -102,12 +102,12 @@ if($config->edition != 'open') $lang->execution->menu->gantt = array('link' =
$lang->execution->menu->tree = array('link' => "Tree|execution|tree|executionID=%s");
$lang->execution->menu->grouptask = array('link' => "Group view|execution|grouptask|executionID=%s");
$lang->kanban->menu->doc['subMenu'] = new stdclass();
$lang->kanbanProject->menu->doc['subMenu'] = new stdclass();
$lang->kanban->menu->settings['subMenu'] = new stdclass();
$lang->kanban->menu->settings['subMenu']->view = array('link' => "$lang->overview|project|view|project=%s", 'alias' => 'edit');
$lang->kanban->menu->settings['subMenu']->members = array('link' => "{$lang->team->common}|project|team|project=%s", 'alias' => 'managemembers,team');
$lang->kanban->menu->settings['subMenu']->whitelist = array('link' => "{$lang->whitelist}|project|whitelist|project=%s", 'subModule' => 'personnel');
$lang->kanbanProject->menu->settings['subMenu'] = new stdclass();
$lang->kanbanProject->menu->settings['subMenu']->view = array('link' => "$lang->overview|project|view|project=%s", 'alias' => 'edit');
$lang->kanbanProject->menu->settings['subMenu']->members = array('link' => "{$lang->team->common}|project|team|project=%s", 'alias' => 'managemembers,team');
$lang->kanbanProject->menu->settings['subMenu']->whitelist = array('link' => "{$lang->whitelist}|project|whitelist|project=%s", 'subModule' => 'personnel');
unset($lang->doc->menu->product);
unset($lang->doc->menu->api);
@@ -222,6 +222,7 @@ $lang->searchObjects['task'] = 'Task';
$lang->searchObjects['doc'] = 'Doc';
$lang->searchObjects['project'] = 'Project';
$lang->searchObjects['execution'] = 'Kanban';
$lang->searchObjects['user'] = 'User';
if($config->edition != 'open') $lang->searchObjects['feedback'] = 'Feedback';
if($config->visions == ',lite,') unset($lang->searchObjects['feedback']);
@@ -239,7 +240,6 @@ unset($lang->searchObjects['testtask']);
unset($lang->searchObjects['caselib']);
unset($lang->searchObjects['testreport']);
unset($lang->searchObjects['program']);
unset($lang->searchObjects['user']);
/* biz search. */
unset($lang->searchObjects['service']);
+20 -20
View File
@@ -75,24 +75,24 @@ $lang->my->dividerMenu = ',calendar,';
$lang->project->target = '目标';
/* Scrum menu. */
$lang->kanban->menu = new stdclass();
$lang->kanban->menu->index = array('link' => "{$lang->dashboard}|project|index|project=%s");
$lang->kanban->menu->execution = array('link' => "$lang->executionKanban|project|execution|status=all&projectID=%s", 'subModule' => 'execution,task');
$lang->kanban->menu->story = array('link' => "{$lang->project->target}|projectstory|story|projectID=%s", 'subModule' => 'projectstory,tree,story', 'alias' => 'story,track');
$lang->kanban->menu->doc = array('link' => "{$lang->doc->common}|doc|tableContents|type=project&objectID=%s", 'subModule' => 'doc');
$lang->kanban->menu->dynamic = array('link' => "$lang->dynamic|project|dynamic|project=%s");
$lang->kanban->menu->settings = array('link' => "$lang->settings|project|view|project=%s", 'subModule' => 'stakeholder', 'alias' => 'edit,manageproducts,group,managemembers,manageview,managepriv,whitelist,addwhitelist,team');
$lang->kanbanProject->menu = new stdclass();
$lang->kanbanProject->menu->index = array('link' => "{$lang->dashboard}|project|index|project=%s");
$lang->kanbanProject->menu->execution = array('link' => "$lang->executionKanban|project|execution|status=all&projectID=%s", 'subModule' => 'execution,task');
$lang->kanbanProject->menu->story = array('link' => "{$lang->project->target}|projectstory|story|projectID=%s", 'subModule' => 'projectstory,tree,story', 'alias' => 'story,track');
$lang->kanbanProject->menu->doc = array('link' => "{$lang->doc->common}|doc|tableContents|type=project&objectID=%s", 'subModule' => 'doc');
$lang->kanbanProject->menu->dynamic = array('link' => "$lang->dynamic|project|dynamic|project=%s");
$lang->kanbanProject->menu->settings = array('link' => "$lang->settings|project|view|project=%s", 'subModule' => 'stakeholder', 'alias' => 'edit,manageproducts,group,managemembers,manageview,managepriv,whitelist,addwhitelist,team');
$lang->kanban->dividerMenu = ',execution,settings,';
$lang->kanbanProject->dividerMenu = ',execution,settings,';
/* Scrum menu order. */
$lang->kanban->menuOrder = array();
$lang->kanban->menuOrder[5] = 'index';
$lang->kanban->menuOrder[10] = 'execution';
$lang->kanban->menuOrder[15] = 'story';
$lang->kanban->menuOrder[20] = 'doc';
$lang->kanban->menuOrder[25] = 'dynamic';
$lang->kanban->menuOrder[30] = 'settings';
$lang->kanbanProject->menuOrder = array();
$lang->kanbanProject->menuOrder[5] = 'index';
$lang->kanbanProject->menuOrder[10] = 'execution';
$lang->kanbanProject->menuOrder[15] = 'story';
$lang->kanbanProject->menuOrder[20] = 'doc';
$lang->kanbanProject->menuOrder[25] = 'dynamic';
$lang->kanbanProject->menuOrder[30] = 'settings';
$lang->execution->menu = new stdclass();
$lang->execution->menu->kanban = array('link' => "看板视图|execution|kanban|executionID=%s");
@@ -102,12 +102,12 @@ if($config->edition != 'open') $lang->execution->menu->gantt = array('link' =
$lang->execution->menu->tree = array('link' => "树状图|execution|tree|executionID=%s");
$lang->execution->menu->grouptask = array('link' => "分组视图|execution|grouptask|executionID=%s");
$lang->kanban->menu->doc['subMenu'] = new stdclass();
$lang->kanbanProject->menu->doc['subMenu'] = new stdclass();
$lang->kanban->menu->settings['subMenu'] = new stdclass();
$lang->kanban->menu->settings['subMenu']->view = array('link' => "$lang->overview|project|view|project=%s", 'alias' => 'edit');
$lang->kanban->menu->settings['subMenu']->members = array('link' => "{$lang->team->common}|project|team|project=%s", 'alias' => 'managemembers,team');
$lang->kanban->menu->settings['subMenu']->whitelist = array('link' => "{$lang->whitelist}|project|whitelist|project=%s", 'subModule' => 'personnel');
$lang->kanbanProject->menu->settings['subMenu'] = new stdclass();
$lang->kanbanProject->menu->settings['subMenu']->view = array('link' => "$lang->overview|project|view|project=%s", 'alias' => 'edit');
$lang->kanbanProject->menu->settings['subMenu']->members = array('link' => "{$lang->team->common}|project|team|project=%s", 'alias' => 'managemembers,team');
$lang->kanbanProject->menu->settings['subMenu']->whitelist = array('link' => "{$lang->whitelist}|project|whitelist|project=%s", 'subModule' => 'personnel');
unset($lang->doc->menu->product);
unset($lang->doc->menu->api);
@@ -10,7 +10,7 @@ class myExecution extends execution
$currentMethod = $this->app->methodName;
$execution = $this->execution->getById($executionID);
$this->loadModel('project')->setMenu($execution->project);
$this->lang->kanban->menu->execution['subMenu'] = new stdClass();
$this->lang->kanbanProject->menu->execution['subMenu'] = new stdClass();
$this->session->set('kanbanview', $currentMethod);
setcookie('kanbanview', $currentMethod, $this->config->cookieLife, $this->config->webRoot, '', false, true);
+3 -3
View File
@@ -7,8 +7,8 @@ public function setMenu($executionID, $buildID = 0, $extra = '')
if(isset($this->lang->execution->menu->kanban))
{
$this->loadModel('project')->setMenu($execution->project);
$this->lang->kanban->menu->execution['subMenu'] = new stdClass();
if($this->app->rawModule == 'tree') unset($this->lang->kanban->menu->execution['subMenu']);
$this->lang->kanbanProject->menu->execution['subMenu'] = new stdClass();
if($this->app->rawModule == 'tree') unset($this->lang->kanbanProject->menu->execution['subMenu']);
}
$kanbanList = $this->getList($execution->project, 'kanban', 'all');
@@ -140,7 +140,7 @@ public function getTRActions($currentMethod)
unset($subMenu->$key);
}
}
$TRActions = '';
$TRActions .= "<div class='btn-group dropdown'>";
$TRActions .= html::a("javascript:;", "<i class='icon icon-" . $this->lang->execution->icons[$currentMethod]."'> </i>" . $subMenu->{$currentMethod}['name'] . " <span class='caret'></span>", '', "class='btn btn-link' data-toggle='dropdown'");
-10
View File
@@ -1,10 +0,0 @@
<?php
// Use this file if you cannot use class autoloading. It will include all the
// files needed for the Markdown parser.
//
// Take a look at the PSR-0-compatible class autoloading implementation
// in the Readme.php file if you want a simple autoloader setup.
require_once dirname(__FILE__) . '/MarkdownInterface.php';
require_once dirname(__FILE__) . '/Markdown.php';
File diff suppressed because it is too large Load Diff
-11
View File
@@ -1,11 +0,0 @@
<?php
// Use this file if you cannot use class autoloading. It will include all the
// files needed for the MarkdownExtra parser.
//
// Take a look at the PSR-0-compatible class autoloading implementation
// in the Readme.php file if you want a simple autoloader setup.
require_once dirname(__FILE__) . '/MarkdownInterface.php';
require_once dirname(__FILE__) . '/Markdown.php';
require_once dirname(__FILE__) . '/MarkdownExtra.php';
File diff suppressed because it is too large Load Diff
@@ -1,9 +0,0 @@
<?php
// Use this file if you cannot use class autoloading. It will include all the
// files needed for the MarkdownInterface interface.
//
// Take a look at the PSR-0-compatible class autoloading implementation
// in the Readme.php file if you want a simple autoloader setup.
require_once dirname(__FILE__) . '/MarkdownInterface.php';
-38
View File
@@ -1,38 +0,0 @@
<?php
/**
* Markdown - A text-to-HTML conversion tool for web writers
*
* @package php-markdown
* @author Michel Fortin <michel.fortin@michelf.com>
* @copyright 2004-2021 Michel Fortin <https://michelf.com/projects/php-markdown/>
* @copyright (Original Markdown) 2004-2006 John Gruber <https://daringfireball.net/projects/markdown/>
*/
namespace Michelf;
/**
* Markdown Parser Interface
*/
interface MarkdownInterface {
/**
* Initialize the parser and return the result of its transform method.
* This will work fine for derived classes too.
*
* @api
*
* @param string $text
* @return string
*/
public static function defaultTransform($text);
/**
* Main function. Performs some preprocessing on the input text
* and pass it through the document gamut.
*
* @api
*
* @param string $text
* @return string
*/
public function transform($text);
}
-27
View File
@@ -1,27 +0,0 @@
<?php
require_once 'Michelf/MarkdownExtra.inc.php';
use Michelf\Markdown;
use Michelf\MarkdownExtra;
class michelf
{
/**
* Convert markdown to html parser.
*
* @param string $mdCodes
* @static
* @access public
* @return void
*/
public static function parse($mdCodes = '')
{
if(strlen($mdCodes) == 0) return '';
$html = Markdown::defaultTransform($mdCodes);
$parser = new MarkdownExtra;
$parser->fn_id_prefix = "post22-";
$html = $parser->transform($mdCodes);
return "<div class='markdown-print'>$html</div>";
}
}
+20
View File
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013-2018 Emanuil Rusev, erusev.com
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View File
File diff suppressed because it is too large Load Diff
+686
View File
@@ -0,0 +1,686 @@
<?php
#
#
# Parsedown Extra
# https://github.com/erusev/parsedown-extra
#
# (c) Emanuil Rusev
# http://erusev.com
#
# For the full license information, view the LICENSE file that was distributed
# with this source code.
#
#
class parsedownextra extends parsedown
{
# ~
const version = '0.8.0';
# ~
function __construct()
{
if (version_compare(parent::version, '1.7.1') < 0)
{
throw new Exception('ParsedownExtra requires a later version of Parsedown');
}
$this->BlockTypes[':'] []= 'DefinitionList';
$this->BlockTypes['*'] []= 'Abbreviation';
# identify footnote definitions before reference definitions
array_unshift($this->BlockTypes['['], 'Footnote');
# identify footnote markers before before links
array_unshift($this->InlineTypes['['], 'FootnoteMarker');
}
#
# ~
function text($text)
{
$Elements = $this->textElements($text);
# convert to markup
$markup = $this->elements($Elements);
# trim line breaks
$markup = trim($markup, "\n");
# merge consecutive dl elements
$markup = preg_replace('/<\/dl>\s+<dl>\s+/', '', $markup);
# add footnotes
if (isset($this->DefinitionData['Footnote']))
{
$Element = $this->buildFootnoteElement();
$markup .= "\n" . $this->element($Element);
}
return $markup;
}
#
# Blocks
#
#
# Abbreviation
protected function blockAbbreviation($Line)
{
if (preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches))
{
$this->DefinitionData['Abbreviation'][$matches[1]] = $matches[2];
$Block = array(
'hidden' => true,
);
return $Block;
}
}
#
# Footnote
protected function blockFootnote($Line)
{
if (preg_match('/^\[\^(.+?)\]:[ ]?(.*)$/', $Line['text'], $matches))
{
$Block = array(
'label' => $matches[1],
'text' => $matches[2],
'hidden' => true,
);
return $Block;
}
}
protected function blockFootnoteContinue($Line, $Block)
{
if ($Line['text'][0] === '[' and preg_match('/^\[\^(.+?)\]:/', $Line['text']))
{
return;
}
if (isset($Block['interrupted']))
{
if ($Line['indent'] >= 4)
{
$Block['text'] .= "\n\n" . $Line['text'];
return $Block;
}
}
else
{
$Block['text'] .= "\n" . $Line['text'];
return $Block;
}
}
protected function blockFootnoteComplete($Block)
{
$this->DefinitionData['Footnote'][$Block['label']] = array(
'text' => $Block['text'],
'count' => null,
'number' => null,
);
return $Block;
}
#
# Definition List
protected function blockDefinitionList($Line, $Block)
{
if ( ! isset($Block) or $Block['type'] !== 'Paragraph')
{
return;
}
$Element = array(
'name' => 'dl',
'elements' => array(),
);
$terms = explode("\n", $Block['element']['handler']['argument']);
foreach ($terms as $term)
{
$Element['elements'] []= array(
'name' => 'dt',
'handler' => array(
'function' => 'lineElements',
'argument' => $term,
'destination' => 'elements'
),
);
}
$Block['element'] = $Element;
$Block = $this->addDdElement($Line, $Block);
return $Block;
}
protected function blockDefinitionListContinue($Line, array $Block)
{
if ($Line['text'][0] === ':')
{
$Block = $this->addDdElement($Line, $Block);
return $Block;
}
else
{
if (isset($Block['interrupted']) and $Line['indent'] === 0)
{
return;
}
if (isset($Block['interrupted']))
{
$Block['dd']['handler']['function'] = 'textElements';
$Block['dd']['handler']['argument'] .= "\n\n";
$Block['dd']['handler']['destination'] = 'elements';
unset($Block['interrupted']);
}
$text = substr($Line['body'], min($Line['indent'], 4));
$Block['dd']['handler']['argument'] .= "\n" . $text;
return $Block;
}
}
#
# Header
protected function blockHeader($Line)
{
$Block = parent::blockHeader($Line);
if ($Block !== null && preg_match('/[ #]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE))
{
$attributeString = $matches[1][0];
$Block['element']['attributes'] = $this->parseAttributeData($attributeString);
$Block['element']['handler']['argument'] = substr($Block['element']['handler']['argument'], 0, $matches[0][1]);
}
return $Block;
}
#
# Markup
protected function blockMarkup($Line)
{
if ($this->markupEscaped or $this->safeMode)
{
return;
}
if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
{
$element = strtolower($matches[1]);
if (in_array($element, $this->textLevelElements))
{
return;
}
$Block = array(
'name' => $matches[1],
'depth' => 0,
'element' => array(
'rawHtml' => $Line['text'],
'autobreak' => true,
),
);
$length = strlen($matches[0]);
$remainder = substr($Line['text'], $length);
if (trim($remainder) === '')
{
if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
{
$Block['closed'] = true;
$Block['void'] = true;
}
}
else
{
if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
{
return;
}
if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
{
$Block['closed'] = true;
}
}
return $Block;
}
}
protected function blockMarkupContinue($Line, array $Block)
{
if (isset($Block['closed']))
{
return;
}
if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
{
$Block['depth'] ++;
}
if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
{
if ($Block['depth'] > 0)
{
$Block['depth'] --;
}
else
{
$Block['closed'] = true;
}
}
if (isset($Block['interrupted']))
{
$Block['element']['rawHtml'] .= "\n";
unset($Block['interrupted']);
}
$Block['element']['rawHtml'] .= "\n".$Line['body'];
return $Block;
}
protected function blockMarkupComplete($Block)
{
if ( ! isset($Block['void']))
{
$Block['element']['rawHtml'] = $this->processTag($Block['element']['rawHtml']);
}
return $Block;
}
#
# Setext
protected function blockSetextHeader($Line, array $Block = null)
{
$Block = parent::blockSetextHeader($Line, $Block);
if ($Block !== null && preg_match('/[ ]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE))
{
$attributeString = $matches[1][0];
$Block['element']['attributes'] = $this->parseAttributeData($attributeString);
$Block['element']['handler']['argument'] = substr($Block['element']['handler']['argument'], 0, $matches[0][1]);
}
return $Block;
}
#
# Inline Elements
#
#
# Footnote Marker
protected function inlineFootnoteMarker($Excerpt)
{
if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches))
{
$name = $matches[1];
if ( ! isset($this->DefinitionData['Footnote'][$name]))
{
return;
}
$this->DefinitionData['Footnote'][$name]['count'] ++;
if ( ! isset($this->DefinitionData['Footnote'][$name]['number']))
{
$this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » &
}
$Element = array(
'name' => 'sup',
'attributes' => array('id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name),
'element' => array(
'name' => 'a',
'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'),
'text' => $this->DefinitionData['Footnote'][$name]['number'],
),
);
return array(
'extent' => strlen($matches[0]),
'element' => $Element,
);
}
}
private $footnoteCount = 0;
#
# Link
protected function inlineLink($Excerpt)
{
$Link = parent::inlineLink($Excerpt);
$remainder = $Link !== null ? substr($Excerpt['text'], $Link['extent']) : '';
if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches))
{
$Link['element']['attributes'] += $this->parseAttributeData($matches[1]);
$Link['extent'] += strlen($matches[0]);
}
return $Link;
}
#
# ~
#
private $currentAbreviation;
private $currentMeaning;
protected function insertAbreviation(array $Element)
{
if (isset($Element['text']))
{
$Element['elements'] = self::pregReplaceElements(
'/\b'.preg_quote($this->currentAbreviation, '/').'\b/',
array(
array(
'name' => 'abbr',
'attributes' => array(
'title' => $this->currentMeaning,
),
'text' => $this->currentAbreviation,
)
),
$Element['text']
);
unset($Element['text']);
}
return $Element;
}
protected function inlineText($text)
{
$Inline = parent::inlineText($text);
if (isset($this->DefinitionData['Abbreviation']))
{
foreach ($this->DefinitionData['Abbreviation'] as $abbreviation => $meaning)
{
$this->currentAbreviation = $abbreviation;
$this->currentMeaning = $meaning;
$Inline['element'] = $this->elementApplyRecursiveDepthFirst(
array($this, 'insertAbreviation'),
$Inline['element']
);
}
}
return $Inline;
}
#
# Util Methods
#
protected function addDdElement(array $Line, array $Block)
{
$text = substr($Line['text'], 1);
$text = trim($text);
unset($Block['dd']);
$Block['dd'] = array(
'name' => 'dd',
'handler' => array(
'function' => 'lineElements',
'argument' => $text,
'destination' => 'elements'
),
);
if (isset($Block['interrupted']))
{
$Block['dd']['handler']['function'] = 'textElements';
unset($Block['interrupted']);
}
$Block['element']['elements'] []= & $Block['dd'];
return $Block;
}
protected function buildFootnoteElement()
{
$Element = array(
'name' => 'div',
'attributes' => array('class' => 'footnotes'),
'elements' => array(
array('name' => 'hr'),
array(
'name' => 'ol',
'elements' => array(),
),
),
);
uasort($this->DefinitionData['Footnote'], 'self::sortFootnotes');
foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData)
{
if ( ! isset($DefinitionData['number']))
{
continue;
}
$text = $DefinitionData['text'];
$textElements = parent::textElements($text);
$numbers = range(1, $DefinitionData['count']);
$backLinkElements = array();
foreach ($numbers as $number)
{
$backLinkElements[] = array('text' => ' ');
$backLinkElements[] = array(
'name' => 'a',
'attributes' => array(
'href' => "#fnref$number:$definitionId",
'rev' => 'footnote',
'class' => 'footnote-backref',
),
'rawHtml' => '&#8617;',
'allowRawHtmlInSafeMode' => true,
'autobreak' => false,
);
}
unset($backLinkElements[0]);
$n = count($textElements) -1;
if ($textElements[$n]['name'] === 'p')
{
$backLinkElements = array_merge(
array(
array(
'rawHtml' => '&#160;',
'allowRawHtmlInSafeMode' => true,
),
),
$backLinkElements
);
unset($textElements[$n]['name']);
$textElements[$n] = array(
'name' => 'p',
'elements' => array_merge(
array($textElements[$n]),
$backLinkElements
),
);
}
else
{
$textElements[] = array(
'name' => 'p',
'elements' => $backLinkElements
);
}
$Element['elements'][1]['elements'] []= array(
'name' => 'li',
'attributes' => array('id' => 'fn:'.$definitionId),
'elements' => array_merge(
$textElements
),
);
}
return $Element;
}
# ~
protected function parseAttributeData($attributeString)
{
$Data = array();
$attributes = preg_split('/[ ]+/', $attributeString, - 1, PREG_SPLIT_NO_EMPTY);
foreach ($attributes as $attribute)
{
if ($attribute[0] === '#')
{
$Data['id'] = substr($attribute, 1);
}
else # "."
{
$classes []= substr($attribute, 1);
}
}
if (isset($classes))
{
$Data['class'] = implode(' ', $classes);
}
return $Data;
}
# ~
protected function processTag($elementMarkup) # recursive
{
# http://stackoverflow.com/q/1148928/200145
libxml_use_internal_errors(true);
$DOMDocument = new DOMDocument;
# http://stackoverflow.com/q/11309194/200145
$elementMarkup = mb_convert_encoding($elementMarkup, 'HTML-ENTITIES', 'UTF-8');
# http://stackoverflow.com/q/4879946/200145
$DOMDocument->loadHTML($elementMarkup);
$DOMDocument->removeChild($DOMDocument->doctype);
$DOMDocument->replaceChild($DOMDocument->firstChild->firstChild->firstChild, $DOMDocument->firstChild);
$elementText = '';
if ($DOMDocument->documentElement->getAttribute('markdown') === '1')
{
foreach ($DOMDocument->documentElement->childNodes as $Node)
{
$elementText .= $DOMDocument->saveHTML($Node);
}
$DOMDocument->documentElement->removeAttribute('markdown');
$elementText = "\n".$this->text($elementText)."\n";
}
else
{
foreach ($DOMDocument->documentElement->childNodes as $Node)
{
$nodeMarkup = $DOMDocument->saveHTML($Node);
if ($Node instanceof DOMElement and ! in_array($Node->nodeName, $this->textLevelElements))
{
$elementText .= $this->processTag($nodeMarkup);
}
else
{
$elementText .= $nodeMarkup;
}
}
}
# because we don't want for markup to get encoded
$DOMDocument->documentElement->nodeValue = 'placeholder\x1A';
$markup = $DOMDocument->saveHTML($DOMDocument->documentElement);
$markup = str_replace('placeholder\x1A', $elementText, $markup);
return $markup;
}
# ~
protected function sortFootnotes($A, $B) # callback
{
return $A['number'] - $B['number'];
}
#
# Fields
#
protected $regexAttribute = '(?:[#.][-\w]+[ ]*)';
}
@@ -0,0 +1,590 @@
<?php
#
#
# Parsedown Extra Plugin
# https://github.com/taufik-nurrohman/parsedown-extra-plugin
#
# (c) Emanuil Rusev
# http://erusev.com
#
# (c) Taufik Nurrohman
# https://mecha-cms.com
#
# For the full license information, view the LICENSE file that was distributed
# with this source code.
#
#
require 'parsedown.php';
require 'parsedownextra.php';
class parsedownextraplugin extends parsedownextra {
const version = '1.3.6';
public $abbreviationData = array();
public $blockCodeAttributes = array();
public $blockCodeClassFormat = 'language-%s';
public $blockCodeHtml = null;
public $blockQuoteAttributes = array();
public $blockQuoteText = null;
public $codeAttributes = array();
public $codeAttributesOnParent = false;
public $codeHtml = null;
public $figureAttributes = array();
public $figuresEnabled = false;
public $footnoteAttributes = array();
public $footnoteBackLinkAttributes = array();
public $footnoteBackLinkHtml = null;
public $footnoteBackReferenceAttributes = array();
public $footnoteLinkAttributes = array();
public $footnoteLinkHtml = null;
public $footnoteReferenceAttributes = array();
public $headerAttributes = array();
public $headerText = null;
public $imageAttributes = array();
public $imageAttributesOnParent = false;
public $linkAttributes = array();
public $referenceData = array();
public $tableAttributes = array();
public $tableColumnAttributes = array();
public $voidElementSuffix = ' />';
protected $regexAttribute = '(?:[#.][-\w:\\\]+[ ]*|[-\w:\\\]+(?:=(?:["\'][^\n]*?["\']|[^\s]+)?)?[ ]*)';
# Method aliases for every configuration property
public function __call($key, array $arguments = array()) {
$property = lcfirst(substr($key, 3));
if (strpos($key, 'set') === 0 && property_exists($this, $property)) {
$this->{$property} = $arguments[0];
return $this;
}
throw new Exception('Method ' . $key . ' does not exists.');
}
public function __construct() {
if (version_compare(parent::version, '0.8.0-beta-1') < 0) {
throw new Exception('ParsedownExtraPlugin requires a later version of Parsedown');
}
$this->BlockTypes['!'][] = 'Image';
parent::__construct();
}
protected function blockAbbreviation($Line) {
// Allow empty abbreviations
if (preg_match('/^\*\[(.+?)\]:[ ]*$/', $Line['text'], $matches)) {
$this->DefinitionData['Abbreviation'][$matches[1]] = null;
return array('hidden' => true);
}
return parent::blockAbbreviation($Line);
}
protected function blockCodeComplete($Block) {
$this->doSetAttributes($Block['element']['element'], $this->blockCodeAttributes);
$this->doSetContent($Block['element']['element'], $this->blockCodeHtml, true);
// Put code attributes on parent element
if ($this->codeAttributesOnParent) {
if ($this->codeAttributesOnParent === true) {
// $this->codeAttributesOnParent = array_keys($Block['element']['element']['attributes']);
$this->codeAttributesOnParent = array('class', 'id');
}
foreach ((array) $this->codeAttributesOnParent as $Name) {
if (isset($Block['element']['element']['attributes'][$Name])) {
$Block['element']['attributes'][$Name] = $Block['element']['element']['attributes'][$Name];
unset($Block['element']['element']['attributes'][$Name]);
}
}
}
$Block['element']['element']['rawHtml'] = $Block['element']['element']['text'];
$Block['element']['element']['allowRawHtmlInSafeMode'] = true;
unset($Block['element']['element']['text']);
return $Block;
}
protected function blockFencedCode($Line) {
// Re-enable the multiple class name feature
$Line['text'] = strtr(trim($Line['text']), array(
' ' => "\x1A",
'.' => "\x1A."
));
// Enable custom attribute syntax on code block
$Attributes = array();
if (strpos($Line['text'], '{') !== false && substr($Line['text'], -1) === '}') {
$Parts = explode('{', $Line['text'], 2);
$Attributes = $this->parseAttributeData(strtr(substr($Parts[1], 0, -1), "\x1A", ' '));
$Line['text'] = trim($Parts[0]);
}
if (!$Block = parent::blockFencedCode($Line)) {
return;
}
if ($Attributes) {
$Block['element']['element']['attributes'] = $Attributes;
} else if (isset($Block['element']['element']['attributes']['class'])) {
$Classes = explode("\x1A", strtr($Block['element']['element']['attributes']['class'], ' ', "\x1A"));
// `~~~ php` → `<pre><code class="language-php">`
// `~~~ php html` → `<pre><code class="language-php language-html">`
// `~~~ .php` → `<pre><code class="php">`
// `~~~ .php.html` → `<pre><code class="php html">`
// `~~~ .php html` → `<pre><code class="php language-html">`
// `~~~ {.php #foo}` → `<pre><code id="foo" class="php">`
$Results = [];
foreach ($Classes as $Class) {
if ($Class === "" || $Class === strtr($this->blockCodeClassFormat, array('%s' => ""))) {
continue;
}
if ($Class[0] === '.') {
$Results[] = substr($Class, 1);
} else if (preg_match('/^' . strtr(preg_quote($this->blockCodeClassFormat), array('%s' => '\S+')) . '$/', $Class)) {
$Results[] = $Class; // Do nothing!
} else {
$Results[] = sprintf($this->blockCodeClassFormat, $Class);
}
}
if ($Results = array_unique($Results)) {
$Block['element']['element']['attributes']['class'] = implode(' ', $Results);
} else {
unset($Block['element']['element']['attributes']['class']);
}
}
return $Block;
}
protected function blockFencedCodeComplete($Block) {
return $this->blockCodeComplete($Block);
}
protected function blockHeader($Line) {
if (!$Block = parent::blockHeader($Line)) {
return;
}
$Level = strspn($Line['text'], '#');
$this->doSetAttributes($Block['element'], $this->headerAttributes, array($Level));
$this->doSetContent($Block['element'], $this->headerText, false, 'argument', array($Level));
return $Block;
}
protected function blockImage($Line) {
if (!$this->figuresEnabled) {
return;
}
// Match exactly an image syntax in a paragraph (with optional custom attributes, and optional hard break marker)
if (preg_match('/^\!\[[^\n]*?\](\[[^\n]*?\]|\([^\n]*?\))(\s*\{' . $this->regexAttribute . '+?\})?([ ]{2})?$/', $Line['text'])) {
$Block = array(
'description' => "",
'element' => array(
'name' => 'figure',
'attributes' => array(),
'elements' => array(
$this->inlineImage($Line)
)
)
);
$this->doSetAttributes($Block['element'], $this->figureAttributes);
return $Block;
}
return;
}
protected function blockImageComplete($Block) {
if (!empty($Block['description'])) {
$Description = $Block['description'];
$Block['element']['elements'][] = array(
'name' => 'figcaption',
'rawHtml' => $this->{strpos($Description, "\n\n") === false ? 'line' : 'text'}(trim($Description, "\n"))
);
// unset($Block['description']);
}
if ($this->imageAttributesOnParent) {
$Inline = $Block['element']['elements'][0];
if ($this->imageAttributesOnParent === true) {
$this->imageAttributesOnParent = array_keys($Inline['element']['attributes']);
}
foreach ((array) $this->imageAttributesOnParent as $Name) {
if (isset($Inline['element']['attributes'][$Name])) {
// Merge class names
if (
$Name === 'class' &&
isset($Block['element']['attributes'][$Name]) &&
isset($Inline['element']['attributes'][$Name])
) {
$Classes = array_merge(
explode(' ', $Block['element']['attributes'][$Name]),
explode(' ', $Inline['element']['attributes'][$Name])
);
sort($Classes);
$Block['element']['attributes']['class'] = implode(' ', array_unique(array_filter($Classes)));
unset($Block['element']['elements'][0]['element']['attributes'][$Name]);
continue;
}
$Block['element']['attributes'][$Name] = $Inline['element']['attributes'][$Name];
unset($Block['element']['elements'][0]['element']['attributes'][$Name]);
}
}
}
return $Block;
}
protected function blockImageContinue($Line, array $Block) {
if (isset($Block['complete'])) {
return;
}
if (isset($Block['interrupted'])) {
$Block['description'] .= "\n";
unset($Block['interrupted']);
}
if ($Line['indent'] === 0) {
$Block['complete'] = true;
return;
}
if ($Line['indent'] > 0 && $Line['indent'] < 4) {
$Block['description'] .= "\n" . $Line['text'];
return $Block;
}
return;
}
protected function blockQuoteComplete($Block) {
$this->doSetAttributes($Block['element'], $this->blockQuoteAttributes);
$this->doSetContent($Block['element'], $this->blockQuoteText, false, 'arguments');
return $Block;
}
protected function blockSetextHeader($Line, array $Block = null) {
if (!$Block = parent::blockSetextHeader($Line, $Block)) {
return;
}
$Level = $Line['text'][0] === '=' ? 1 : 2;
$this->doSetAttributes($Block['element'], $this->headerAttributes, array($Level));
$this->doSetContent($Block['element'], $this->headerText, false, 'argument', array($Level));
return $Block;
}
protected function blockTableContinue($Line, array $Block) {
if (!$Block = parent::blockTableContinue($Line, $Block)) {
return;
}
$Aligns = $Block['alignments'];
// `<thead>` or `<tbody>`
foreach ($Block['element']['elements'] as $Index0 => &$Element0) {
// `<tr>`
foreach ($Element0['elements'] as $Index1 => &$Element1) {
// `<th>` or `<td>`
foreach ($Element1['elements'] as $Index2 => &$Element2) {
$this->doSetAttributes($Element2, $this->tableColumnAttributes, array($Aligns[$Index2], $Index2, $Index1));
}
}
}
return $Block;
}
protected function blockTableComplete($Block) {
$this->doSetAttributes($Block['element'], $this->tableAttributes);
return $Block;
}
protected function buildFootnoteElement() {
$DefinitionData = $this->DefinitionData['Footnote'];
if (!$Footnotes = parent::buildFootnoteElement()) {
return;
}
$DefinitionKey = array_keys($DefinitionData);
$DefinitionData = array_values($DefinitionData);
$this->doSetAttributes($Footnotes, $this->footnoteAttributes);
foreach ($Footnotes['elements'][1]['elements'] as $Index0 => &$Element0) {
$Name = $DefinitionKey[$Index0];
$Count = $DefinitionData[$Index0]['count'];
$Args = array(is_numeric($Name) ? (float) $Name : $Name, $Count);
$this->doSetAttributes($Element0, $this->footnoteBackReferenceAttributes, $Args);
foreach ($Element0['elements'] as $Index1 => &$Element1) {
if (!isset($Element1['elements'])) {
continue;
}
$Count = 0;
foreach ($Element1['elements'] as $Index2 => &$Element2) {
if (!isset($Element2['name']) || $Element2['name'] !== 'a') {
continue;
}
$Args[1] = ++$Count;
$this->doSetAttributes($Element2, $this->footnoteBackLinkAttributes, $Args);
$this->doSetContent($Element2, $this->footnoteBackLinkHtml, false, 'rawHtml');
}
}
}
return $Footnotes;
}
protected function doGetAttributes($Element) {
if (isset($Element['attributes'])) {
return (array) $Element['attributes'];
}
return array();
}
protected function doGetContent($Element) {
if (isset($Element['text'])) {
return $Element['text'];
}
if (isset($Element['rawHtml'])) {
return $Element['rawHtml'];
}
if (isset($Element['handler']['argument'])) {
return implode("\n", (array) $Element['handler']['argument']);
}
return null;
}
private function doSetLink($Excerpt, $Function) {
if (!$Inline = call_user_func('parent::' . $Function, $Excerpt)) {
return;
}
$this->doSetAttributes($Inline['element'], $this->linkAttributes, array($this->isLocal($Inline['element'], 'href')));
$this->doSetData($this->DefinitionData['Reference'], $this->referenceData);
return $Inline;
}
protected function doSetAttributes(&$Element, $From, $Args = array()) {
$Attributes = $this->doGetAttributes($Element);
$Content = $this->doGetContent($Element);
if (is_callable($From)) {
$Args = array_merge(array($Content, $Attributes, &$Element), $Args);
$Element['attributes'] = array_replace($Attributes, (array) call_user_func_array($From, $Args));
} else {
$Element['attributes'] = array_replace($Attributes, (array) $From);
}
}
protected function doSetContent(&$Element, $From, $Esc = false, $Mode = 'text', $Args = array()) {
$Attributes = $this->doGetAttributes($Element);
$Content = $this->doGetContent($Element);
if ($Esc) {
$Content = parent::escape($Content, true);
}
if (is_callable($From)) {
$Args = array_merge(array($Content, $Attributes, &$Element), $Args);
$Content = call_user_func_array($From, $Args);
} else if (!empty($From)) {
$Content = sprintf($From, $Content);
}
if ($Mode === 'arguments') {
$Element['handler']['argument'] = explode("\n", $Content);
} else if ($Mode === 'argument') {
$Element['handler']['argument'] = $Content;
} else {
$Element[$Mode] = $Content;
}
}
protected function doSetData(&$To, $From) {
$To = array_replace((array) $To, (array) $From);
}
protected function element(array $Element) {
if (!$Any = parent::element($Element)) {
return;
}
if (substr($Any, -3) === ' />') {
if (is_callable($this->voidElementSuffix)) {
$Attributes = $this->doGetAttributes($Element);
$Content = $this->doGetContent($Element);
$Suffix = call_user_func_array($this->voidElementSuffix, [$Content, $Attributes, &$Element]);
} else {
$Suffix = $this->voidElementSuffix;
}
$Any = substr_replace($Any, $Suffix, -3);
}
return $Any;
}
protected function inlineCode($Excerpt) {
if (!$Inline = parent::inlineCode($Excerpt)) {
return;
}
$this->doSetAttributes($Inline['element'], $this->codeAttributes);
$this->doSetContent($Inline['element'], $this->codeHtml, true);
$Inline['element']['rawHtml'] = $Inline['element']['text'];
$Inline['element']['allowRawHtmlInSafeMode'] = true;
unset($Inline['element']['text']);
return $Inline;
}
protected function inlineFootnoteMarker($Excerpt) {
if (!$Inline = parent::inlineFootnoteMarker($Excerpt)) {
return;
}
$Name = null;
if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches)) {
$Name = $matches[1];
}
$Args = array(is_numeric($Name) ? (float) $Name : $Name, $this->DefinitionData['Footnote'][$Name]['count']);
$this->doSetAttributes($Inline['element'], $this->footnoteReferenceAttributes, $Args);
$this->doSetAttributes($Inline['element']['element'], $this->footnoteLinkAttributes, $Args);
$this->doSetContent($Inline['element']['element'], $this->footnoteLinkHtml, false, 'text', $Args);
$Inline['element']['element']['rawHtml'] = $Inline['element']['element']['text'];
$Inline['element']['element']['allowRawHtmlInSafeMode'] = true;
unset($Inline['element']['element']['text']);
return $Inline;
}
protected function inlineImage($Excerpt) {
if (!$Inline = parent::inlineImage($Excerpt)) {
return;
}
$this->doSetAttributes($Inline['element'], $this->imageAttributes, array($this->isLocal($Inline['element'], 'src')));
return $Inline;
}
protected function inlineLink($Excerpt) {
return $this->doSetLink($Excerpt, __FUNCTION__);
}
protected function inlineText($Text) {
$this->doSetData($this->DefinitionData['Abbreviation'], $this->abbreviationData);
return parent::inlineText($Text);
}
protected function inlineUrl($Excerpt) {
return $this->doSetLink($Excerpt, __FUNCTION__);
}
protected function inlineUrlTag($Excerpt) {
return $this->doSetLink($Excerpt, __FUNCTION__);
}
protected function isLocal($Element, $Key) {
$Link = isset($Element['attributes'][$Key]) ? (string) $Element['attributes'][$Key] : null;
if (
// `<a href="">`
$Link === "" ||
// `<a href="../foo/bar">`
// `<a href="/foo/bar">`
// `<a href="?foo=bar">`
// `<a href="&foo=bar">`
// `<a href="#foo">`
strpos('./?&#', $Link[0]) !== false && strpos($Link, '//') !== 0 ||
// `<a href="data:text/html,asdf">`
strpos($Link, 'data:') === 0 ||
// `<a href="javascript:;">`
strpos($Link, 'javascript:') === 0 ||
// `<a href="mailto:as@df">`
strpos($Link, 'mailto:') === 0
) {
return true;
}
if (isset($_SERVER['HTTP_HOST'])) {
$Host = $_SERVER['HTTP_HOST'];
} else if (isset($_SERVER['SERVER_NAME'])) {
$Host = $_SERVER['SERVER_NAME'];
} else {
$Host = "";
}
// `<a href="//example.com">`
if (strpos($Link, '//') === 0 && strpos($Link, '//' . $Host) !== 0) {
return false;
}
if (
// `<a href="https://127.0.0.1">`
strpos($Link, 'https://' . $Host) === 0 ||
// `<a href="http://127.0.0.1">`
strpos($Link, 'http://' . $Host) === 0
) {
return true;
}
// `<a href="foo/bar">`
return strpos($Link, '://') === false;
}
protected function parseAttributeData($attributeString) {
// Allow compact attributes
$attributeString = strtr($attributeString, array(
'#' => ' #',
'.' => ' .'
));
if (strpos($attributeString, '="') !== false || strpos($attributeString, "='") !== false) {
$attributeString = preg_replace_callback('#([-\w]+=)(["\'])([^\n]*?)\2#', function($matches) {
$value = strtr($matches[3], array(
' #' => '#',
' .' => '.',
' ' => "\x1A"
));
return $matches[1] . $matches[2] . $value . $matches[2];
}, $attributeString);
}
$Attributes = array();
foreach (explode(' ', $attributeString) as $v) {
if (!$v) {
continue;
}
// `{#foo}`
if ($v[0] === '#' && isset($v[1])) {
$Attributes['id'] = substr($v, 1);
// `{.foo}`
} else if ($v[0] === '.' && isset($v[1])) {
$Attributes['class'][] = substr($v, 1);
// ~
} else if (strpos($v, '=') !== false) {
$vv = explode('=', $v, 2);
// `{foo=}`
if ($vv[1] === "") {
if ($vv[0] === 'class') {
continue;
}
$Attributes[$vv[0]] = "";
// `{foo="bar baz"}`
// `{foo='bar baz'}`
} else if ($vv[1][0] === '"' && substr($vv[1], -1) === '"' || $vv[1][0] === "'" && substr($vv[1], -1) === "'") {
$values = stripslashes(strtr(substr(substr($vv[1], 1), 0, -1), "\x1A", ' '));
if ($vv[0] === 'class' && isset($Attributes[$vv[0]])) {
$values = explode(' ', $values);
$Attributes[$vv[0]] = array_merge($Attributes[$vv[0]], $values);
} else {
$Attributes[$vv[0]] = $values;
}
// `{foo=bar}`
} else {
if ($vv[0] === 'class' && isset($Attributes[$vv[0]])) {
$Attributes[$vv[0]] = array_merge($Attributes[$vv[0]], [$vv[1]]);
} else {
$Attributes[$vv[0]] = $vv[1];
}
}
// `{foo}`
} else {
if ($v === 'class' && isset($Attributes[$v])) {
continue;
}
$Attributes[$v] = $v;
}
}
if (isset($Attributes['class'])) {
$Attributes['class'] = implode(' ', array_unique((array) $Attributes['class']));
}
return $Attributes;
}
}
+24
View File
@@ -11,6 +11,30 @@
*/
class action extends control
{
/**
* Create a action or delete all patch actions, this method is used by the Ztools.
*
* @param string $objectType
* @param string $actionType
* @param string $objectName
* @access public
* @return void
*/
public function create($objectType, $actionType, $objectName)
{
$actionID = $this->action->create($objectType, 0, $actionType, '', $objectName);
if($actionID)
{
$this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess));
}
else
{
$this->send(array('result' => 'fail', 'message' => 'error'));
}
}
/**
* Trash.
*
+3
View File
@@ -139,6 +139,7 @@ $lang->action->objectTypes['kanbancard'] = 'Kanban Card';
$lang->action->objectTypes['sonarqube'] = 'SonarQube Server';
$lang->action->objectTypes['sonarqubeproject'] = 'SonarQube Project';
$lang->action->objectTypes['stage'] = 'Stage';
$lang->action->objectTypes['patch'] = 'Patch';
/* Used to describe operation history. */
$lang->action->desc = new stdclass();
@@ -232,6 +233,8 @@ $lang->action->desc->unlinkbug = '$date, 由 <strong>$actor</strong> 从计划
/* Used to display dynamic information. */
$lang->action->label = new stdclass();
$lang->action->label->install = 'install ';
$lang->action->label->revert = 'revert ';
$lang->action->label->created = 'created ';
$lang->action->label->opened = 'opened ';
$lang->action->label->openedbysystem = 'Opened by system ';
+3
View File
@@ -139,6 +139,7 @@ $lang->action->objectTypes['kanbancard'] = '看板卡片';
$lang->action->objectTypes['sonarqube'] = 'SonarQube服务器';
$lang->action->objectTypes['sonarqubeproject'] = 'SonarQube项目';
$lang->action->objectTypes['stage'] = '阶段';
$lang->action->objectTypes['patch'] = '补丁';
/* 用来描述操作历史记录。*/
$lang->action->desc = new stdclass();
@@ -232,6 +233,8 @@ $lang->action->desc->unlinkbug = '$date, 由 <strong>$actor</strong> 从计划
/* 用来显示动态信息。*/
$lang->action->label = new stdclass();
$lang->action->label->install = '安装';
$lang->action->label->revert = '还原';
$lang->action->label->created = '创建';
$lang->action->label->opened = '创建';
$lang->action->label->openedbysystem = '系统创建';
+12
View File
@@ -1555,6 +1555,18 @@ class actionModel extends model
}
}
/**
* Delete action by objectType.
*
* @param string $objectType
* @access public
* @return void
*/
public function deleteByType($objectType)
{
$this->dao->delete()->from(TABLE_ACTION)->where('objectType')->eq($objectType)->exec();
}
/**
* Undelete a record.
*
+1 -1
View File
@@ -106,7 +106,7 @@ class apiModel extends model
->autoCheck()
->batchCheck($this->config->api->create->requiredFields, 'notempty')
->check('title', 'unique', "lib = $data->lib AND module = $data->module")
->check('path', 'unique', "lib = $data->lib AND module = $data->module")
->check('path', 'unique', "lib = $data->lib AND module = $data->module AND method = '$data->method'")
->exec();
if(dao::isError()) return false;
+2 -1
View File
@@ -896,7 +896,8 @@ class block extends control
$count = isset($this->params->count) ? (int)$this->params->count : 15;
/* Get projects. */
$projects = $this->loadModel('project')->getOverviewList('byStatus', $status, 'id_desc', $count);
$excludedModel = $this->config->edition == 'max' ? '' : 'waterfall';
$projects = $this->loadModel('project')->getOverviewList('byStatus', $status, 'id_desc', $count, $excludedModel);
if(empty($projects))
{
$this->view->projects = $projects;
+5 -3
View File
@@ -3276,11 +3276,13 @@ EOD;
if(empty($markdown)) return false;
global $app;
$app->loadClass('michelf');
$app->loadClass('parsedownextraplugin');
$Michelf = new michelf;
$Parsedown = new parsedownextraplugin;
return $Michelf->parse($markdown);
$Parsedown->voidElementSuffix = '>'; // HTML5
return $Parsedown->text($markdown);
}
}
+3 -2
View File
@@ -8,6 +8,7 @@ $defaultTheme = $webRoot . 'theme/default/';
$langTheme = $themeRoot . 'lang/' . $clientLang . '.css';
$clientTheme = $this->app->getClientTheme();
$onlybody = zget($_GET, 'onlybody', 'no');
$defaultLang = array('zh-cn', 'zh-tw', 'en');
?>
<!DOCTYPE html>
<html lang='<?php echo $clientLang;?>'>
@@ -31,7 +32,7 @@ $onlybody = zget($_GET, 'onlybody', 'no');
js::import($jsRoot . 'jquery/lib.js');
js::import($jsRoot . 'zui/min.js?t=' . $timestamp);
if($clientLang === 'ja') js::import($jsRoot . 'zui/lang.' . $clientLang . '.min.js?t=' . $timestamp);
if(!in_array($clientLang, $defaultLang)) js::import($jsRoot . 'zui/lang.' . $clientLang . '.min.js?t=' . $timestamp);
js::import($jsRoot . 'my.full.js?t=' . $timestamp);
}
@@ -41,7 +42,7 @@ $onlybody = zget($_GET, 'onlybody', 'no');
if(!file_exists($this->app->getThemeRoot() . 'default/' . $this->cookie->lang . '.' . $this->cookie->theme . '.css')) $minCssFile = $defaultTheme . 'en.' . $this->cookie->theme . '.css';
css::import($minCssFile);
js::import($jsRoot . 'all.js');
if($clientLang === 'ja') js::import($jsRoot . 'zui/lang.' . $clientLang . '.min.js');
if(!in_array($clientLang, $defaultLang)) js::import($jsRoot . 'zui/lang.' . $clientLang . '.min.js');
}
if($this->app->getViewType() == 'xhtml') css::import($defaultTheme . 'x.style.css');
+1 -1
View File
@@ -11,7 +11,7 @@ $config->company->dynamic->search['module'] = 'action';
if($config->vision == 'rnd') $config->company->dynamic->search['fields']['product'] = $lang->action->product;
$config->company->dynamic->search['fields']['project'] = '项目';
if($config->systemMode == 'new') $config->company->dynamic->search['fields']['project'] = '项目';
$config->company->dynamic->search['fields']['execution'] = $lang->action->execution;
$config->company->dynamic->search['fields']['actor'] = $lang->action->actor;
$config->company->dynamic->search['fields']['objectID'] = $lang->action->objectID;
+2
View File
@@ -21,3 +21,5 @@
.right-icon a {color: #1183fb;}
.cell th,td {word-break: break-word;}
.main-actions {width: 100% !important;}
.main-col .editor-preview {background: #fff}
.main-col .CodeMirror{border: none}
+1 -1
View File
@@ -295,7 +295,7 @@
$(function()
{
var simplemde = new SimpleMDE({element: $("#markdownContent")[0],toolbar:false, status: false});
simplemde.value(markdownText);
simplemde.value(String(markdownText));
simplemde.togglePreview();
$('#content .CodeMirror .editor-preview a').attr('target', '_blank');
+19
View File
@@ -114,6 +114,10 @@ js::set('docID', $doc->id);
echo "</div></div>";
}
}
elseif($doc->contentType == 'markdown')
{
echo "<textarea id='markdownContent' /></textarea>";
}
else
{
echo $doc->content;
@@ -237,4 +241,19 @@ js::set('docID', $doc->id);
</div>
<?php js::set('canDeleteFile', common::hasPriv('doc', 'deleteFile'));?>
<?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(String(markdownText));
simplemde.togglePreview();
$('#content .CodeMirror .editor-preview a').attr('target', '_blank');
})
</script>
<?php endif;?>
<?php include '../../common/view/footer.html.php';?>
+1 -1
View File
@@ -126,7 +126,7 @@ class execution extends control
$execution = $this->commonAction($executionID, $status);
$executionID = $execution->id;
if($execution->type == 'kanban' and $this->config->vision != 'lite' and defined('RUN_MODE') and RUN_MODE != 'api') $this->locate($this->createLink('execution', 'kanban', "executionID=$executionID"));
if($execution->type == 'kanban' and $this->config->vision != 'lite' and $this->app->getViewType() != 'json') $this->locate($this->createLink('execution', 'kanban', "executionID=$executionID"));
/* Get products by execution. */
$products = $this->product->getProductPairsByProject($executionID);
+3
View File
@@ -17,6 +17,9 @@
#execution_chosen .chosen-single {width: 230px;}
#execution_chosen .chosen-drop ul li .label {margin-top: 2px; background: #fff; color: #838a9d; border: 1px solid #d8d8d8;}
.radio-inline {line-height: normal;}
#dept + .picker {width: 220px;}
#execution + .picker {width: 230px;}
#mainMenu .picker-has-value {float: left;}
#teamForm .table tr > td:first-child {padding-left: 10px;}
#teamForm .table tr > td:last-child {padding-right: 10px;}
+2 -3
View File
@@ -1225,7 +1225,6 @@ function initKanban($kanban)
{
var id = $kanban.data('id');
var region = regions[id];
var displayCards = window.displayCards == 'undefined' ? 2 : window.displayCards;
$kanban.kanban(
{
@@ -1236,7 +1235,7 @@ function initKanban($kanban)
maxColWidth: 240,
cardHeight: getCardHeight(),
fluidBoardWidth: fluidBoard,
displayCards: displayCards,
displayCards: typeof window.displayCards === 'number' ? window.displayCards : 2,
createColumnText: kanbanLang.createColumn,
addItemText: '',
cardsPerRow: window.kanbanScaleSize,
@@ -1533,7 +1532,7 @@ $(function()
function calcColHeight(col, lane, colCards, colHeight, kanban)
{
var options = kanban.options;
if(!options.displayCards) return 0;
if(!options.displayCards) return colHeight;
var displayCards = +(options.displayCards || 2);
+2 -2
View File
@@ -1,10 +1,10 @@
$(function()
{
$('#execution_chosen').click(function()
$('#execution_chosen, #execution + .picker').click(function()
{
if(systemMode == 'new')
{
$('#execution_chosen ul li').each(function(index)
$('#execution_chosen ul li, #pickerDropMenu-pk_execution div a').each(function(index)
{
if(index == 0)
{
+3 -4
View File
@@ -384,12 +384,11 @@ function updateKanban(kanbanID, data)
*/
function createKanban(kanbanID, data, options)
{
var $kanban = $('#kanban-' + kanbanID);
var displayCards = window.displayCards == 'undefined' ? 2 : window.displayCards;
var $kanban = $('#kanban-' + kanbanID);
if($kanban.length) return updateKanban(kanbanID, data);
$kanban = $('<div id="kanban-' + kanbanID + '" data-id="' + kanbanID + '"></div>').appendTo('#kanbans');
$kanban.kanban($.extend({data: data, calcColHeight: calcColHeight, displayCards: displayCards}, options));
$kanban.kanban($.extend({data: data, calcColHeight: calcColHeight, displayCards: typeof window.displayCards === 'number' ? window.displayCards : 2}, options));
}
/**
@@ -1224,7 +1223,7 @@ $('.c-group').change(function()
function calcColHeight(col, lane, colCards, colHeight, kanban)
{
var options = kanban.options;
if(!options.displayCards) return 0;
if(!options.displayCards) return colHeight;
var displayCards = +(options.displayCards || 2);
+22 -7
View File
@@ -2033,13 +2033,28 @@ class executionModel extends model
*/
public function getTasks2Imported($toExecution, $branches)
{
$execution = $this->getById($toExecution);
$project = $this->loadModel('project')->getById($execution->project);
$brotherProjects = $this->project->getBrotherProjects($project);
$executions = $this->dao->select('id')->from(TABLE_EXECUTION)
->where('project')->in($brotherProjects)
->andWhere('status')->ne('closed')
->fetchPairs('id');
if($this->config->systemMode == 'classic')
{
$products = $this->loadModel('product')->getProducts($toExecution);
if(empty($products)) return array();
$execution = $this->getById($toExecution);
$executions = $this->dao->select('t1.product, t1.project')->from(TABLE_PROJECTPRODUCT)->alias('t1')
->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.project=t2.id')
->where('t1.product')->in(array_keys($products))
->andWhere('t2.project')->eq($execution->project)
->fetchGroup('project');
}
else
{
$execution = $this->getById($toExecution);
$project = $this->loadModel('project')->getById($execution->project);
$brotherProjects = $this->project->getBrotherProjects($project);
$executions = $this->dao->select('id')->from(TABLE_EXECUTION)
->where('project')->in($brotherProjects)
->andWhere('status')->ne('closed')
->fetchPairs('id');
}
$branches = str_replace(',', "','", $branches);
$tasks = $this->dao->select('t1.*, t2.id AS storyID, t2.title AS storyTitle, t2.version AS latestStoryVersion, t2.status AS storyStatus, t3.realname AS assignedToRealName')->from(TABLE_TASK)->alias('t1')
+1 -1
View File
@@ -103,6 +103,7 @@ body {margin-bottom: 25px;}
?>
<a class="btn btn-link querybox-toggle" id='bysearchTab'><i class="icon icon-search muted"></i> <?php echo $lang->product->searchStory;?></a>
</div>
<?php $taskCreateLink = $this->createLink('task', 'create', "executionID=$executionID" . (isset($moduleID) ? "&storyID=0&moduleID=$moduleID" : ""));?>
<?php if(!isonlybody()): ?>
<div class="btn-toolbar pull-right">
<?php
@@ -147,7 +148,6 @@ body {margin-bottom: 25px;}
<?php
$checkObject = new stdclass();
$checkObject->execution = $executionID;
$taskCreateLink = $this->createLink('task', 'create', "executionID=$executionID" . (isset($moduleID) ? "&storyID=0&moduleID=$moduleID" : ""));
?>
<?php if($canBeChanged and (common::hasPriv('task', 'batchCreate', $checkObject) or common::hasPriv('task', 'create', $checkObject))):?>
<div class='btn-group dropdown'>
+4 -5
View File
@@ -1228,7 +1228,7 @@ function calcColHeight(col, lane, colCards, colHeight, kanban)
var options = kanban.options;
if(!options.displayCards) return 0;
if(!options.displayCards) return colHeight;
var displayCards = +(options.displayCards || 2);
if (typeof displayCards !== 'number' || displayCards < 2) displayCards = 2;
@@ -1276,9 +1276,8 @@ window.menuCreators =
*/
function initKanban($kanban)
{
var id = $kanban.data('id');
var region = regions[id];
var displayCards = window.displayCards == 'undefined' ? 2 : window.displayCards;
var id = $kanban.data('id');
var region = regions[id];
$kanban.kanban(
{
@@ -1289,7 +1288,7 @@ function initKanban($kanban)
minColWidth: 285,
maxColWidth: 285,
cardHeight: 60,
displayCards: displayCards,
displayCards: typeof window.displayCards === 'number' ? window.displayCards : 2,
createColumnText: kanbanLang.createColumn,
addItemText: '',
itemRender: renderKanbanItem,
+7 -6
View File
@@ -353,18 +353,18 @@ class kanbanModel extends model
$childColumn->limit = isset($data->noLimit[$i]) ? -1 : $data->WIPCount[$i];
$childColumn->order = $order;
if(!preg_match("/^-?\d+$/", $childColumn->limit) or (!isset($data->noLimit[$i]) and $childColumn->limit <= 0))
{
dao::$errors['limit'] = $this->lang->kanban->error->mustBeInt;
return false;
}
if(empty($childColumn->name))
{
dao::$errors['name'] = sprintf($this->lang->error->notempty, $this->lang->kanbancolumn->name);
return false;
}
if(!preg_match("/^-?\d+$/", $childColumn->limit) or (!isset($data->noLimit[$i]) and $childColumn->limit <= 0))
{
dao::$errors['limit'] = $this->lang->kanban->error->mustBeInt;
return false;
}
$sumChildLimit += $childColumn->limit == -1 ? 0 : $childColumn->limit;
if($column->limit != -1 and ($childColumn->limit == -1 or ($column->limit < $sumChildLimit)))
{
@@ -2379,6 +2379,7 @@ class kanbanModel extends model
$cell->lane = $laneID;
$cell->column = $colID;
$cell->type = $type;
$cell->cards = $cardID ? ",$cardID," : '';
$this->dao->insert(TABLE_KANBANCELL)->data($cell)->exec();
}
+4
View File
@@ -30,7 +30,11 @@
<?php if(empty($projectStats)):?>
<div class="table-empty-tip">
<p>
<?php if($config->systemMode == 'new'):?>
<span class="text-muted"><?php echo $lang->project->empty;?></span>
<?php else:?>
<span class="text-muted"><?php echo $lang->execution->noExecution;?></span>
<?php endif;?>
</p>
</div>
<?php else:?>
+3 -2
View File
@@ -274,7 +274,7 @@ class productplan extends control
}
if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess));
die(js::locate(inlink('browse', "productID=$plan->product"), 'parent'));
die(js::reload('parent'));
}
}
@@ -397,7 +397,8 @@ class productplan extends control
if($this->app->getViewType() == 'xhtml') $recPerPage = 10;
/* Append id for secend sort. */
$sort = common::appendOrder($orderBy);
$orderBy = ($type == 'bug' and $orderBy == 'order_desc') ? 'id_desc' : $orderBy;
$sort = common::appendOrder($orderBy);
$this->commonAction($plan->product, $plan->branch);
$products = $this->product->getProductPairsByProject($this->session->project);
@@ -150,7 +150,9 @@
</tbody>
</table>
<div class="table-footer">
<?php if(common::hasPriv('productplan', 'batchEdit') or common::hasPriv('productplan', 'batchChangeStatus')):?>
<div class="checkbox-primary check-all"><label><?php echo $lang->selectAll?></label></div>
<?php endif;?>
<div class="table-actions btn-toolbar">
<?php if(common::hasPriv('productplan', 'batchEdit')):?>
<?php $actionLink = $this->inlink('batchEdit', "productID=$product->id&branch=$branch");?>
+3 -1
View File
@@ -273,16 +273,18 @@ class projectModel extends model
* @param string|int $param
* @param string $orderBy
* @param int $limit
* @param string $excludedModel
* @access public
* @return array
*/
public function getOverviewList($queryType = 'byStatus', $param = 'all', $orderBy = 'id_desc', $limit = 15)
public function getOverviewList($queryType = 'byStatus', $param = 'all', $orderBy = 'id_desc', $limit = 15, $excludedModel = '')
{
$queryType = strtolower($queryType);
$projects = $this->dao->select('*')->from(TABLE_PROJECT)
->where('type')->eq('project')
->andWhere('vision')->eq($this->config->vision)
->andWhere('deleted')->eq(0)
->beginIF($excludedModel)->andWhere('model')->ne($excludedModel)->fi()
->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->projects)->fi()
->beginIF($queryType == 'bystatus' and $param == 'undone')->andWhere('status')->notIN('done,closed')->fi()
->beginIF($queryType == 'bystatus' and $param != 'all' and $param != 'undone')->andWhere('status')->eq($param)->fi()
+4 -1
View File
@@ -42,7 +42,10 @@
<tr>
<td><?php echo sprintf('%03d', $projectID) . html::hidden("projectIdList[$projectID]", $projectID);?></td>
<?php if(isset($unauthorizedPrograms[$project->parent])):?>
<td><?php echo html::select("parents[$projectID]", $unauthorizedPrograms, $project->parent, "class='form-control chosen' data-id='$projectID' data-name='{$project->name}' data-parent='{$project->parent}' disabled");?></td>
<td>
<?php echo html::select("parents[$projectID]", $unauthorizedPrograms, $project->parent, "class='form-control chosen' data-id='$projectID' data-name='{$project->name}' data-parent='{$project->parent}' disabled");?>
<?php echo html::hidden("parents[$projectID]", $project->parent);?>
</td>
<?php else:?>
<td><?php echo html::select("parents[$projectID]", $programs, $project->parent, "class='form-control chosen' data-id='$projectID' data-name='{$project->name}' data-parent='{$project->parent}'");?></td>
<?php endif;?>
+14 -11
View File
@@ -3908,7 +3908,8 @@ class storyModel extends model
$canBeChanged = common::canModify('execution', $execution);
if($canBeChanged)
{
$param = "executionID={$execution->id}&story={$story->id}&moduleID={$story->module}";
$executionID = empty($execution) ? $this->session->execution : $execution->id;
$param = "executionID=$executionID&story={$story->id}&moduleID={$story->module}";
$story->reviewer = isset($story->reviewer) ? $story->reviewer : array();
$story->notReview = isset($story->notReview) ? $story->notReview : array();
@@ -3939,7 +3940,7 @@ class storyModel extends model
}
$this->lang->task->batchCreate = $this->lang->execution->batchWBS;
if($hasDBPriv) $menu .= common::printIcon('task', 'batchCreate', "executionID={$execution->id}&story={$story->id}", '', 'list', 'pluses', '', $toTaskDisabled);
if($hasDBPriv) $menu .= common::printIcon('task', 'batchCreate', "executionID=$executionID&story={$story->id}", '', 'list', 'pluses', '', $toTaskDisabled);
$this->lang->testcase->batchCreate = $this->lang->testcase->create;
if($hasDBPriv and common::hasPriv('testcase', 'create'))
@@ -3949,12 +3950,12 @@ class storyModel extends model
if($canBeChanged and common::hasPriv('execution', 'storyEstimate', $execution))
{
$menu .= common::printIcon('execution', 'storyEstimate', "executionID=$execution->id&storyID=$story->id", '', 'list', 'estimate', '', 'iframe', true, "data-width='600px'");
$menu .= common::printIcon('execution', 'storyEstimate', "executionID=$executionID&storyID=$story->id", '', 'list', 'estimate', '', 'iframe', true, "data-width='600px'");
}
if($canBeChanged and common::hasPriv('execution', 'unlinkStory', $execution))
{
$menu .= common::printIcon('execution', 'unlinkStory', "executionID=$execution->id&storyID=$story->id&confirm=no", '', 'list', 'unlink', 'hiddenwin');
$menu .= common::printIcon('execution', 'unlinkStory', "executionID=$executionID&storyID=$story->id&confirm=no", '', 'list', 'unlink', 'hiddenwin');
}
}
}
@@ -4124,11 +4125,12 @@ class storyModel extends model
*/
public function printCell($col, $story, $users, $branches, $storyStages, $modulePairs = array(), $storyTasks = array(), $storyBugs = array(), $storyCases = array(), $mode = 'datatable', $storyType = 'story', $execution = '', $isShowBranch = '')
{
$module = $this->app->rawModule == 'product' ? 'story' : $this->app->rawModule;
$canView = common::hasPriv($module, 'view');
$tab = $this->app->tab;
$storyLink = $tab == 'execution' ? helper::createLink('story', 'view', "storyID=$story->id&version=$story->version&from=execution&param=$execution->id") : helper::createLink($module, 'view', "storyID=$story->id");
$account = $this->app->user->account;
$module = $this->app->rawModule == 'product' ? 'story' : $this->app->rawModule;
$canView = common::hasPriv($module, 'view');
$tab = $this->app->tab;
$executionID = empty($execution) ? $this->session->execution : $execution->id;
$storyLink = $tab == 'execution' ? helper::createLink('story', 'view', "storyID=$story->id&version=$story->version&from=execution&param=$executionID") : helper::createLink($module, 'view', "storyID=$story->id");
$account = $this->app->user->account;
/* Check the product is closed. */
$canBeChanged = common::canBeChanged('story', $story);
@@ -4148,8 +4150,9 @@ class storyModel extends model
if($tab == 'execution')
{
$checkObject = new stdclass();
$checkObject->execution = $execution->id;
$canBatchToTask = common::hasPriv('story', 'batchToTask', $checkObject);
$checkObject->execution = $executionID;
$canBatchToTask = common::hasPriv('story', 'batchToTask', $checkObject);
}
if($tab == 'execution')
+2 -17
View File
@@ -464,6 +464,7 @@ class task extends control
public function edit($taskID, $comment = false, $kanbanGroup = '')
{
$this->commonAction($taskID);
$task = $this->task->getById($taskID);
if(!empty($_POST))
{
@@ -478,7 +479,6 @@ class task extends control
if(empty($files) and $this->post->uid != '' and isset($_SESSION['album']['used'][$this->post->uid])) $files = $this->file->getPairs($_SESSION['album']['used'][$this->post->uid]);
}
$task = $this->task->getById($taskID);
if($this->post->comment != '' or !empty($changes) or !empty($files))
{
$action = (!empty($changes) or !empty($files)) ? 'Edited' : 'Commented';
@@ -533,21 +533,6 @@ class task extends control
$tasks = $this->task->getParentTaskPairs($this->view->execution->id, $this->view->task->parent);
if(isset($tasks[$taskID])) unset($tasks[$taskID]);
if($this->config->systemMode == 'classic')
{
$executionsPair = $this->execution->getPairs();
}
else
{
$executionsPair = array();
$executions = $this->execution->getByProject(0, 'all', 0);
$projects = $this->project->getPairsByProgram('', 'noclosed');
foreach($executions as $executionId => $execution)
{
$executionsPair[$executionId] = (isset($projects[$execution->project]) ? $projects[$execution->project] . ' / ' : '') . $execution->name;
}
}
if(!isset($this->view->members[$this->view->task->assignedTo])) $this->view->members[$this->view->task->assignedTo] = $this->view->task->assignedTo;
if(isset($this->view->members['closed']) or $this->view->task->status == 'closed') $this->view->members['closed'] = 'Closed';
@@ -559,7 +544,7 @@ class task extends control
$this->view->users = $this->loadModel('user')->getPairs('nodeleted', "{$this->view->task->openedBy},{$this->view->task->canceledBy},{$this->view->task->closedBy}");
$this->view->showAllModule = isset($this->config->execution->task->allModule) ? $this->config->execution->task->allModule : '';
$this->view->modules = $this->tree->getTaskOptionMenu($this->view->task->execution, 0, 0, $this->view->showAllModule ? 'allModule' : '');
$this->view->executions = $executionsPair;
$this->view->executions = $this->config->systemMode == 'classic' ? $this->execution->getPairs() : $this->execution->getByProject($task->project, 'all', 0, true);
$this->display();
}
+2
View File
@@ -295,6 +295,8 @@ class taskModel extends model
if($storyID == 'ditto') $storyID = $preStory;
$preStory = $storyID;
if(!isset($tasks->story[$key - 1]) and $key > 1 and !empty($tasks->name[$key - 1])) $taskNames[] = $tasks->name[$key - 1];
$inNames = in_array($tasks->name[$key], $taskNames);
if(!$inNames || ($inNames && !in_array($storyID, $storyIDs)))
{
+10 -3
View File
@@ -417,6 +417,7 @@ class testcase extends control
/* Set branch. */
$product = $this->product->getById($productID);
if(!isset($this->products[$productID])) $this->products[$productID] = $product->name;
if($this->app->tab == 'execution' or $this->app->tab == 'project')
{
$objectID = $this->app->tab == 'project' ? $this->session->project : $executionID;
@@ -658,6 +659,12 @@ class testcase extends control
if(!$case) return print(js::error($this->lang->notFound) . js::locate('back', 'parent'));
if(!isset($this->products[$productID]))
{
$product = $this->product->getByID($productID);
$this->products[$productID] = $product->name;
}
$this->view->title = $this->products[$productID] . $this->lang->colon . $this->lang->testcase->createBug;
$this->view->runID = $runID;
$this->view->case = $case;
@@ -957,9 +964,9 @@ class testcase extends control
$branchProduct = false;
if($this->app->tab == 'project') $this->loadModel('project')->setMenu($this->session->project);
if($this->app->tab == 'qa') $this->testcase->setMenu($this->products, $productID, $branch);
if($this->app->tab == 'execution') $this->loadModel('execution')->setMenu($this->session->execution);
if($this->app->tab == 'project') $this->loadModel('project')->setMenu($this->session->project);
if($this->app->tab == 'qa' and $type != 'lib') $this->testcase->setMenu($this->products, $productID, $branch);
if($this->app->tab == 'execution') $this->loadModel('execution')->setMenu($this->session->execution);
/* The cases of a product. */
if($productID)
+30
View File
@@ -296,6 +296,12 @@ class testtask extends control
$productID = $task->product;
$buildID = $task->build;
if(!isset($this->products[$productID]))
{
$product = $this->product->getByID($productID);
$this->products[$productID] = $product->name;
}
$build = $this->loadModel('build')->getByID($buildID);
$stories = array();
$bugs = array();
@@ -466,6 +472,12 @@ class testtask extends control
if(!$task) return print(js::error($this->lang->testtask->checkLinked) . js::locate('back'));
$productID = $task->product;
if(!isset($this->products[$productID]))
{
$product = $this->product->getByID($productID);
$this->products[$productID] = $product->name;
}
if($this->app->tab == 'project')
{
$this->loadModel('project')->setMenu($task->project);
@@ -609,6 +621,12 @@ class testtask extends control
unset($this->lang->testtask->report->charts['bugStageGroups']);
unset($this->lang->testtask->report->charts['bugHandleGroups']);
if(!isset($this->products[$productID]))
{
$product = $this->product->getByID($productID);
$this->products[$productID] = $product->name;
}
$this->view->title = $this->products[$productID] . $this->lang->colon . $this->lang->testtask->common . $this->lang->colon . $this->lang->testtask->reportChart;
$this->view->position[] = html::a($this->createLink('testtask', 'cases', "taskID=$taskID"), $this->products[$productID]);
$this->view->position[] = $this->lang->testtask->reportChart;
@@ -644,7 +662,14 @@ class testtask extends control
$groupBy = empty($groupBy) ? 'story' : $groupBy;
$task = $this->testtask->getById($taskID);
if(!$task) return print(js::error($this->lang->notFound) . js::locate('back'));
$productID = $task->product;
if(!isset($this->products[$productID]))
{
$product = $this->product->getByID($productID);
$this->products[$productID] = $product->name;
}
if($this->app->tab == 'project')
{
$this->loadModel('project')->setMenu($this->session->project);
@@ -1024,6 +1049,11 @@ class testtask extends control
/* Get task and product id. */
$task = $this->testtask->getById($taskID);
$productID = $this->product->saveState($task->product, $this->products);
if(!isset($this->products[$productID]))
{
$product = $this->product->getByID($productID);
$this->products[$productID] = $product->name;
}
/* Save session. */
if($this->app->tab == 'project')
+1 -1
View File
@@ -1,2 +1,2 @@
#mailtoGroup .chosen-container-single a {border-left-width: 0px;}
#ownerAndPriBox .picker .picker-selections .picker-selection {width: 180px;}
#ownerAndPriBox .picker .picker-selections .picker-selection {width: 158px;}
+4
View File
@@ -153,6 +153,10 @@ class upgrade extends control
if(!$this->upgrade->isError())
{
$systemMode = $this->loadModel('setting')->getItem('owner=system&module=common&section=global&key=mode');
/* Delete all patch actions if upgrade success. */
$this->loadModel('action')->deleteByType('patch');
if((empty($systemMode) && !isset($this->config->qcVersion) && strpos($fromVersion, 'max') === false) or
($systemMode != 'new' && strpos($fromVersion, 'max') === false && strpos($this->config->version, 'max') !== false))
{
+47
View File
@@ -573,6 +573,53 @@ $(function()
$('[name=programAcl]').attr('disabled', 'disabled');
$('[name=projectAcl]').removeAttr('disabled');
}
$('#submit').click(function()
{
if(type == 'productline')
{
var checkedProductCount = $("input[name^='products']:checked").length;
if(checkedProductCount <= 0)
{
alert(errorNoProduct);
return false;
}
}
else if(type == 'product')
{
var checkedProductCount = $("input[name^='products']:checked").length;
if(checkedProductCount <= 0)
{
alert(errorNoProduct);
return false;
}
var executionCount = 0;
var checkedExecutionCount = 0;
$("input[name^='products']:checked").each(function()
{
var productID = $(this).val()
executionCount += $("[data-product='" + productID + "']").length;
checkedExecutionCount += $("[data-product='" + productID + "']:checked").length;
});
if(executionCount !== 0 && checkedExecutionCount === 0)
{
alert(errorNoExecution);
return false;
}
}
else
{
var checkedExecutionCount = $("input[name^='sprints']:checked").length;
if(checkedExecutionCount === 0)
{
alert(errorNoExecution);
return false;
}
}
})
});
/**
+2
View File
@@ -134,6 +134,8 @@ $lang->upgrade->duplicateProject = "Project name in the same program cannot be
$lang->upgrade->upgradeTips = "Historically deleted data cannot be upgraded, and restoration is not supported after the upgrade. Please be aware.";
$lang->upgrade->moveEXTFileFail = 'The migration file failed, please execute the above command and refresh!';
$lang->upgrade->deleteDirTip = 'After the upgrade, the following folders will affect the use of system functions, please delete them.';
$lang->upgrade->errorNoProduct = "Select the {$lang->productCommon} that you want to merge.";
$lang->upgrade->errorNoExecution = "Select the {$lang->projectCommon} that you want to merge.";
$lang->upgrade->moveExtFileTip = <<<EOT
<p>The new version will be compatible with the extension mechanism of the historical customization/plug-in. You need to migrate the customization/plug-in related files to extension/custom, otherwise the customization/plug-in function will not be available.</p>
<p>Please confirm whether the system has been customized/plug-in. If no customization/plug-in has been done, you can uncheck the following files; Whether you have done customization/plug-in, you can also keep the file checked.</p>
+2
View File
@@ -134,6 +134,8 @@ $lang->upgrade->duplicateProject = "同一个项目集内项目名称不能重
$lang->upgrade->upgradeTips = "历史删除数据不参与升级,升级后将不支持还原,请知悉";
$lang->upgrade->moveEXTFileFail = '迁移文件失败, 请执行上面命令后刷新!';
$lang->upgrade->deleteDirTip = '升级后,如下文件夹会影响系统功能的使用,请删除。';
$lang->upgrade->errorNoProduct = "请选择需要归并的{$lang->productCommon}";
$lang->upgrade->errorNoExecution = "请选择需要归并的{$lang->projectCommon}";
$lang->upgrade->moveExtFileTip = <<<EOT
<p>新版本将对历史的定制/插件进行扩展机制兼容处理,需要将定制/插件相关的文件迁移到extension/custom下,否则定制/插件功能将无法使用。</p>
<p>请您确认系统是否有做过定制/插件,如没有做过定制/插件,可取消勾选如下文件;如果不清楚是否做过定制/插件,也可保持文件勾选。</p>
+40
View File
@@ -508,6 +508,9 @@ class upgradeModel extends model
$this->execSQL($xuanxuanSql);
}
break;
case '17_0_beta2':
$this->changeStoryNeedReview();
break;
}
$this->deletePatch();
@@ -1320,6 +1323,18 @@ class upgradeModel extends model
}
}
/* Delete all patch files when upgrade zentao. */
$patchPath = $this->app->getTmpRoot() . 'patch';
$isDir = is_dir($patchPath);
if(file_exists($patchPath))
{
if(!is_writable($patchPath) or ($isDir and !$zfile->removeDir($patchPath)) or
(!$isDir and !$zfile->removeDir($patchPath)))
{
$result[] = 'rm -f ' . ($isDir ? '-r ' : '') . $patchPath;
}
}
return $result;
}
@@ -6275,4 +6290,29 @@ class upgradeModel extends model
foreach ($executions as $execution) $this->dao->update(TABLE_PROJECT)->set('realBegan')->eq($execution->minBegan)->where('id')->eq($execution->project)->exec();
}
/**
* Change story need Review.
*
* @access public
* @return void
*/
public function changeStoryNeedReview()
{
$this->loadModel('story');
$this->loadModel('setting');
$rndNeedReview = $this->setting->getItem('owner=system&vision=rnd&module=story&section=&key=needReview');
$liteNeedReview = $this->setting->getItem('owner=system&vision=lite&module=story&section=&key=needReview');
$data = new stdclass();
$data->forceReview = '';
$data->forceReviewDepts = '';
$data->forceReviewRoles = '';
if(!empty($rndNeedReview)) $this->setting->setItems("system.story@rnd", $data);
if(!empty($liteNeedReview)) $this->setting->setItems("system.story@lite", $data);
$this->setting->deleteItems('owner=system&module=story&section=&key=forceReviewAll');
}
}
@@ -13,6 +13,8 @@
<?php include '../../common/view/header.lite.html.php';?>
<?php js::set('weekend', $config->execution->weekend);?>
<?php js::set('type', $type);?>
<?php js::set('errorNoProduct', $lang->upgrade->errorNoProduct);?>
<?php js::set('errorNoExecution', $lang->upgrade->errorNoExecution);?>
<div class='container'>
<form method='post' target='hiddenwin'>
<div class='modal-dialog'>
-31
View File
@@ -1,31 +0,0 @@
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> - v<%= pkg.version %> */\n'
},
build: {
src: 'fingerprint.js',
dest: 'build/fingerprint.min.js'
}
},
jshint: {
file: ['Gruntfile.js', 'fingerprint.js', 'specs/**/*_spec.js'],
options: {
eqnull: true,
'-W086': true //W086: Expected a 'break' statement before 'case'.
}
}
});
// Load the plugin that provides the "uglify" task.
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jshint');
// Default task(s).
grunt.registerTask('default', ['jshint', 'uglify']);
};
-285
View File
@@ -1,285 +0,0 @@
/*
* fingerprintJS 0.5.4 - Fast browser fingerprint library
* https://github.com/Valve/fingerprintjs
* Copyright (c) 2013 Valentin Vasilyev (valentin.vasilyev@outlook.com)
* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
;(function (name, context, definition) {
if (typeof module !== 'undefined' && module.exports) { module.exports = definition(); }
else if (typeof define === 'function' && define.amd) { define(definition); }
else { context[name] = definition(); }
})('Fingerprint', this, function () {
'use strict';
var Fingerprint = function (options) {
var nativeForEach, nativeMap;
nativeForEach = Array.prototype.forEach;
nativeMap = Array.prototype.map;
this.each = function (obj, iterator, context) {
if (obj === null) {
return;
}
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
if (iterator.call(context, obj[i], i, obj) === {}) return;
}
} else {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (iterator.call(context, obj[key], key, obj) === {}) return;
}
}
}
};
this.map = function(obj, iterator, context) {
var results = [];
// Not using strict equality so that this acts as a
// shortcut to checking for `null` and `undefined`.
if (obj == null) return results;
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
this.each(obj, function(value, index, list) {
results[results.length] = iterator.call(context, value, index, list);
});
return results;
};
if (typeof options == 'object'){
this.hasher = options.hasher;
this.screen_resolution = options.screen_resolution;
this.screen_orientation = options.screen_orientation;
this.canvas = options.canvas;
this.ie_activex = options.ie_activex;
} else if(typeof options == 'function'){
this.hasher = options;
}
};
Fingerprint.prototype = {
get: function(){
var keys = [];
keys.push(navigator.userAgent);
keys.push(navigator.language);
keys.push(screen.colorDepth);
if (this.screen_resolution) {
var resolution = this.getScreenResolution();
if (typeof resolution !== 'undefined'){ // headless browsers, such as phantomjs
keys.push(this.getScreenResolution().join('x'));
}
}
keys.push(new Date().getTimezoneOffset());
keys.push(this.hasSessionStorage());
keys.push(this.hasLocalStorage());
keys.push(!!window.indexedDB);
//body might not be defined at this point or removed programmatically
if(document.body){
keys.push(typeof(document.body.addBehavior));
} else {
keys.push(typeof undefined);
}
keys.push(typeof(window.openDatabase));
keys.push(navigator.cpuClass);
keys.push(navigator.platform);
keys.push(navigator.doNotTrack);
keys.push(this.getPluginsString());
if(this.canvas && this.isCanvasSupported()){
keys.push(this.getCanvasFingerprint());
}
if(this.hasher){
return this.hasher(keys.join('###'), 31);
} else {
return this.murmurhash3_32_gc(keys.join('###'), 31);
}
},
/**
* JS Implementation of MurmurHash3 (r136) (as of May 20, 2011)
*
* @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
* @see http://github.com/garycourt/murmurhash-js
* @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a>
* @see http://sites.google.com/site/murmurhash/
*
* @param {string} key ASCII only
* @param {number} seed Positive integer only
* @return {number} 32-bit positive integer hash
*/
murmurhash3_32_gc: function(key, seed) {
var remainder, bytes, h1, h1b, c1, c2, k1, i;
remainder = key.length & 3; // key.length % 4
bytes = key.length - remainder;
h1 = seed;
c1 = 0xcc9e2d51;
c2 = 0x1b873593;
i = 0;
while (i < bytes) {
k1 =
((key.charCodeAt(i) & 0xff)) |
((key.charCodeAt(++i) & 0xff) << 8) |
((key.charCodeAt(++i) & 0xff) << 16) |
((key.charCodeAt(++i) & 0xff) << 24);
++i;
k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff;
k1 = (k1 << 15) | (k1 >>> 17);
k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff;
h1 ^= k1;
h1 = (h1 << 13) | (h1 >>> 19);
h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff;
h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16));
}
k1 = 0;
switch (remainder) {
case 3: k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
case 2: k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
case 1: k1 ^= (key.charCodeAt(i) & 0xff);
k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
k1 = (k1 << 15) | (k1 >>> 17);
k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
h1 ^= k1;
}
h1 ^= key.length;
h1 ^= h1 >>> 16;
h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff;
h1 ^= h1 >>> 13;
h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff;
h1 ^= h1 >>> 16;
return h1 >>> 0;
},
// https://bugzilla.mozilla.org/show_bug.cgi?id=781447
hasLocalStorage: function () {
try{
return !!window.localStorage;
} catch(e) {
return true; // SecurityError when referencing it means it exists
}
},
hasSessionStorage: function () {
try{
return !!window.sessionStorage;
} catch(e) {
return true; // SecurityError when referencing it means it exists
}
},
isCanvasSupported: function () {
var elem = document.createElement('canvas');
return !!(elem.getContext && elem.getContext('2d'));
},
isIE: function () {
if(navigator.appName === 'Microsoft Internet Explorer') {
return true;
} else if(navigator.appName === 'Netscape' && /Trident/.test(navigator.userAgent)){// IE 11
return true;
}
return false;
},
getPluginsString: function () {
if(this.isIE() && this.ie_activex){
return this.getIEPluginsString();
} else {
return this.getRegularPluginsString();
}
},
getRegularPluginsString: function () {
return this.map(navigator.plugins, function (p) {
var mimeTypes = this.map(p, function(mt){
return [mt.type, mt.suffixes].join('~');
}).join(',');
return [p.name, p.description, mimeTypes].join('::');
}, this).join(';');
},
getIEPluginsString: function () {
if(window.ActiveXObject){
var names = ['ShockwaveFlash.ShockwaveFlash',//flash plugin
'AcroPDF.PDF', // Adobe PDF reader 7+
'PDF.PdfCtrl', // Adobe PDF reader 6 and earlier, brrr
'QuickTime.QuickTime', // QuickTime
// 5 versions of real players
'rmocx.RealPlayer G2 Control',
'rmocx.RealPlayer G2 Control.1',
'RealPlayer.RealPlayer(tm) ActiveX Control (32-bit)',
'RealVideo.RealVideo(tm) ActiveX Control (32-bit)',
'RealPlayer',
'SWCtl.SWCtl', // ShockWave player
'WMPlayer.OCX', // Windows media player
'AgControl.AgControl', // Silverlight
'Skype.Detection'];
// starting to detect plugins in IE
return this.map(names, function(name){
try{
new ActiveXObject(name);
return name;
} catch(e){
return null;
}
}).join(';');
} else {
return ""; // behavior prior version 0.5.0, not breaking backwards compat.
}
},
getScreenResolution: function () {
var resolution;
if(this.screen_orientation){
resolution = (screen.height > screen.width) ? [screen.height, screen.width] : [screen.width, screen.height];
}else{
resolution = [screen.height, screen.width];
}
return resolution;
},
getCanvasFingerprint: function () {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
// https://www.browserleaks.com/canvas#how-does-it-work
var txt = 'http://valve.github.io';
ctx.textBaseline = "top";
ctx.font = "14px 'Arial'";
ctx.textBaseline = "alphabetic";
ctx.fillStyle = "#f60";
ctx.fillRect(125,1,62,20);
ctx.fillStyle = "#069";
ctx.fillText(txt, 2, 15);
ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
ctx.fillText(txt, 4, 17);
return canvas.toDataURL();
}
};
return Fingerprint;
});
-10
View File
@@ -1,10 +0,0 @@
{
"name": "fingerprintjs",
"version": "0.5.3",
"main": "fingerprint.js",
"devDependencies": {
"grunt": "~0.4.2",
"grunt-contrib-jshint": "~0.6.3",
"grunt-contrib-uglify": "~0.2.2"
}
}
@@ -1,124 +0,0 @@
describe("Fingerprint", function(){
beforeEach(function() {
this.addMatchers({
toBeInstanceOf : function(expected) {
return this.actual instanceof expected && this.actual.length > 0;
},
toBeA: function(expected) {
return typeof this.actual === expected;
}
});
});
describe("new Fingerprint", function(){
it("Creates a new instance of Fingerprint", function(){
expect(new Fingerprint()).not.toBeNull();
});
it("Accepts a custom hashing function as argument", function(){
var hasher = function(){return 31;};
expect(new Fingerprint(hasher)).not.toBeNull();
});
it("Accepts a custom hasing function as options argument", function(){
var hasher = function(){return 31;};
expect(new Fingerprint({hasher: hasher})).not.toBeNull();
});
});
describe("#get", function(){
it("Calculates fingerprint with built-in hashing if no custom hashing is given", function(){
var fingerprint = new Fingerprint();
spyOn(fingerprint, 'murmurhash3_32_gc');
fingerprint.get();
expect(fingerprint.murmurhash3_32_gc).toHaveBeenCalled();
});
it("Calculates fingerprint with custom hashing if it is given as an argument", function(){
var hasher = function(){return 'abcdef';};
var fingerprint = new Fingerprint(hasher);
expect(fingerprint.get()).toEqual('abcdef');
});
it("Calculates fingerprint with custom hashing if it is given as an options argument", function(){
var hasher = function(){return 'abcdef';};
var fingerprint = new Fingerprint({hasher: hasher});
expect(fingerprint.get()).toEqual('abcdef');
});
it('Calculates fingerprint with canvas fingerprinting if it is said to do so', function(){
var fp = new Fingerprint({canvas: true});
spyOn(fp, 'getCanvasFingerprint');
fp.get();
expect(fp.getCanvasFingerprint).toHaveBeenCalled();
});
it('Does not try to use canvas fingerprinting when not told to(version 1)', function(){
var fp = new Fingerprint({canvas: false});
spyOn(fp, 'getCanvasFingerprint');
fp.get();
expect(fp.getCanvasFingerprint).not.toHaveBeenCalled();
});
it('Does not try to use canvas fingerprinting when not told to(version 2)', function(){
var fp = new Fingerprint();
spyOn(fp, 'getCanvasFingerprint');
fp.get();
expect(fp.getCanvasFingerprint).not.toHaveBeenCalled();
});
it('Calculates fingerprint with ActiveX fingerprinting if it is said to do so', function(){
var fp = new Fingerprint({ie_activex: true});
spyOn(fp, 'isIE').andReturn(true);
spyOn(fp, 'getIEPluginsString');
fp.get();
expect(fp.getIEPluginsString).toHaveBeenCalled();
});
it('Does not try to use ActiveX fingerprinting when not told to(version 1)', function(){
var fp = new Fingerprint({ie_activex: false});
spyOn(fp, 'getIEPluginsString');
fp.get();
expect(fp.getIEPluginsString).not.toHaveBeenCalled();
});
it('Does not try to use ActiveX fingerprinting when not told to(version 2)', function(){
var fp = new Fingerprint();
spyOn(fp, 'getIEPluginsString');
fp.get();
expect(fp.getIEPluginsString).not.toHaveBeenCalled();
});
it('Calculates fingerprint accessing screen resolution if it is said to do so', function(){
var fp = new Fingerprint({screen_resolution: true});
spyOn(fp, 'getScreenResolution');
fp.get();
expect(fp.getScreenResolution).toHaveBeenCalled();
});
it('Does not try to use screen resolution when not told to', function(){
var fp = new Fingerprint();
spyOn(fp, 'getScreenResolution');
fp.get();
expect(fp.getScreenResolution).not.toHaveBeenCalled();
});
it("Returns a number as a fingerprint value when used with a built-in hashing function", function(){
var fingerprint = new Fingerprint();
expect(fingerprint.get()).toBeA('number');
});
it('Returns a string from getCanvasFingerprint function', function(){
var fp = new Fingerprint({canvas: true});
expect(fp.getCanvasFingerprint()).toBeA('string');
});
it('Does not fail when document.body is null', function(){
var body = document.body.parentNode.removeChild(document.body);
var fingerprint = new Fingerprint();
expect(fingerprint.get()).toBeA('number');
document.body = body;
});
});
});
@@ -1,681 +0,0 @@
jasmine.HtmlReporterHelpers = {};
jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
var el = document.createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
if (child) {
el.appendChild(child);
}
}
}
for (var attr in attrs) {
if (attr == "className") {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
};
jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
var results = child.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.skipped) {
status = 'skipped';
}
return status;
};
jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
var parentDiv = this.dom.summary;
var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
var parent = child[parentSuite];
if (parent) {
if (typeof this.views.suites[parent.id] == 'undefined') {
this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
}
parentDiv = this.views.suites[parent.id].element;
}
parentDiv.appendChild(childElement);
};
jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
for(var fn in jasmine.HtmlReporterHelpers) {
ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
}
};
jasmine.HtmlReporter = function(_doc) {
var self = this;
var doc = _doc || window.document;
var reporterView;
var dom = {};
// Jasmine Reporter Public Interface
self.logRunningSpecs = false;
self.reportRunnerStarting = function(runner) {
var specs = runner.specs() || [];
if (specs.length == 0) {
return;
}
createReporterDom(runner.env.versionString());
doc.body.appendChild(dom.reporter);
setExceptionHandling();
reporterView = new jasmine.HtmlReporter.ReporterView(dom);
reporterView.addSpecs(specs, self.specFilter);
};
self.reportRunnerResults = function(runner) {
reporterView && reporterView.complete();
};
self.reportSuiteResults = function(suite) {
reporterView.suiteComplete(suite);
};
self.reportSpecStarting = function(spec) {
if (self.logRunningSpecs) {
self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
}
};
self.reportSpecResults = function(spec) {
reporterView.specComplete(spec);
};
self.log = function() {
var console = jasmine.getGlobal().console;
if (console && console.log) {
if (console.log.apply) {
console.log.apply(console, arguments);
} else {
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
}
}
};
self.specFilter = function(spec) {
if (!focusedSpecName()) {
return true;
}
return spec.getFullName().indexOf(focusedSpecName()) === 0;
};
return self;
function focusedSpecName() {
var specName;
(function memoizeFocusedSpec() {
if (specName) {
return;
}
var paramMap = [];
var params = jasmine.HtmlReporter.parameters(doc);
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
}
specName = paramMap.spec;
})();
return specName;
}
function createReporterDom(version) {
dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
dom.banner = self.createDom('div', { className: 'banner' },
self.createDom('span', { className: 'title' }, "Jasmine "),
self.createDom('span', { className: 'version' }, version)),
dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
dom.alert = self.createDom('div', {className: 'alert'},
self.createDom('span', { className: 'exceptions' },
self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'),
self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))),
dom.results = self.createDom('div', {className: 'results'},
dom.summary = self.createDom('div', { className: 'summary' }),
dom.details = self.createDom('div', { id: 'details' }))
);
}
function noTryCatch() {
return window.location.search.match(/catch=false/);
}
function searchWithCatch() {
var params = jasmine.HtmlReporter.parameters(window.document);
var removed = false;
var i = 0;
while (!removed && i < params.length) {
if (params[i].match(/catch=/)) {
params.splice(i, 1);
removed = true;
}
i++;
}
if (jasmine.CATCH_EXCEPTIONS) {
params.push("catch=false");
}
return params.join("&");
}
function setExceptionHandling() {
var chxCatch = document.getElementById('no_try_catch');
if (noTryCatch()) {
chxCatch.setAttribute('checked', true);
jasmine.CATCH_EXCEPTIONS = false;
}
chxCatch.onclick = function() {
window.location.search = searchWithCatch();
};
}
};
jasmine.HtmlReporter.parameters = function(doc) {
var paramStr = doc.location.search.substring(1);
var params = [];
if (paramStr.length > 0) {
params = paramStr.split('&');
}
return params;
}
jasmine.HtmlReporter.sectionLink = function(sectionName) {
var link = '?';
var params = [];
if (sectionName) {
params.push('spec=' + encodeURIComponent(sectionName));
}
if (!jasmine.CATCH_EXCEPTIONS) {
params.push("catch=false");
}
if (params.length > 0) {
link += params.join("&");
}
return link;
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);
jasmine.HtmlReporter.ReporterView = function(dom) {
this.startedAt = new Date();
this.runningSpecCount = 0;
this.completeSpecCount = 0;
this.passedCount = 0;
this.failedCount = 0;
this.skippedCount = 0;
this.createResultsMenu = function() {
this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
' | ',
this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
this.summaryMenuItem.onclick = function() {
dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
};
this.detailsMenuItem.onclick = function() {
showDetails();
};
};
this.addSpecs = function(specs, specFilter) {
this.totalSpecCount = specs.length;
this.views = {
specs: {},
suites: {}
};
for (var i = 0; i < specs.length; i++) {
var spec = specs[i];
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
if (specFilter(spec)) {
this.runningSpecCount++;
}
}
};
this.specComplete = function(spec) {
this.completeSpecCount++;
if (isUndefined(this.views.specs[spec.id])) {
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
}
var specView = this.views.specs[spec.id];
switch (specView.status()) {
case 'passed':
this.passedCount++;
break;
case 'failed':
this.failedCount++;
break;
case 'skipped':
this.skippedCount++;
break;
}
specView.refresh();
this.refresh();
};
this.suiteComplete = function(suite) {
var suiteView = this.views.suites[suite.id];
if (isUndefined(suiteView)) {
return;
}
suiteView.refresh();
};
this.refresh = function() {
if (isUndefined(this.resultsMenu)) {
this.createResultsMenu();
}
// currently running UI
if (isUndefined(this.runningAlert)) {
this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" });
dom.alert.appendChild(this.runningAlert);
}
this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
// skipped specs UI
if (isUndefined(this.skippedAlert)) {
this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" });
}
this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
if (this.skippedCount === 1 && isDefined(dom.alert)) {
dom.alert.appendChild(this.skippedAlert);
}
// passing specs UI
if (isUndefined(this.passedAlert)) {
this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" });
}
this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
// failing specs UI
if (isUndefined(this.failedAlert)) {
this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
}
this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
if (this.failedCount === 1 && isDefined(dom.alert)) {
dom.alert.appendChild(this.failedAlert);
dom.alert.appendChild(this.resultsMenu);
}
// summary info
this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
};
this.complete = function() {
dom.alert.removeChild(this.runningAlert);
this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
if (this.failedCount === 0) {
dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
} else {
showDetails();
}
dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
};
return this;
function showDetails() {
if (dom.reporter.className.search(/showDetails/) === -1) {
dom.reporter.className += " showDetails";
}
}
function isUndefined(obj) {
return typeof obj === 'undefined';
}
function isDefined(obj) {
return !isUndefined(obj);
}
function specPluralizedFor(count) {
var str = count + " spec";
if (count > 1) {
str += "s"
}
return str;
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
this.spec = spec;
this.dom = dom;
this.views = views;
this.symbol = this.createDom('li', { className: 'pending' });
this.dom.symbolSummary.appendChild(this.symbol);
this.summary = this.createDom('div', { className: 'specSummary' },
this.createDom('a', {
className: 'description',
href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()),
title: this.spec.getFullName()
}, this.spec.description)
);
this.detail = this.createDom('div', { className: 'specDetail' },
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
title: this.spec.getFullName()
}, this.spec.getFullName())
);
};
jasmine.HtmlReporter.SpecView.prototype.status = function() {
return this.getSpecStatus(this.spec);
};
jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
this.symbol.className = this.status();
switch (this.status()) {
case 'skipped':
break;
case 'passed':
this.appendSummaryToSuiteDiv();
break;
case 'failed':
this.appendSummaryToSuiteDiv();
this.appendFailureDetail();
break;
}
};
jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
this.summary.className += ' ' + this.status();
this.appendToSummary(this.spec, this.summary);
};
jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
this.detail.className += ' ' + this.status();
var resultItems = this.spec.results().getItems();
var messagesDiv = this.createDom('div', { className: 'messages' });
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
if (result.type == 'log') {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
} else if (result.type == 'expect' && result.passed && !result.passed()) {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
if (result.trace.stack) {
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
}
}
}
if (messagesDiv.childNodes.length > 0) {
this.detail.appendChild(messagesDiv);
this.dom.details.appendChild(this.detail);
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
this.suite = suite;
this.dom = dom;
this.views = views;
this.element = this.createDom('div', { className: 'suite' },
this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description)
);
this.appendToSummary(this.suite, this.element);
};
jasmine.HtmlReporter.SuiteView.prototype.status = function() {
return this.getSpecStatus(this.suite);
};
jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
this.element.className += " " + this.status();
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
/* @deprecated Use jasmine.HtmlReporter instead
*/
jasmine.TrivialReporter = function(doc) {
this.document = doc || document;
this.suiteDivs = {};
this.logRunningSpecs = false;
};
jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
var el = document.createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
if (child) { el.appendChild(child); }
}
}
for (var attr in attrs) {
if (attr == "className") {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
};
jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
var showPassed, showSkipped;
this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
this.createDom('div', { className: 'banner' },
this.createDom('div', { className: 'logo' },
this.createDom('span', { className: 'title' }, "Jasmine"),
this.createDom('span', { className: 'version' }, runner.env.versionString())),
this.createDom('div', { className: 'options' },
"Show ",
showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
)
),
this.runnerDiv = this.createDom('div', { className: 'runner running' },
this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
);
this.document.body.appendChild(this.outerDiv);
var suites = runner.suites();
for (var i = 0; i < suites.length; i++) {
var suite = suites[i];
var suiteDiv = this.createDom('div', { className: 'suite' },
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
this.suiteDivs[suite.id] = suiteDiv;
var parentDiv = this.outerDiv;
if (suite.parentSuite) {
parentDiv = this.suiteDivs[suite.parentSuite.id];
}
parentDiv.appendChild(suiteDiv);
}
this.startedAt = new Date();
var self = this;
showPassed.onclick = function(evt) {
if (showPassed.checked) {
self.outerDiv.className += ' show-passed';
} else {
self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
}
};
showSkipped.onclick = function(evt) {
if (showSkipped.checked) {
self.outerDiv.className += ' show-skipped';
} else {
self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
}
};
};
jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
var results = runner.results();
var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
this.runnerDiv.setAttribute("class", className);
//do it twice for IE
this.runnerDiv.setAttribute("className", className);
var specs = runner.specs();
var specCount = 0;
for (var i = 0; i < specs.length; i++) {
if (this.specFilter(specs[i])) {
specCount++;
}
}
var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
};
jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
var results = suite.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.totalCount === 0) { // todo: change this to check results.skipped
status = 'skipped';
}
this.suiteDivs[suite.id].className += " " + status;
};
jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
if (this.logRunningSpecs) {
this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
}
};
jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
var results = spec.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.skipped) {
status = 'skipped';
}
var specDiv = this.createDom('div', { className: 'spec ' + status },
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(spec.getFullName()),
title: spec.getFullName()
}, spec.description));
var resultItems = results.getItems();
var messagesDiv = this.createDom('div', { className: 'messages' });
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
if (result.type == 'log') {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
} else if (result.type == 'expect' && result.passed && !result.passed()) {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
if (result.trace.stack) {
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
}
}
}
if (messagesDiv.childNodes.length > 0) {
specDiv.appendChild(messagesDiv);
}
this.suiteDivs[spec.suite.id].appendChild(specDiv);
};
jasmine.TrivialReporter.prototype.log = function() {
var console = jasmine.getGlobal().console;
if (console && console.log) {
if (console.log.apply) {
console.log.apply(console, arguments);
} else {
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
}
}
};
jasmine.TrivialReporter.prototype.getLocation = function() {
return this.document.location;
};
jasmine.TrivialReporter.prototype.specFilter = function(spec) {
var paramMap = {};
var params = this.getLocation().search.substring(1).split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
}
if (!paramMap.spec) {
return true;
}
return spec.getFullName().indexOf(paramMap.spec) === 0;
};
@@ -1,82 +0,0 @@
body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
#HTMLReporter a { text-decoration: none; }
#HTMLReporter a:hover { text-decoration: underline; }
#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
#HTMLReporter .version { color: #aaaaaa; }
#HTMLReporter .banner { margin-top: 14px; }
#HTMLReporter .duration { color: #aaaaaa; float: right; }
#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
#HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
#HTMLReporter .runningAlert { background-color: #666666; }
#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
#HTMLReporter .passingAlert { background-color: #a6b779; }
#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
#HTMLReporter .failingAlert { background-color: #cf867e; }
#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
#HTMLReporter .results { margin-top: 14px; }
#HTMLReporter #details { display: none; }
#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter.showDetails .summary { display: none; }
#HTMLReporter.showDetails #details { display: block; }
#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter .summary { margin-top: 14px; }
#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
#HTMLReporter .description + .suite { margin-top: 0; }
#HTMLReporter .suite { margin-top: 14px; }
#HTMLReporter .suite a { color: #333333; }
#HTMLReporter #details .specDetail { margin-bottom: 28px; }
#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
#HTMLReporter .resultMessage span.result { display: block; }
#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
#TrivialReporter .runner.running { background-color: yellow; }
#TrivialReporter .options { text-align: right; font-size: .8em; }
#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
#TrivialReporter .suite .suite { margin: 5px; }
#TrivialReporter .suite.passed { background-color: #dfd; }
#TrivialReporter .suite.failed { background-color: #fdd; }
#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
#TrivialReporter .spec.skipped { background-color: #bbb; }
#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
#TrivialReporter .passed { background-color: #cfc; display: none; }
#TrivialReporter .failed { background-color: #fbb; }
#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
#TrivialReporter .resultMessage .mismatch { color: black; }
#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
File diff suppressed because it is too large Load Diff
@@ -1,104 +0,0 @@
/**
Jasmine Reporter that outputs test results to the browser console.
Useful for running in a headless environment such as PhantomJs, ZombieJs etc.
Usage:
// From your html file that loads jasmine:
jasmine.getEnv().addReporter(new jasmine.ConsoleReporter());
jasmine.getEnv().execute();
*/
(function(jasmine, console) {
if (!jasmine) {
throw "jasmine library isn't loaded!";
}
var ANSI = {}
ANSI.color_map = {
"green" : 32,
"red" : 31
}
ANSI.colorize_text = function(text, color) {
var color_code = this.color_map[color];
return "\033[" + color_code + "m" + text + "\033[0m";
}
var ConsoleReporter = function() {
if (!console || !console.log) { throw "console isn't present!"; }
this.status = this.statuses.stopped;
};
var proto = ConsoleReporter.prototype;
proto.statuses = {
stopped : "stopped",
running : "running",
fail : "fail",
success : "success"
};
proto.reportRunnerStarting = function(runner) {
this.status = this.statuses.running;
this.start_time = (new Date()).getTime();
this.executed_specs = 0;
this.passed_specs = 0;
this.log("Starting...");
};
proto.reportRunnerResults = function(runner) {
var failed = this.executed_specs - this.passed_specs;
var spec_str = this.executed_specs + (this.executed_specs === 1 ? " spec, " : " specs, ");
var fail_str = failed + (failed === 1 ? " failure in " : " failures in ");
var color = (failed > 0)? "red" : "green";
var dur = (new Date()).getTime() - this.start_time;
this.log("");
this.log("Finished");
this.log("-----------------");
this.log(spec_str + fail_str + (dur/1000) + "s.", color);
this.status = (failed > 0)? this.statuses.fail : this.statuses.success;
/* Print something that signals that testing is over so that headless browsers
like PhantomJs know when to terminate. */
this.log("");
this.log("ConsoleReporter finished");
};
proto.reportSpecStarting = function(spec) {
this.executed_specs++;
};
proto.reportSpecResults = function(spec) {
if (spec.results().passed()) {
this.passed_specs++;
return;
}
var resultText = spec.suite.description + " : " + spec.description;
this.log(resultText, "red");
var items = spec.results().getItems()
for (var i = 0; i < items.length; i++) {
var trace = items[i].trace.stack || items[i].trace;
this.log(trace, "red");
}
};
proto.reportSuiteResults = function(suite) {
if (!suite.parentSuite) { return; }
var results = suite.results();
var failed = results.totalCount - results.passedCount;
var color = (failed > 0)? "red" : "green";
this.log(suite.getFullName() + ": " + results.passedCount + " of " + results.totalCount + " passed.", color);
};
proto.log = function(str, color) {
var text = (color != undefined)? ANSI.colorize_text(str, color) : str;
console.log(text)
};
jasmine.ConsoleReporter = ConsoleReporter;
})(jasmine, console);
@@ -1,46 +0,0 @@
#!/usr/local/bin/phantomjs
# Runs a Jasmine Suite from an html page
# @page is a PhantomJs page object
# @exit_func is the function to call in order to exit the script
class PhantomJasmineRunner
constructor: (@page, @exit_func = phantom.exit) ->
@tries = 0
@max_tries = 10
get_status: -> @page.evaluate(-> console_reporter.status)
terminate: ->
switch @get_status()
when "success" then @exit_func 0
when "fail" then @exit_func 1
else @exit_func 2
# Script Begin
if phantom.args.length == 0
console.log "Need a url as the argument"
phantom.exit 1
page = new WebPage()
runner = new PhantomJasmineRunner(page)
# Don't supress console output
page.onConsoleMessage = (msg) ->
console.log msg
# Terminate when the reporter singals that testing is over.
# We cannot use a callback function for this (because page.evaluate is sandboxed),
# so we have to *observe* the website.
if msg == "ConsoleReporter finished"
runner.terminate()
address = phantom.args[0]
page.open address, (status) ->
if status != "success"
console.log "can't load the address!"
phantom.exit 1
# Now we wait until onConsoleMessage reads the termination signal from the log.
-1
View File
@@ -1 +0,0 @@
phantomjs lib/phantom-jasmine/run_jasmine_test.coffee test_runner.html
-22
View File
@@ -1,22 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Jasmine Test Runner</title>
<link rel="stylesheet" type="text/css" href="lib/jasmine-1.3.1/jasmine.css">
<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script>
<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script>
<script type="text/javascript" src="lib/phantom-jasmine/console-runner.js"></script>
<script type="text/javascript" src="../fingerprint.js"></script>
<script type="text/javascript" src="fingerprint_spec.js"></script>
</head>
<body>
<script type="text/javascript">
var console_reporter = new jasmine.ConsoleReporter()
jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
jasmine.getEnv().addReporter(console_reporter);
jasmine.getEnv().execute();
</script>
</body>
</html>
+1 -1
View File
@@ -245,7 +245,7 @@ function setMailto(mailto, contactListID)
{
$('#' + mailto).replaceWith(users);
$('#' + mailto + '_chosen').remove();
$('.picker').remove();
$('#' + mailto).siblings('.picker').remove();
if($("[data-pickertype='remote']").length == 0 && $('.picker-select').length == 0)
{