Merge branch 'master' of https://gitlab.zcorp.cc/easycorp/zentaopms
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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')
|
||||
{
|
||||
|
||||
@@ -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
@@ -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');
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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'");
|
||||
|
||||
@@ -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
@@ -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';
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>";
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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' => '↩',
|
||||
'allowRawHtmlInSafeMode' => true,
|
||||
'autobreak' => false,
|
||||
);
|
||||
}
|
||||
|
||||
unset($backLinkElements[0]);
|
||||
|
||||
$n = count($textElements) -1;
|
||||
|
||||
if ($textElements[$n]['name'] === 'p')
|
||||
{
|
||||
$backLinkElements = array_merge(
|
||||
array(
|
||||
array(
|
||||
'rawHtml' => ' ',
|
||||
'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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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 ';
|
||||
|
||||
@@ -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 = '系统创建';
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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';?>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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:?>
|
||||
|
||||
@@ -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");?>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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¶m=$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¶m=$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
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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)))
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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;}
|
||||
|
||||
@@ -153,6 +153,10 @@ class upgrade extends control
|
||||
if(!$this->upgrade->isError())
|
||||
{
|
||||
$systemMode = $this->loadModel('setting')->getItem('owner=system&module=common§ion=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))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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§ion=&key=needReview');
|
||||
$liteNeedReview = $this->setting->getItem('owner=system&vision=lite&module=story§ion=&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§ion=&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'>
|
||||
|
||||
@@ -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']);
|
||||
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
});
|
||||
@@ -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 +0,0 @@
|
||||
phantomjs lib/phantom-jasmine/run_jasmine_test.coffee test_runner.html
|
||||
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user