From f3d4b5b60bd663a8ae88b55ce0343e1fb8c16ee2 Mon Sep 17 00:00:00 2001 From: sunguangming Date: Fri, 7 Mar 2025 01:57:11 +0000 Subject: [PATCH 01/53] * [ticket#1833,done,0.2h] fix bug. --- module/execution/model.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/module/execution/model.php b/module/execution/model.php index b34bce5194..4a1ffd560d 100755 --- a/module/execution/model.php +++ b/module/execution/model.php @@ -3696,20 +3696,17 @@ class executionModel extends model $orderBy = str_replace('storyTitle', 'story', $orderBy); $orderBy = implode(',', $orderBy); - $taskIdList = $sql->where($condition) - ->andWhere('deleted')->eq(0) - ->orderBy($orderBy) - ->page($pager, 't1.id') - ->fetchAll('id'); - - $orderBy = str_replace(array('t1.pri_', 't1.`pri'), array('priOrder_', '`priOrder_'), $orderBy); - $tasks = $this->dao->select('t1.*, t2.id AS storyID, t2.title AS storyTitle, t2.product, t2.branch, t2.version AS latestStoryVersion, t2.status AS storyStatus, t3.realname AS assignedToRealName, IF(t1.`pri` = 0, 999, t1.`pri`) as priOrder') + $condition = preg_replace('/`(\w+)`/', 't1.`$1`', $condition); + $condition = str_replace("AND deleted = '0'", '', $condition); + $orderBy = str_replace(array('t1.pri_', 't1.`pri'), array('priOrder_', '`priOrder_'), $orderBy); + $tasks = $this->dao->select('t1.*, t2.id AS storyID, t2.title AS storyTitle, t2.product, t2.branch, t2.version AS latestStoryVersion, t2.status AS storyStatus, t3.realname AS assignedToRealName, IF(t1.`pri` = 0, 999, t1.`pri`) as priOrder') ->from(TABLE_TASK)->alias('t1') ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') ->leftJoin(TABLE_USER)->alias('t3')->on('t1.assignedTo = t3.account') ->where('t1.deleted')->eq(0) - ->andWhere('t1.id')->in(array_keys($taskIdList)) + ->andWhere($condition) ->orderBy($orderBy) + ->page($pager, 't1.id') ->fetchAll('id'); $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'task', true); From 0dfb7cee984f10cfc45623d2678c4d43962a28a5 Mon Sep 17 00:00:00 2001 From: tianshujie Date: Tue, 18 Feb 2025 15:30:41 +0800 Subject: [PATCH 02/53] * [task#137483,done,0.3h] add preStatus field for zt_feedback table. --- db/update21.5.sql | 1 + db/zentao.sql | 1 + 2 files changed, 2 insertions(+) create mode 100644 db/update21.5.sql diff --git a/db/update21.5.sql b/db/update21.5.sql new file mode 100644 index 0000000000..cabc573716 --- /dev/null +++ b/db/update21.5.sql @@ -0,0 +1 @@ +ALTER TABLE `zt_feedback` ADD COLUMN `preStatus` varchar(30) NOT NULL DEFAULT '' AFTER `subStatus`; diff --git a/db/zentao.sql b/db/zentao.sql index db555f2391..7f61f91b62 100755 --- a/db/zentao.sql +++ b/db/zentao.sql @@ -2628,6 +2628,7 @@ CREATE TABLE IF NOT EXISTS `zt_feedback` ( `pri` tinyint unsigned NOT NULL DEFAULT 2, `status` varchar(30) NOT NULL DEFAULT '', `subStatus` varchar(30) NOT NULL default '', + `preStatus` varchar(30) NOT NULL DEFAULT '', `public` enum('0','1') NOT NULL DEFAULT '0', `notify` enum('0','1') NOT NULL DEFAULT '0', `notifyEmail` varchar(100) NOT NULL DEFAULT '', From 8d89b485e96ece4fef54ee32c51620245fe67e86 Mon Sep 17 00:00:00 2001 From: tianshujie Date: Tue, 18 Feb 2025 15:36:41 +0800 Subject: [PATCH 03/53] * [task#137483,doing,0.1h] change field name. --- db/update21.5.sql | 2 +- db/zentao.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/db/update21.5.sql b/db/update21.5.sql index cabc573716..15904b48d2 100644 --- a/db/update21.5.sql +++ b/db/update21.5.sql @@ -1 +1 @@ -ALTER TABLE `zt_feedback` ADD COLUMN `preStatus` varchar(30) NOT NULL DEFAULT '' AFTER `subStatus`; +ALTER TABLE `zt_feedback` ADD COLUMN `prevStatus` varchar(30) NOT NULL DEFAULT '' AFTER `subStatus`; diff --git a/db/zentao.sql b/db/zentao.sql index 7f61f91b62..d251f4354a 100755 --- a/db/zentao.sql +++ b/db/zentao.sql @@ -2628,7 +2628,7 @@ CREATE TABLE IF NOT EXISTS `zt_feedback` ( `pri` tinyint unsigned NOT NULL DEFAULT 2, `status` varchar(30) NOT NULL DEFAULT '', `subStatus` varchar(30) NOT NULL default '', - `preStatus` varchar(30) NOT NULL DEFAULT '', + `prevStatus` varchar(30) NOT NULL DEFAULT '', `public` enum('0','1') NOT NULL DEFAULT '0', `notify` enum('0','1') NOT NULL DEFAULT '0', `notifyEmail` varchar(100) NOT NULL DEFAULT '', From 5ef05b2e277e67663de62ec7a10b7551d42d7920 Mon Sep 17 00:00:00 2001 From: tianshujie Date: Wed, 19 Feb 2025 08:41:04 +0800 Subject: [PATCH 04/53] * [task#137483,done,0.1h] skip prevStatus field. --- module/common/model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/common/model.php b/module/common/model.php index 8fe630d714..d015297174 100644 --- a/module/common/model.php +++ b/module/common/model.php @@ -911,7 +911,7 @@ class commonModel extends model if(is_object($value) || is_array($value)) continue; $check = strtolower($key); - if(in_array($check, array('lastediteddate', 'lasteditedby', 'assigneddate', 'editedby', 'editeddate', 'editingdate', 'uid'))) continue; + if(in_array($check, array('lastediteddate', 'lasteditedby', 'assigneddate', 'editedby', 'editeddate', 'editingdate', 'uid', 'prevstatus'))) continue; if(in_array($check, array('finisheddate', 'canceleddate', 'hangupeddate', 'lastcheckeddate', 'activateddate', 'closeddate', 'actualcloseddate')) && $value == '') continue; if(isset($old->$key) && !is_object($old->$key) && !is_array($old->$key)) From 48616ca697105fcc9209ca29363020c6f9c87bb3 Mon Sep 17 00:00:00 2001 From: tianshujie Date: Thu, 20 Feb 2025 10:45:27 +0800 Subject: [PATCH 05/53] * [task#137481,137482,doing,0.5h] change feedback status to processing when activate bugs. --- module/bug/model.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/module/bug/model.php b/module/bug/model.php index 3660560b36..66710fe602 100644 --- a/module/bug/model.php +++ b/module/bug/model.php @@ -608,6 +608,8 @@ class bugModel extends model if(isset($kanbanParams['toColID'])) $this->kanban->moveCard($bug->id, $kanbanParams['fromColID'], $kanbanParams['toColID'], $kanbanParams['fromLaneID'], $kanbanParams['toLaneID']); } + if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status); + $changes = common::createChanges($oldBug, $bug); $files = $this->loadModel('file')->saveUpload('bug', $bug->id); if($changes || $files) From b3ba5d55bf0f97e75432d169cbd5e7d4d6045adb Mon Sep 17 00:00:00 2001 From: tianshujie Date: Thu, 20 Feb 2025 10:45:46 +0800 Subject: [PATCH 06/53] * [task#137481,137482,doing,0.5h] change feedback status to processing when activate stories. --- module/story/model.php | 1 + 1 file changed, 1 insertion(+) diff --git a/module/story/model.php b/module/story/model.php index 6cda234f7e..a7e9dbdf33 100644 --- a/module/story/model.php +++ b/module/story/model.php @@ -2198,6 +2198,7 @@ class storyModel extends model $changes = common::createChanges($oldStory, $story); if(!empty($oldStory->twins)) $this->syncTwins($storyID, $oldStory->twins, $changes, 'Activated'); + if($this->config->edition != 'open' && $oldStory->feedback) $this->loadModel('feedback')->updateStatus('story', $oldStory->feedback, $story->status, $oldStory->status); if($this->config->edition == 'ipd' and $oldStory->demand) { From 112d638fbadf5d7d27769895caff4a9088f86c00 Mon Sep 17 00:00:00 2001 From: tianshujie Date: Thu, 20 Feb 2025 10:45:58 +0800 Subject: [PATCH 07/53] * [task#137481,137482,doing,0.5h] change feedback status to processing when activate tasks. --- module/task/model.php | 1 + 1 file changed, 1 insertion(+) diff --git a/module/task/model.php b/module/task/model.php index 4e9ef61a40..f409a10abc 100755 --- a/module/task/model.php +++ b/module/task/model.php @@ -63,6 +63,7 @@ class taskModel extends model if($oldTask->parent > 0) $this->updateParentStatus($taskID); if($oldTask->story) $this->loadModel('story')->setStage($oldTask->story); + if($this->config->edition != 'open' && $oldTask->feedback) $this->loadModel('feedback')->updateStatus('task', $oldTask->feedback, $task->status, $oldTask->status); $this->updateKanbanCell($taskID, $drag, $oldTask->execution); From d62602720f18e68cb2d3e0d8b8a292aaca891703 Mon Sep 17 00:00:00 2001 From: tianshujie Date: Thu, 20 Feb 2025 10:46:08 +0800 Subject: [PATCH 08/53] * [task#137481,137482,doing,0.5h] change feedback status to processing when activate todoes. --- module/todo/model.php | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/module/todo/model.php b/module/todo/model.php index 91e0c9e134..83c4de7ddf 100755 --- a/module/todo/model.php +++ b/module/todo/model.php @@ -88,14 +88,21 @@ class todoModel extends model { if(empty($todos)) return $todos; - $allChanges = array(); + $this->loadModel('action'); + if($this->config->edition != 'open') $this->loadModel('feedback'); /* Initialize todos from the post data. */ - $oldTodos = $this->getTodosByIdList($todoIdList); + $oldTodos = $this->getTodosByIdList($todoIdList); + $allChanges = array(); foreach($todos as $todoID => $todo) { $oldTodo = $oldTodos[$todoID]; - if(in_array($todo->type, $this->config->todo->moduleList)) $oldTodo->name = ''; + if(in_array($todo->type, $this->config->todo->moduleList)) + { + $table = $this->config->objectTables[$todo->type]; + $nameField = $this->config->action->objectNameFields[$todo->type]; + $todo->name = $this->dao->select($nameField)->from($table)->where('id')->eq($todo->objectID)->fetch($nameField); + } if(!empty($oldTodo->private) && !isset($todo->private)) $todo->assignedTo = $oldTodo->assignedTo; $this->todoTao->updateRow($todoID, $todo); @@ -106,7 +113,7 @@ class todoModel extends model if($this->config->edition != 'open' && $todo->type == 'feedback' && $todo->objectID && !isset($feedbacks[$todo->objectID])) { $feedbacks[$todo->objectID] = $todo->objectID; - $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, $todo->status); + $this->feedback->updateStatus('todo', $todo->objectID, $todo->status); } /* Create changes of one object. */ @@ -382,7 +389,12 @@ class todoModel extends model */ public function activate(int $todoID): bool { + $oldTodo = $this->fetchByID($todoID); $this->dao->update(TABLE_TODO)->set('status')->eq('wait')->where('id')->eq($todoID)->exec(); + + $todo = $this->fetchByID($todoID); + if($this->config->edition != 'open' && $todo->type == 'feedback' && $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, $todo->status); + $this->loadModel('action')->create('todo', $todoID, 'activated', '', 'wait'); return !dao::isError(); From 199f1ce9a87b46ac7a895081accd8901fae219bf Mon Sep 17 00:00:00 2001 From: tianshujie Date: Thu, 20 Feb 2025 15:16:42 +0800 Subject: [PATCH 09/53] * [task#137482,done,0.2h] add prevAssignedTo column for zt_feedback table. --- db/update21.5.sql | 1 + db/zentao.sql | 1 + 2 files changed, 2 insertions(+) diff --git a/db/update21.5.sql b/db/update21.5.sql index 15904b48d2..33ad3389be 100644 --- a/db/update21.5.sql +++ b/db/update21.5.sql @@ -1 +1,2 @@ ALTER TABLE `zt_feedback` ADD COLUMN `prevStatus` varchar(30) NOT NULL DEFAULT '' AFTER `subStatus`; +ALTER TABLE `zt_feedback` ADD COLUMN `prevAssignedTo` varchar(255) NOT NULL DEFAULT '' AFTER `assignedTo`; diff --git a/db/zentao.sql b/db/zentao.sql index d251f4354a..8720f12511 100755 --- a/db/zentao.sql +++ b/db/zentao.sql @@ -2648,6 +2648,7 @@ CREATE TABLE IF NOT EXISTS `zt_feedback` ( `editedBy` char(30) NOT NULL DEFAULT '', `editedDate` datetime NULL, `assignedTo` varchar(255) NOT NULL DEFAULT '', + `prevAssignedTo` varchar(255) NOT NULL DEFAULT '', `assignedDate` datetime NULL, `activatedBy` varchar(30) NOT NULL DEFAULT '', `activatedDate` datetime NULL, From a3a7c8ff6dfd467d9f98b441634203ed3f0a79ff Mon Sep 17 00:00:00 2001 From: tianshujie Date: Thu, 20 Feb 2025 15:31:22 +0800 Subject: [PATCH 10/53] * [task#137482,doing,0.1h] skip prevassignedto field when save action. --- module/common/model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/common/model.php b/module/common/model.php index d015297174..3d9facbb2f 100644 --- a/module/common/model.php +++ b/module/common/model.php @@ -911,7 +911,7 @@ class commonModel extends model if(is_object($value) || is_array($value)) continue; $check = strtolower($key); - if(in_array($check, array('lastediteddate', 'lasteditedby', 'assigneddate', 'editedby', 'editeddate', 'editingdate', 'uid', 'prevstatus'))) continue; + if(in_array($check, array('lastediteddate', 'lasteditedby', 'assigneddate', 'editedby', 'editeddate', 'editingdate', 'uid', 'prevstatus', 'prevassignedto'))) continue; if(in_array($check, array('finisheddate', 'canceleddate', 'hangupeddate', 'lastcheckeddate', 'activateddate', 'closeddate', 'actualcloseddate')) && $value == '') continue; if(isset($old->$key) && !is_object($old->$key) && !is_array($old->$key)) From 3b7d4c96213e389790d3ae3c7604a5fc663ce023 Mon Sep 17 00:00:00 2001 From: tianshujie Date: Fri, 21 Feb 2025 09:58:39 +0800 Subject: [PATCH 11/53] * [task#137480,doing,0.1h] skip feedback status change to commenting action. --- module/action/config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/action/config.php b/module/action/config.php index 6930d1d6bd..4c05223d10 100755 --- a/module/action/config.php +++ b/module/action/config.php @@ -89,7 +89,7 @@ $config->action->needGetProjectType = 'build,task,bug,case,testcase,caseli $config->action->needGetRelateField = ',branch,story,epic,requirement,productplan,release,task,build,bug,testcase,case,testtask,testreport,doc,doclib,issue,risk,opportunity,trainplan,gapanalysis,team,whitelist,researchplan,researchreport,meeting,kanbanlane,kanbancolumn,module,review,'; $config->action->noLinkModules = ',doclib,module,webhook,gitlab,instance,gitea,gogs,sonarqube,pipeline,jenkins,kanban,kanbanspace,kanbancolumn,kanbanlane,kanbanregion,kanbancard,execution,project,traincategory,apistruct,program,product,user,entry,repo,pivot,scene,boardspace,auditplan,auditresult,productline,'; $config->action->ignoreObjectType4Dynamic = 'kanbanregion,kanbanlane,kanbancolumn'; -$config->action->ignoreActions4Dynamic = 'disconnectxuanxuan,reconnectxuanxuan,loginxuanxuan,logoutxuanxuan,editmr,removemr'; +$config->action->ignoreActions4Dynamic = 'disconnectxuanxuan,reconnectxuanxuan,loginxuanxuan,logoutxuanxuan,editmr,removemr,syncdoingbyticket,syncdoingbystory,syncdoingbyuserstory,syncdoingbyepic,syncdoingbytask,syncdoingbybug,syncdoingbytodo,syncdoingbydemand'; $config->action->latestDateList = array('today', 'yesterday', 'thisWeek', 'lastWeek', 'thisMonth'); From 5b80e0b219f825fd4161b39f0970fc5f538aa169 Mon Sep 17 00:00:00 2001 From: tianshujie Date: Mon, 24 Feb 2025 11:02:38 +0800 Subject: [PATCH 12/53] * [task#137480,doing,2h] log feedback actions. --- module/bug/model.php | 6 +++--- module/bug/zen.php | 6 +++--- module/story/control.php | 12 ------------ module/story/model.php | 33 +++++++++++++++++++++------------ module/story/tao.php | 4 ++-- module/task/control.php | 3 +++ module/task/model.php | 15 ++++++--------- module/todo/model.php | 10 +++++----- 8 files changed, 43 insertions(+), 46 deletions(-) diff --git a/module/bug/model.php b/module/bug/model.php index 66710fe602..554fc62cc6 100644 --- a/module/bug/model.php +++ b/module/bug/model.php @@ -510,7 +510,7 @@ class bugModel extends model if($changes) $this->action->logHistory($actionID, $changes); /* If the edition is not pms, update feedback. */ - if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status); + if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status, $oldBug->id); return !dao::isError(); } @@ -608,7 +608,7 @@ class bugModel extends model if(isset($kanbanParams['toColID'])) $this->kanban->moveCard($bug->id, $kanbanParams['fromColID'], $kanbanParams['toColID'], $kanbanParams['fromLaneID'], $kanbanParams['toLaneID']); } - if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status); + if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status, $oldBug->id); $changes = common::createChanges($oldBug, $bug); $files = $this->loadModel('file')->saveUpload('bug', $bug->id); @@ -639,7 +639,7 @@ class bugModel extends model $this->dao->update(TABLE_BUG)->data($bug, 'comment')->autoCheck()->checkFlow()->where('id')->eq($bug->id)->exec(); if(dao::isError()) return false; - if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status); + if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status, $oldBug->id); $changes = common::createChanges($oldBug, $bug); $actionID = $this->loadModel('action')->create('bug', $bug->id, 'Closed', $this->post->comment); diff --git a/module/bug/zen.php b/module/bug/zen.php index 5950719d54..be78118fc2 100644 --- a/module/bug/zen.php +++ b/module/bug/zen.php @@ -2012,7 +2012,7 @@ class bugZen extends bug if($this->config->edition != 'open') { $todo = $this->dao->select('type, objectID')->from(TABLE_TODO)->where('id')->eq($todoID)->fetch(); - if($todo->type == 'feedback' && $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, 'done'); + if($todo->type == 'feedback' && $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, 'done', '', $todoID); } return !dao::isError(); @@ -2106,7 +2106,7 @@ class bugZen extends bug /* 更新反馈的状态。*/ /* Update the status of feedback. */ - if(($this->config->edition != 'open') && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status); + if(($this->config->edition != 'open') && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status, $oldBug->id); return !dao::isError(); } @@ -2345,7 +2345,7 @@ class bugZen extends bug /* 更新相关反馈的状态。*/ /* Update status of related feedback. */ - if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status); + if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status, $oldBug->id); } /** diff --git a/module/story/control.php b/module/story/control.php index 2c647837d4..c770807256 100755 --- a/module/story/control.php +++ b/module/story/control.php @@ -909,18 +909,6 @@ class story extends control $changes = $this->story->close($storyID, $postData); if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); - if($changes) - { - $preStatus = $story->status; - $isChanged = !empty($story->changedBy) ? true : false; - if($preStatus == 'reviewing') $preStatus = $isChanged ? 'changing' : 'draft'; - - $actionID = $this->action->create('story', $storyID, 'Closed', $this->post->comment, ucfirst($this->post->closedReason) . ($this->post->duplicateStory ? ':' . (int)$this->post->duplicateStory : '') . "|$preStatus"); - $this->action->logHistory($actionID, $changes); - } - - $this->dao->update(TABLE_STORY)->set('assignedTo')->eq('closed')->where('id')->eq((int)$storyID)->exec(); - $this->executeHooks($storyID); if(isInModal()) diff --git a/module/story/model.php b/module/story/model.php index a7e9dbdf33..f8b0a2f4bc 100644 --- a/module/story/model.php +++ b/module/story/model.php @@ -590,7 +590,7 @@ class storyModel extends model if($this->config->edition != 'open') { $todo = $this->dao->select('type, objectID')->from(TABLE_TODO)->where('id')->eq($todoID)->fetch(); - if($todo->type == 'feedback' && $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, 'done'); + if($todo->type == 'feedback' && $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, 'done', '', $todoID); } } @@ -906,7 +906,7 @@ class storyModel extends model } unset($oldStory->parent, $story->parent); - if($this->config->edition != 'open' && $oldStory->feedback) $this->loadModel('feedback')->updateStatus('story', $oldStory->feedback, $story->status, $oldStory->status); + if($this->config->edition != 'open' && $oldStory->feedback) $this->loadModel('feedback')->updateStatus('story', $oldStory->feedback, $story->status, $oldStory->status, $storyID); if(!empty($story->reviewer)) { @@ -1035,7 +1035,7 @@ class storyModel extends model if($preStatus == 'reviewing') $preStatus = $isChanged ? 'changing' : 'draft'; } - if($this->config->edition != 'open' && $oldParentStory->feedback) $this->loadModel('feedback')->updateStatus('story', $oldParentStory->feedback, $newParentStory->status, $oldParentStory->status); + if($this->config->edition != 'open' && $oldParentStory->feedback) $this->loadModel('feedback')->updateStatus('story', $oldParentStory->feedback, $newParentStory->status, $oldParentStory->status, $oldParentStory->id); } else { @@ -1185,7 +1185,7 @@ class storyModel extends model if($this->config->edition != 'open' && $oldStory->feedback && !isset($feedbacks[$oldStory->feedback])) { $feedbacks[$oldStory->feedback] = $oldStory->feedback; - $this->loadModel('feedback')->updateStatus('story', $oldStory->feedback, $story->status, $oldStory->status); + $this->loadModel('feedback')->updateStatus('story', $oldStory->feedback, $story->status, $oldStory->status, $storyID); } } @@ -1538,16 +1538,25 @@ class storyModel extends model ->exec(); if(dao::isError()) return false; - if(!dao::isError()) + $changes = common::createChanges($oldStory, $story); + if($changes) { - if($oldStory->isParent == '1') $this->closeAllChildren($storyID, $story->closedReason); - $this->setStage($storyID); - $this->loadModel('score')->create('story', 'close', $storyID); + $preStatus = $story->status; + $isChanged = !empty($story->changedBy) ? true : false; + if($preStatus == 'reviewing') $preStatus = $isChanged ? 'changing' : 'draft'; - if($this->config->edition != 'open' && $oldStory->feedback) $this->loadModel('feedback')->updateStatus('story', $oldStory->feedback, $story->status, $oldStory->status); + $actionID = $this->loadModel('action')->create('story', $storyID, 'Closed', $this->post->comment, ucfirst($this->post->closedReason) . ($this->post->duplicateStory ? ':' . (int)$this->post->duplicateStory : '') . "|$preStatus"); + $this->action->logHistory($actionID, $changes); } - $changes = common::createChanges($oldStory, $story); + $this->dao->update(TABLE_STORY)->set('assignedTo')->eq('closed')->where('id')->eq((int)$storyID)->exec(); + + if($oldStory->isParent == '1') $this->closeAllChildren($storyID, $story->closedReason); + $this->setStage($storyID); + $this->loadModel('score')->create('story', 'close', $storyID); + + if($this->config->edition != 'open' && $oldStory->feedback) $this->loadModel('feedback')->updateStatus('story', $oldStory->feedback, $story->status, $oldStory->status, $storyID); + if(!empty($postData->closeSync)) { $this->relieveTwins($oldStory->product, $storyID); @@ -1607,7 +1616,7 @@ class storyModel extends model if($this->config->edition != 'open' && $oldStory->feedback && !isset($feedbacks[$oldStory->feedback])) { $feedbacks[$oldStory->feedback] = $oldStory->feedback; - $this->loadModel('feedback')->updateStatus('story', $oldStory->feedback, $story->status, $oldStory->status); + $this->loadModel('feedback')->updateStatus('story', $oldStory->feedback, $story->status, $oldStory->status, $storyID); } $this->loadModel('score')->create('story', 'close', $storyID); @@ -2198,7 +2207,7 @@ class storyModel extends model $changes = common::createChanges($oldStory, $story); if(!empty($oldStory->twins)) $this->syncTwins($storyID, $oldStory->twins, $changes, 'Activated'); - if($this->config->edition != 'open' && $oldStory->feedback) $this->loadModel('feedback')->updateStatus('story', $oldStory->feedback, $story->status, $oldStory->status); + if($this->config->edition != 'open' && $oldStory->feedback) $this->loadModel('feedback')->updateStatus('story', $oldStory->feedback, $story->status, $oldStory->status, $storyID); if($this->config->edition == 'ipd' and $oldStory->demand) { diff --git a/module/story/tao.php b/module/story/tao.php index fb16e1f244..7f788e3284 100644 --- a/module/story/tao.php +++ b/module/story/tao.php @@ -985,7 +985,7 @@ class storyTao extends storyModel if(empty($bugID) or empty($storyID)) return; $oldBug = $this->dao->select('*')->from(TABLE_BUG)->where('id')->eq($bugID)->fetch(); - if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, 'closed', $oldBug->status); + if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, 'closed', $oldBug->status, $bugID); $now = helper::now(); $bug = new stdclass(); @@ -1048,7 +1048,7 @@ class storyTao extends storyModel if($this->config->edition == 'open')return; $todo = $this->dao->select('type, objectID')->from(TABLE_TODO)->where('id')->eq($todoID)->fetch(); - if($todo->type == 'feedback' && $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, 'done'); + if($todo->type == 'feedback' && $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, 'done', '', $todoID); } /** diff --git a/module/task/control.php b/module/task/control.php index f6ce6c0e8b..fc4a796c71 100755 --- a/module/task/control.php +++ b/module/task/control.php @@ -636,6 +636,9 @@ class task extends control $result = $this->task->afterStart($task, $changes, 0, $output); if(is_array($result)) return $this->send($result); + if($taskData->status == 'done') $this->loadModel('score')->create('task', 'finish', $task->id); + if($this->config->edition != 'open' && $task->feedback) $this->loadModel('feedback')->updateStatus('task', $task->feedback, $taskData->status, $task->status, $task->id); + /* Get the information returned after a task is started. */ $from = zget($output, 'from'); $response = $this->taskZen->responseAfterChangeStatus($task, $from); diff --git a/module/task/model.php b/module/task/model.php index f409a10abc..5ab71841d4 100755 --- a/module/task/model.php +++ b/module/task/model.php @@ -63,7 +63,7 @@ class taskModel extends model if($oldTask->parent > 0) $this->updateParentStatus($taskID); if($oldTask->story) $this->loadModel('story')->setStage($oldTask->story); - if($this->config->edition != 'open' && $oldTask->feedback) $this->loadModel('feedback')->updateStatus('task', $oldTask->feedback, $task->status, $oldTask->status); + if($this->config->edition != 'open' && $oldTask->feedback) $this->loadModel('feedback')->updateStatus('task', $oldTask->feedback, $task->status, $oldTask->status, $taskID); $this->updateKanbanCell($taskID, $drag, $oldTask->execution); @@ -172,7 +172,7 @@ class taskModel extends model if($this->config->edition != 'open' && $oldTask->feedback && !isset($feedbacks[$oldTask->feedback])) { $feedbacks[$oldTask->feedback] = $oldTask->feedback; - $this->feedback->updateStatus('task', $oldTask->feedback, $task->status, $oldTask->status); + $this->feedback->updateStatus('task', $oldTask->feedback, $task->status, $oldTask->status, $taskID); } if(!empty($task->story) && !empty($task->isParent) && $this->post->syncChildren) $this->syncStoryToChildren($task); @@ -252,7 +252,7 @@ class taskModel extends model /* If the todo comes from a feedback, update the feedback information. */ $todo = $this->dao->findByID($todoID)->from(TABLE_TODO)->fetch(); - if($this->config->edition != 'open' && $todo->type == 'feedback' && $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, 'done'); + if($this->config->edition != 'open' && $todo->type == 'feedback' && $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, 'done', '', $todoID); } /* If the task comes from a design, update the task information. */ @@ -402,7 +402,7 @@ class taskModel extends model if(!empty($changes)) $this->action->logHistory($actionID, $changes); } - if($this->config->edition != 'open' && $oldTask->feedback) $this->loadModel('feedback')->updateStatus('task', $oldTask->feedback, $task->status, $oldTask->status); + if($this->config->edition != 'open' && $oldTask->feedback) $this->loadModel('feedback')->updateStatus('task', $oldTask->feedback, $task->status, $oldTask->status, $oldTask->id); if(!empty($oldTask->mode) && empty($task->mode)) $this->dao->delete()->from(TABLE_TASKTEAM)->where('task')->eq($task->id)->exec(); if(!empty($task->story) && $this->post->syncChildren) $this->syncStoryToChildren($task); @@ -1263,9 +1263,6 @@ class taskModel extends model if(dao::isError()) return false; if($task->consumed != $oldTask->consumed || $task->left != $oldTask->left) $this->loadModel('program')->refreshProjectStats($oldTask->project); - - if($task->status == 'done') $this->loadModel('score')->create('task', 'finish', $oldTask->id); - if($this->config->edition != 'open' && $oldTask->feedback) $this->loadModel('feedback')->updateStatus('task', $oldTask->feedback, $task->status, $oldTask->status); return common::createChanges($oldTask, $task); } @@ -3417,7 +3414,7 @@ class taskModel extends model /* Create action record. */ $this->taskTao->createAutoUpdateTaskAction($parentTask, 'child'); - if($this->config->edition != 'open' && $parentTask->feedback) $this->loadModel('feedback')->updateStatus('task', $parentTask->feedback, $status, $parentTask->status); + if($this->config->edition != 'open' && $parentTask->feedback) $this->loadModel('feedback')->updateStatus('task', $parentTask->feedback, $status, $parentTask->status, $parentID); } } @@ -3461,7 +3458,7 @@ class taskModel extends model /* Create action record. */ $this->taskTao->createAutoUpdateTaskAction($childTask, 'parent'); - if($this->config->edition != 'open' && $childTask->feedback) $this->loadModel('feedback')->updateStatus('task', $childTask->feedback, $status, $childTask->status); + if($this->config->edition != 'open' && $childTask->feedback) $this->loadModel('feedback')->updateStatus('task', $childTask->feedback, $status, $childTask->status, $childID); } } diff --git a/module/todo/model.php b/module/todo/model.php index 83c4de7ddf..de3c969745 100755 --- a/module/todo/model.php +++ b/module/todo/model.php @@ -71,7 +71,7 @@ class todoModel extends model if(!empty($todo->uid)) $this->loadModel('file')->updateObjectID($todo->uid, $todoID, 'todo'); if(!empty($oldTodo->cycle)) $this->createByCycle(array($todoID => $todo)); - if($this->config->edition != 'open' && $todo->type == 'feedback' && $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, $todo->status); + if($this->config->edition != 'open' && $todo->type == 'feedback' && $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, $todo->status, '', $todoID); return common::createChanges($oldTodo, (array)$todo); } @@ -113,7 +113,7 @@ class todoModel extends model if($this->config->edition != 'open' && $todo->type == 'feedback' && $todo->objectID && !isset($feedbacks[$todo->objectID])) { $feedbacks[$todo->objectID] = $todo->objectID; - $this->feedback->updateStatus('todo', $todo->objectID, $todo->status); + $this->feedback->updateStatus('todo', $todo->objectID, $todo->status, '', $todoID); } /* Create changes of one object. */ @@ -169,7 +169,7 @@ class todoModel extends model { $todo = $this->todoTao->fetch($todoID); $feedbackID = $todo->objectID ? $todo->objectID : '' ; - if($feedbackID) $this->loadModel('feedback')->updateStatus('todo', $feedbackID, 'done'); + if($feedbackID) $this->loadModel('feedback')->updateStatus('todo', $feedbackID, 'done', '', $todoID); } return true; } @@ -393,7 +393,7 @@ class todoModel extends model $this->dao->update(TABLE_TODO)->set('status')->eq('wait')->where('id')->eq($todoID)->exec(); $todo = $this->fetchByID($todoID); - if($this->config->edition != 'open' && $todo->type == 'feedback' && $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, $todo->status); + if($this->config->edition != 'open' && $todo->type == 'feedback' && $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, $todo->status, '', $todoID); $this->loadModel('action')->create('todo', $todoID, 'activated', '', 'wait'); @@ -420,7 +420,7 @@ class todoModel extends model if($this->config->edition != 'open') { $feedbackID = $this->dao->select('objectID')->from(TABLE_TODO)->where('id')->eq($todoID)->andWhere('type')->eq('feedback')->fetch('objectID'); - if($feedbackID) $this->loadModel('feedback')->updateStatus('todo', $feedbackID, 'closed'); + if($feedbackID) $this->loadModel('feedback')->updateStatus('todo', $feedbackID, 'closed', '', $todoID); } return true; } From 9c8104ddd3844e4c780a03709b98b00b3396e344 Mon Sep 17 00:00:00 2001 From: tianshujie Date: Thu, 27 Feb 2025 13:41:21 +0800 Subject: [PATCH 13/53] * [feedback#8552,doing,0.2h] add review tip. --- module/common/lang/de.php | 1 + module/common/lang/en.php | 1 + module/common/lang/fr.php | 1 + module/common/lang/zh-cn.php | 1 + 4 files changed, 4 insertions(+) diff --git a/module/common/lang/de.php b/module/common/lang/de.php index 33736c19aa..46ff6aea75 100644 --- a/module/common/lang/de.php +++ b/module/common/lang/de.php @@ -147,6 +147,7 @@ $lang->levelExceeded = 'The level has exceeded the display range. For more inf $lang->noticeOkFile = 'Aus Sicherheitsgrü nden muss Ihr Adminkonto bestätigt werden. \n Bitte melden Sie sich an und erstellen Sie die Datei %s File.\n Hinweis:\n 1. Die Datei ist leer.\n 2. Wenn die Datei bereits existiert, löschen Sie sie und erstellen Sie eine neue Datei.'; $lang->noticeDrag = 'Click to add or drag to upload, no more than %s'; $lang->allProgress = 'All Progress'; +$lang->hasReviewed = 'The content has already been reviewed and does not need further review !'; $lang->fieldDisplaySetting = 'Field Display Setting'; $lang->fieldSettingTip = 'The following fields are collapsed by default, click on the "Show more fields" button to expand the view, if you want some fields not to be collapsed by default, you can fix it with a pin.'; diff --git a/module/common/lang/en.php b/module/common/lang/en.php index 74e0dbed5d..d15451b81a 100644 --- a/module/common/lang/en.php +++ b/module/common/lang/en.php @@ -147,6 +147,7 @@ $lang->levelExceeded = 'The level has exceeded the display range. For more inf $lang->noticeOkFile = 'For security reasons, your Admin account has to be confirmed. \n Please login ZenTao server and create %s File.\n Note:\n 1. File is blank.\n 2. If the file existed, delete it and then create a new one.'; $lang->noticeDrag = 'Click to add or drag to upload, no more than %s'; $lang->allProgress = 'All Progress'; +$lang->hasReviewed = 'The content has already been reviewed and does not need further review !'; $lang->fieldDisplaySetting = 'Field Display Setting'; $lang->fieldSettingTip = 'The following fields are collapsed by default, click on the "Show more fields" button to expand the view, if you want some fields not to be collapsed by default, you can fix it with a pin.'; diff --git a/module/common/lang/fr.php b/module/common/lang/fr.php index 4ed4577192..1a85fd6b83 100644 --- a/module/common/lang/fr.php +++ b/module/common/lang/fr.php @@ -147,6 +147,7 @@ $lang->levelExceeded = 'The level has exceeded the display range. For more inf $lang->noticeOkFile = 'For security reasons, your Admin account has to be confirmed. \n Please login ZenTao server and create %s File.\n Note:\n 1. File is blank.\n 2. If the file existed, delete it and then create a new one.'; $lang->noticeDrag = 'Click to add or drag to upload, no more than %s'; $lang->allProgress = 'All Progress'; +$lang->hasReviewed = 'The content has already been reviewed and does not need further review !'; $lang->fieldDisplaySetting = 'Field Display Setting'; $lang->fieldSettingTip = 'The following fields are collapsed by default, click on the "Show more fields" button to expand the view, if you want some fields not to be collapsed by default, you can fix it with a pin.'; diff --git a/module/common/lang/zh-cn.php b/module/common/lang/zh-cn.php index cd2b6b8140..22bd919f27 100644 --- a/module/common/lang/zh-cn.php +++ b/module/common/lang/zh-cn.php @@ -147,6 +147,7 @@ $lang->levelExceeded = '层级已超过显示范围,更多信息请前往网 $lang->noticeOkFile = '为了安全起见,系统需要确认您的管理员身份。\n 请登录禅道所在的服务器,创建%s文件。\n 注意:\n 1. 文件内容为空。\n 2. 如果之前文件存在,删除之后重新创建。'; $lang->noticeDrag = '可点击添加或拖拽上传, 不超过 %s'; $lang->allProgress = '总进度'; +$lang->hasReviewed = '该内容已经评审过,无需重复评审!'; $lang->fieldDisplaySetting = '字段显示设置'; $lang->fieldSettingTip = '以下字段默认收起,点击「显示更多字段」按钮展开查看,若您希望某些字段默认不收起,通过图钉固定即可。'; From 7538b627fc8f10aecf154f2e43c655bd583d2e5d Mon Sep 17 00:00:00 2001 From: tianshujie Date: Thu, 27 Feb 2025 13:41:39 +0800 Subject: [PATCH 14/53] * [feedback#8552,doing,0.5h] add has reviewed tip for story, testcase and project. --- module/my/js/audit.ui.js | 17 ++++++++++++++++- module/my/ui/audit.html.php | 1 + module/story/control.php | 7 ++++++- module/testcase/control.php | 10 ++++++++-- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/module/my/js/audit.ui.js b/module/my/js/audit.ui.js index e21b0e67b2..616e367066 100644 --- a/module/my/js/audit.ui.js +++ b/module/my/js/audit.ui.js @@ -5,7 +5,7 @@ window.onRenderCell = function(result, {row, col}) if(row.data.module == 'review') { result[0].props.items[0]['disabled'] = projectPriv ? false: true; - result[0].props.items[0]['url'] = projectReviewLink.replace('{id}', row.data.id); + result[0].props.items[0]['url'] = 'javascript:checkReview("' + row.data.id + '")'; delete result[0].props.items[0]['data-toggle']; } else if(reviewPrivs[row.data.module]) @@ -49,3 +49,18 @@ window.onRenderCell = function(result, {row, col}) } return result; } + +window.checkReview = function(id) +{ + $.get($.createLink('review', 'ajaxCheckReviewInfo', 'reviewID=' + id), function(data) + { + if(!data) + { + zui.Modal.alert({icon: 'icon-exclamation-sign', iconClass: 'warning-pale rounded-full icon-2x', message: hasReviewedTip}).then((res) => {loadCurrentPage()}); + } + else + { + openPage(projectReviewLink.replace('{id}', id), 'project'); + } + }) +} diff --git a/module/my/ui/audit.html.php b/module/my/ui/audit.html.php index f666f17185..fe472e84da 100644 --- a/module/my/ui/audit.html.php +++ b/module/my/ui/audit.html.php @@ -41,6 +41,7 @@ jsVar('noFlowAuditModules', $config->my->noFlowAuditModules); jsVar('projectPriv', hasPriv('review', 'assess')); jsVar('projectReviewLink', createLink('review', 'assess', 'reviewID={id}')); jsVar('vision', $config->vision); +jsVar('hasReviewedTip', $lang->hasReviewed); $rawMethod = $app->rawMethod; if($rawMethod != 'audit' && isset($lang->my->featureBar[$rawMethod]['audit'])) $lang->my->featureBar[$rawMethod] = $lang->my->featureBar[$rawMethod]['audit']; diff --git a/module/story/control.php b/module/story/control.php index c770807256..2cd0273212 100755 --- a/module/story/control.php +++ b/module/story/control.php @@ -731,7 +731,12 @@ class story extends control } $this->commonAction($storyID); - $story = $this->view->story; + $story = $this->view->story; + if($story->status != 'reviewing') + { + return $this->send(array('result' => 'fail', 'callback' => "zui.Modal.alert({icon: 'icon-exclamation-sign', iconClass: 'warning-pale rounded-full icon-2x', message: '{$this->lang->hasReviewed}'}).then((res) => {loadCurrentPage()});")); + } + $reviewers = $this->story->getReviewerPairs($storyID, $story->version); $this->story->getAffectedScope($story); diff --git a/module/testcase/control.php b/module/testcase/control.php index 8c6e873102..0884bea86c 100644 --- a/module/testcase/control.php +++ b/module/testcase/control.php @@ -691,10 +691,10 @@ class testcase extends control */ public function review(int $caseID) { + $oldCase = $this->testcase->getByID($caseID); if($_POST) { - $oldCase = $this->testcase->getByID($caseID); - $case = $this->testcaseZen->prepareReviewData($caseID, $oldCase); + $case = $this->testcaseZen->prepareReviewData($caseID, $oldCase); if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); $this->testcase->review($case, $oldCase); @@ -706,6 +706,12 @@ class testcase extends control return $this->send(array('result' => 'success', 'message' => $message, 'closeModal' => true, 'load' => true)); } + if((isset($oldCase->caseStatus) && $oldCase->caseStatus != 'wait') || $oldCase->status != 'wait') + { + return $this->send(array('result' => 'fail', 'callback' => "zui.Modal.alert({icon: 'icon-exclamation-sign', iconClass: 'warning-pale rounded-full icon-2x', message: '{$this->lang->hasReviewed}'}).then((res) => {loadCurrentPage()});")); + } + + $this->view->title = $this->lang->testcase->review; $this->view->users = $this->user->getPairs('noletter|noclosed|nodeleted'); $this->view->testcaseID = $caseID; From ceb93dc2d496d92250350862d38d07b0dd145d61 Mon Sep 17 00:00:00 2001 From: tianshujie Date: Wed, 5 Mar 2025 10:50:00 +0800 Subject: [PATCH 15/53] * [feedback#f8552,done,0.5h] fix review tip error. --- module/story/control.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/story/control.php b/module/story/control.php index 2cd0273212..601315635a 100755 --- a/module/story/control.php +++ b/module/story/control.php @@ -732,7 +732,7 @@ class story extends control $this->commonAction($storyID); $story = $this->view->story; - if($story->status != 'reviewing') + if(isset($reviewers[$this->app->user->account]) && $reviewers[$this->app->user->account] != '') { return $this->send(array('result' => 'fail', 'callback' => "zui.Modal.alert({icon: 'icon-exclamation-sign', iconClass: 'warning-pale rounded-full icon-2x', message: '{$this->lang->hasReviewed}'}).then((res) => {loadCurrentPage()});")); } From 7b9bd8174409421c9b56cefe6c6e9215a53077ea Mon Sep 17 00:00:00 2001 From: tianshujie Date: Wed, 5 Mar 2025 10:52:47 +0800 Subject: [PATCH 16/53] * [feedback#f8552,done,0.5h] fix review tip error. --- module/story/control.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/story/control.php b/module/story/control.php index 601315635a..b09875d9a4 100755 --- a/module/story/control.php +++ b/module/story/control.php @@ -732,7 +732,7 @@ class story extends control $this->commonAction($storyID); $story = $this->view->story; - if(isset($reviewers[$this->app->user->account]) && $reviewers[$this->app->user->account] != '') + if(!empty($reviewers[$this->app->user->account])) { return $this->send(array('result' => 'fail', 'callback' => "zui.Modal.alert({icon: 'icon-exclamation-sign', iconClass: 'warning-pale rounded-full icon-2x', message: '{$this->lang->hasReviewed}'}).then((res) => {loadCurrentPage()});")); } From 45861defdb491d6a7a75e70157a7c7c253eb27ff Mon Sep 17 00:00:00 2001 From: wangyuting Date: Fri, 7 Mar 2025 15:25:01 +0800 Subject: [PATCH 17/53] * Merge import jira code. --- config/cache.php | 10 +- config/filter.php | 5 + db/zentao.sql | 4 +- module/admin/lang/menu.php | 2 +- module/admin/model.php | 4 +- module/convert/config.php | 144 +- module/convert/control.php | 247 ++- module/convert/js/importjira.ui.js | 38 +- module/convert/lang/de.php | 181 +- module/convert/lang/en.php | 67 +- module/convert/lang/fr.php | 137 +- module/convert/lang/zh-cn.php | 67 +- module/convert/lang/zh-tw.php | 235 +- module/convert/model.php | 997 +++++++-- module/convert/tao.php | 2355 ++++++++++++++++----- module/convert/ui/convertjira.html.php | 52 - module/convert/ui/importjira.html.php | 43 +- module/convert/ui/importnotice.html.php | 80 - module/convert/ui/initjirauser.html.php | 130 +- module/convert/ui/mapjira2zentao.html.php | 272 ++- module/projectstory/ui/story.html.php | 1 - 21 files changed, 3789 insertions(+), 1282 deletions(-) delete mode 100644 module/convert/ui/convertjira.html.php delete mode 100644 module/convert/ui/importnotice.html.php diff --git a/config/cache.php b/config/cache.php index 26c2911df2..77d532ee32 100644 --- a/config/cache.php +++ b/config/cache.php @@ -1,11 +1,11 @@ cache = new stdclass(); -$config->cache->enable = false; // 是否开启缓存。Enable cache or not. -$config->cache->driver = 'apcu'; // 缓存驱动。 The driver of cache. Can be file|yac|apcu|redis. -$config->cache->scope = ''; // 缓存服务范围。The scope of cache. Can be private|shared. -$config->cache->namespace = ''; // 缓存命名空间。The namespace of cache. -$config->cache->lifetime = 0; // 缓存生存时间,默认永不过期。The lifetime of cache. Default is no expiration. +$config->cache->enable = getenv('ZT_CACHE_ENABLE') ?: false; // 是否开启缓存。Enable cache or not. +$config->cache->driver = getenv('ZT_CACHE_DRIVER') ?: 'apcu'; // 缓存驱动。 The driver of cache. Can be file|yac|apcu|redis. +$config->cache->scope = getenv('ZT_CACHE_SCOPE') ?: ''; // 缓存服务范围。The scope of cache. Can be private|shared. +$config->cache->namespace = getenv('ZT_CACHE_NAMESPACE') ?: ''; // 缓存命名空间。The namespace of cache. +$config->cache->lifetime = getenv('ZT_CACHE_LIFETIME') ?: 0; // 缓存生存时间,默认永不过期。The lifetime of cache. Default is no expiration. $config->cache->dao = new stdClass(); $config->cache->dao->enable = true; // 是否开启 DAO 缓存。Enable DAO cache or not. diff --git a/config/filter.php b/config/filter.php index 3e8155a937..6db2009769 100755 --- a/config/filter.php +++ b/config/filter.php @@ -78,6 +78,7 @@ $filter->productplan = new stdclass(); $filter->projectplan = new stdclass(); $filter->kanban = new stdclass(); $filter->group = new stdclass(); +$filter->convert = new stdclass(); $filter->index->index = new stdclass(); $filter->block->default = new stdclass(); @@ -203,6 +204,7 @@ $filter->group->default = new stdclass(); $filter->caselib->default = new stdclass(); $filter->requirement->batchchangeparent = new stdclass(); $filter->requirement->export = new stdclass(); +$filter->convert->mapjira2zentao = new stdclass(); $filter->index->index->get['open'] = 'reg::base64'; @@ -533,3 +535,6 @@ $filter->bug->ajaxgetproductcases->get['limit'] = 'int'; $filter->bug->ajaxgetproductbugs->get['search'] = 'reg::any'; $filter->bug->ajaxgetproductbugs->get['limit'] = 'int'; + +$filter->convert->mapjira2zentao->cookie['stepStatus'] = 'reg::any'; +$filter->convert->mapjira2zentao->cookie['jiraRelation'] = 'reg::any'; diff --git a/db/zentao.sql b/db/zentao.sql index 8720f12511..f973c297c5 100755 --- a/db/zentao.sql +++ b/db/zentao.sql @@ -868,6 +868,7 @@ CREATE TABLE IF NOT EXISTS `zt_doclib` ( `addedBy` varchar(30) NOT NULL DEFAULT '', `addedDate` datetime NULL, `deleted` enum('0','1') NOT NULL DEFAULT '0', + `archived` enum('0','1') NOT NULL DEFAULT '0', `orderBy` varchar(30) NOT NULL DEFAULT 'id_asc', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -13225,13 +13226,14 @@ CREATE TABLE IF NOT EXISTS `zt_workflowgroup` ( `type` varchar(10) NOT NULL DEFAULT '', `projectModel` varchar(10) NOT NULL DEFAULT '', `projectType` varchar(10) NOT NULL DEFAULT '', - `name` varchar(30) NOT NULL DEFAULT '', + `name` varchar(255) NOT NULL DEFAULT '', `code` varchar(30) NOT NULL DEFAULT '', `desc` text NULL, `disabledModules` varchar(255) NOT NULL DEFAULT '', `status` varchar(10) NOT NULL DEFAULT 'wait', `vision` varchar(10) NOT NULL DEFAULT 'rnd', `main` enum('0','1') NOT NULL DEFAULT '0', + `exclusive` enum('0','1') NOT NULL DEFAULT '0', `createdBy` varchar(30) NOT NULL DEFAULT '', `createdDate` datetime NULL, `editedBy` varchar(30) NOT NULL DEFAULT '', diff --git a/module/admin/lang/menu.php b/module/admin/lang/menu.php index 9e3ce41539..cef8ec25a5 100644 --- a/module/admin/lang/menu.php +++ b/module/admin/lang/menu.php @@ -41,7 +41,7 @@ $lang->admin->menuList->dev['order'] = 45; $lang->admin->menuList->convert['name'] = $lang->admin->menuSetting['convert']['name']; $lang->admin->menuList->convert['desc'] = $lang->admin->menuSetting['convert']['desc']; -$lang->admin->menuList->convert['link'] = 'convert|convertjira'; +$lang->admin->menuList->convert['link'] = 'convert|index|mode=restore'; $lang->admin->menuList->convert['order'] = 50; $lang->admin->menuList->system['subMenu']['mode'] = array('link' => "{$lang->custom->mode}|custom|mode|"); diff --git a/module/admin/model.php b/module/admin/model.php index e4b59a1945..a703f1e8ad 100755 --- a/module/admin/model.php +++ b/module/admin/model.php @@ -200,8 +200,8 @@ class adminModel extends model if(!empty($menu['link']) && strpos($menu['link'], '|') !== false) { - list($module, $method) = explode('|', $menu['link']); - $menu['link'] = helper::createLink($module, $method); + list($module, $method, $params) = explode('|', $menu['link'] . '|'); + $menu['link'] = helper::createLink($module, $method, $params); if(common::hasPriv($module, $method)) $menu['disabled'] = false; } diff --git a/module/convert/config.php b/module/convert/config.php index c27161a4b0..70a704f5d7 100644 --- a/module/convert/config.php +++ b/module/convert/config.php @@ -12,14 +12,144 @@ define('JIRA_BUILD', '`projectversion`'); define('JIRA_ACTION', '`jiraaction`'); define('JIRA_NODEASSOCIATION', '`nodeassociation`'); define('JIRA_FILE', '`fileattachment`'); +define('JIRA_PRIORITY', '`priority`'); define('JIRA_TMPRELATION', '`jiratmprelation`'); +define('JIRA_WORKFLOW', '`jiraworkflows`'); +define('JIRA_WORKFLOWSCHEME', '`workflowscheme`'); +define('JIRA_WORKLOG', '`worklog`'); +define('JIRA_AUDITLOG', '`audit_log`'); +define('JIRA_MEMBERSHIP', '`cwd_membership`'); +define('JIRA_PROJECTROLEACTOR', '`projectroleactor`'); +define('JIRA_CUSTOMFIELD', '`customfield`'); +define('JIRA_CUSTOMFIELDVALUE', '`customfieldvalue`'); +define('JIRA_CUSTOMFIELDOPTION', '`customfieldoption`'); +define('JIRA_OSPROPERTYENTRY', '`propertyentry`'); +define('JIRA_OPTIONCONFIGURATION', '`optionconfiguration`'); +define('JIRA_CONFIGURATIONCONTEXT', '`configurationcontext`'); +define('JIRA_FIELDSCREENLAYOUTITEM', '`fieldscreenlayoutitem`'); +define('JIRA_FIELDCONFIGSCHEME', '`fieldconfigscheme`'); +define('JIRA_FIELDCONFIGSCHEMEISSUETYPE', '`fieldconfigschemeissuetype`'); +define('CONFLUENCE_TMPRELATION', '`confluencetmprelation`'); $config->convert = new stdClass(); $config->convert->objectTables = array(); -$config->convert->objectTables['user'] = JIRA_USER; -$config->convert->objectTables['project'] = JIRA_PROJECT; -$config->convert->objectTables['issue'] = JIRA_ISSUE; -$config->convert->objectTables['build'] = JIRA_BUILD; -$config->convert->objectTables['issuelink'] = JIRA_ISSUELINK; -$config->convert->objectTables['action'] = JIRA_ACTION; -$config->convert->objectTables['file'] = JIRA_FILE; +$config->convert->objectTables['user'] = JIRA_USER; +$config->convert->objectTables['project'] = JIRA_PROJECT; +$config->convert->objectTables['issue'] = JIRA_ISSUE; +$config->convert->objectTables['build'] = JIRA_BUILD; +$config->convert->objectTables['issuelink'] = JIRA_ISSUELINK; +$config->convert->objectTables['issuetype'] = JIRA_ISSUETYPE; +$config->convert->objectTables['issuelinktype'] = JIRA_ISSUELINKTYPE; +$config->convert->objectTables['resolution'] = JIRA_RESOLUTION; +$config->convert->objectTables['nodeassociation'] = JIRA_NODEASSOCIATION; +$config->convert->objectTables['workflow'] = JIRA_WORKFLOW; +$config->convert->objectTables['workflowscheme'] = JIRA_WORKFLOWSCHEME; +$config->convert->objectTables['worklog'] = JIRA_WORKLOG; +$config->convert->objectTables['auditlog'] = JIRA_AUDITLOG; +$config->convert->objectTables['status'] = JIRA_ISSUESTATUS; +$config->convert->objectTables['action'] = JIRA_ACTION; +$config->convert->objectTables['file'] = JIRA_FILE; +$config->convert->objectTables['priority'] = JIRA_PRIORITY; +$config->convert->objectTables['membership'] = JIRA_MEMBERSHIP; +$config->convert->objectTables['projectroleactor'] = JIRA_PROJECTROLEACTOR; +$config->convert->objectTables['customfield'] = JIRA_CUSTOMFIELD; +$config->convert->objectTables['customfieldvalue'] = JIRA_CUSTOMFIELDVALUE; +$config->convert->objectTables['customfieldoption'] = JIRA_CUSTOMFIELDOPTION; +$config->convert->objectTables['ospropertyentry'] = JIRA_OSPROPERTYENTRY; +$config->convert->objectTables['optionconfiguration'] = JIRA_OPTIONCONFIGURATION; +$config->convert->objectTables['configurationcontext'] = JIRA_CONFIGURATIONCONTEXT; +$config->convert->objectTables['fieldscreenlayoutitem'] = JIRA_FIELDSCREENLAYOUTITEM; +$config->convert->objectTables['fieldconfigscheme'] = JIRA_FIELDCONFIGSCHEME; +$config->convert->objectTables['fieldconfigschemeissuetype'] = JIRA_FIELDCONFIGSCHEMEISSUETYPE; + +$config->convert->jiraFieldControl['com.atlassian.jira.plugin.system.customfieldtypes:textfield'] = array('control' => 'input', 'type' => 'varchar', 'length' => '255'); +$config->convert->jiraFieldControl['com.atlassian.jira.plugin.system.customfieldtypes:labels'] = array('control' => 'input', 'type' => 'varchar', 'length' => '255'); +$config->convert->jiraFieldControl['com.atlassian.jira.plugin.system.customfieldtypes:radiobuttons'] = array('control' => 'radio', 'type' => 'varchar', 'length' => '255'); +$config->convert->jiraFieldControl['com.atlassian.jira.plugin.system.customfieldtypes:multicheckboxes'] = array('control' => 'checkbox', 'type' => 'text', 'length' => '0'); +$config->convert->jiraFieldControl['com.atlassian.jira.plugin.system.customfieldtypes:datetime'] = array('control' => 'datetime', 'type' => 'datetime', 'length' => '0'); +$config->convert->jiraFieldControl['com.atlassian.jira.plugin.system.customfieldtypes:datepicker'] = array('control' => 'date', 'type' => 'date', 'length' => '0'); +$config->convert->jiraFieldControl['com.atlassian.jira.plugin.system.customfieldtypes:textarea'] = array('control' => 'textarea', 'type' => 'text', 'length' => '0'); +$config->convert->jiraFieldControl['com.atlassian.jira.plugin.system.customfieldtypes:float'] = array('control' => 'decimal', 'type' => 'decimal', 'length' => '0'); +$config->convert->jiraFieldControl['com.atlassian.jira.plugin.system.customfieldtypes:multiselect'] = array('control' => 'multi-select', 'type' => 'text', 'length' => '0'); +$config->convert->jiraFieldControl['com.atlassian.jira.plugin.system.customfieldtypes:cascadingselect'] = array('control' => 'multi-select', 'type' => 'text', 'length' => '0'); +$config->convert->jiraFieldControl['com.atlassian.jira.plugin.system.customfieldtypes:select'] = array('control' => 'select', 'type' => 'varchar', 'length' => '255'); +$config->convert->jiraFieldControl['com.atlassian.jira.plugin.system.customfieldtypes:userpicker'] = array('control' => 'select', 'type' => 'varchar', 'length' => '255'); +$config->convert->jiraFieldControl['com.atlassian.jira.plugin.system.customfieldtypes:multiuserpicker'] = array('control' => 'multi-select', 'type' => 'text', 'length' => '0'); + +$config->convert->objectFields['epic'] = array('module', 'source', 'sourceNote', 'keywords', 'mailto', 'subStatus'); +$config->convert->objectFields['requirement'] = $config->convert->objectFields['epic']; +$config->convert->objectFields['story'] = $config->convert->objectFields['epic']; +$config->convert->objectFields['bug'] = array('module', 'keywords', 'type', 'os', 'browser', 'hardware', 'feedbackBy', 'notifyEmail', 'deadline', 'resolvedBy', 'resolvedBuild', 'resolvedDate', 'mailto', 'subStatus'); +$config->convert->objectFields['task'] = array('module', 'type', 'estStarted', 'finishedBy', 'mailto', 'subStatus'); +$config->convert->objectFields['testcase'] = array('module', 'precondition', 'type', 'stage', 'scene', 'lastRunner', 'lastRunResult', 'keywords', 'mailto', 'subStatus'); +$config->convert->objectFields['feedback'] = array('module', 'type', 'solution', 'subStatus', 'public', 'notify', 'notifyEmail', 'source', 'likes', 'processedBy', 'processedDate', 'feedbackBy', 'mailto'); +$config->convert->objectFields['ticket'] = array('module', 'openedBuild', 'realStarted', 'startedBy', 'startedDate', 'deadline', 'resolvedBy', 'resolvedDate'); + +$config->convert->importDeafaultValue = array(); +$config->convert->importDeafaultValue['zentaoObject']['Epic'] = 'requirement'; +$config->convert->importDeafaultValue['zentaoObject']['长篇故事'] = 'requirement'; +$config->convert->importDeafaultValue['zentaoObject']['Story'] = 'story'; +$config->convert->importDeafaultValue['zentaoObject']['故事'] = 'story'; +$config->convert->importDeafaultValue['zentaoObject']['需求'] = 'story'; +$config->convert->importDeafaultValue['zentaoObject']['Sub-story'] = 'story'; +$config->convert->importDeafaultValue['zentaoObject']['子需求'] = 'story'; +$config->convert->importDeafaultValue['zentaoObject']['Task'] = 'task'; +$config->convert->importDeafaultValue['zentaoObject']['任务'] = 'task'; +$config->convert->importDeafaultValue['zentaoObject']['Sub-task'] = 'task'; +$config->convert->importDeafaultValue['zentaoObject']['子任务'] = 'task'; +$config->convert->importDeafaultValue['zentaoObject']['Bug'] = 'bug'; +$config->convert->importDeafaultValue['zentaoObject']['测试'] = 'bug'; +$config->convert->importDeafaultValue['zentaoObject']['Case'] = 'testcase'; +$config->convert->importDeafaultValue['zentaoObject']['用例'] = 'testcase'; + +$config->convert->importDeafaultValue['bug']['status']['Open'] = 'active'; +$config->convert->importDeafaultValue['bug']['status']['To Do'] = 'active'; +$config->convert->importDeafaultValue['bug']['status']['In Progress'] = 'active'; +$config->convert->importDeafaultValue['bug']['status']['Reopened'] = 'active'; +$config->convert->importDeafaultValue['bug']['status']['In Review'] = 'active'; +$config->convert->importDeafaultValue['bug']['status']['Resolved'] = 'resolved'; +$config->convert->importDeafaultValue['bug']['status']['Done'] = 'closed'; +$config->convert->importDeafaultValue['bug']['status']['Closed'] = 'closed'; + +$config->convert->importDeafaultValue['task']['status']['Open'] = 'wait'; +$config->convert->importDeafaultValue['task']['status']['To Do'] = 'doing'; +$config->convert->importDeafaultValue['task']['status']['In Progress'] = 'doing'; +$config->convert->importDeafaultValue['task']['status']['Reopened'] = 'doing'; +$config->convert->importDeafaultValue['task']['status']['In Review'] = 'doing'; +$config->convert->importDeafaultValue['task']['status']['Resolved'] = 'done'; +$config->convert->importDeafaultValue['task']['status']['Done'] = 'closed'; +$config->convert->importDeafaultValue['task']['status']['Closed'] = 'closed'; + +$config->convert->importDeafaultValue['story']['status']['Open'] = 'draft'; +$config->convert->importDeafaultValue['story']['status']['To Do'] = 'active'; +$config->convert->importDeafaultValue['story']['status']['In Progress'] = 'active'; +$config->convert->importDeafaultValue['story']['status']['Reopened'] = 'active'; +$config->convert->importDeafaultValue['story']['status']['In Review'] = 'reviewing'; +$config->convert->importDeafaultValue['story']['status']['Resolved'] = 'closed'; +$config->convert->importDeafaultValue['story']['status']['Done'] = 'closed'; +$config->convert->importDeafaultValue['story']['status']['Closed'] = 'closed'; + +$config->convert->importDeafaultValue['bug']['action']['Create'] = 'create'; +$config->convert->importDeafaultValue['task']['action'] = $config->convert->importDeafaultValue['bug']['action']; +$config->convert->importDeafaultValue['story']['action'] = $config->convert->importDeafaultValue['bug']['action']; + +$config->convert->importDeafaultValue['bug']['resolution']['Done'] = 'fixed'; +$config->convert->importDeafaultValue['bug']['resolution']["Won't Do"] = 'willnotfix'; +$config->convert->importDeafaultValue['bug']['resolution']["Duplicate"] = 'duplicate'; +$config->convert->importDeafaultValue['bug']['resolution']["Cannot Reproduce"] = 'notrepro'; +$config->convert->importDeafaultValue['bug']['resolution']["Declined"] = 'willnotfix'; + +$config->convert->importDeafaultValue['task']['reason']['Done'] = 'done'; +$config->convert->importDeafaultValue['task']['reason']["Won't Do"] = 'cancel'; +$config->convert->importDeafaultValue['task']['reason']["Duplicate"] = 'cancel'; +$config->convert->importDeafaultValue['task']['reason']["Cannot Reproduce"] = 'cancel'; +$config->convert->importDeafaultValue['task']['reason']["Declined"] = 'cancel'; + +$config->convert->importDeafaultValue['story']['reason']['Done'] = 'done'; +$config->convert->importDeafaultValue['story']['reason']["Won't Do"] = 'willnotdo'; +$config->convert->importDeafaultValue['story']['reason']["Duplicate"] = 'duplicate'; +$config->convert->importDeafaultValue['story']['reason']["Cannot Reproduce"] = 'bydesign'; +$config->convert->importDeafaultValue['story']['reason']["Declined"] = 'cancel'; + +$config->convert->importDeafaultValue['requirement'] = $config->convert->importDeafaultValue['story']; +$config->convert->importDeafaultValue['epic'] = $config->convert->importDeafaultValue['story']; diff --git a/module/convert/control.php b/module/convert/control.php index ad00beee76..e4394ce057 100644 --- a/module/convert/control.php +++ b/module/convert/control.php @@ -16,12 +16,37 @@ class convert extends control * 数据导入首页。 * Index page of convert. * + * @param string $mode * @access public * @return void */ - public function index() + public function index(string $mode = '') { - $this->convert->saveState(); + $jiraRelation = $this->session->jiraRelation; + $jiraRelation = $jiraRelation ? json_decode($jiraRelation, true) : array(); + if($jiraRelation && $mode == 'restore') + { + $currentStep = 'object'; + $stepStatus = $this->session->stepStatus; + $stepStatus = $stepStatus ? json_decode($stepStatus, true) : array(); + $stepList = $this->convert->getJiraStepList($jiraRelation); + foreach($stepList as $step => $stepLabel) + { + if(!empty($stepStatus[$step]) && $stepStatus[$step] == 'done') $currentStep = $step; + } + $confirmedURL = $currentStep == 'user' ? inlink('initJiraUser', "method={$this->session->jiraMethod}&dbName={$this->session->jiraDB}") : inlink('mapJira2Zentao', "method={$this->session->jiraMethod}&dbName={$this->session->jiraDB}&step=$currentStep"); + $canceledURL = inlink('index', 'type=reset'); + $this->send(array('result' => 'success', 'load' => array('confirm' => $this->lang->convert->jira->restore, 'confirmed' => $confirmedURL, 'canceled' => $canceledURL))); + } + if($mode == 'reset') + { + unset($_SESSION['jiraDB']); + unset($_SESSION['jiraMethod']); + unset($_SESSION['jiraRelation']); + unset($_SESSION['stepStatus']); + unset($_SESSION['jiraUser']); + } + $this->view->title = $this->lang->convert->common; $this->display(); } @@ -252,110 +277,161 @@ class convert extends control } /** - * 选择数据导入方式。 - * Import jira index. - * - * @access public - * @return void - */ - public function convertJira() - { - $this->view->title = $this->lang->convert->jira->method; - $this->display(); - } - - /** - * 数据导入提示。 + * Jira数据导入提示。 * Import jira notice. * * @param string $mehotd db|file * @access public * @return void */ - public function importNotice(string $method = 'db') + public function importJiraNotice(string $method = 'db') { if($this->server->request_method == 'POST') { + $domain = $this->post->jiraDomain; + if($domain && strpos($domain, 'http') === false) $domain = 'http://' . $domain; + + $jiraApi = array(); + $jiraApi['domain'] = $domain ? trim($domain, '/') : ''; + $jiraApi['admin'] = $domain ? $this->post->jiraAdmin : ''; + $jiraApi['token'] = $domain ? $this->post->jiraToken : ''; + + $this->session->set('jiraApi', json_encode($jiraApi)); + $this->session->set('jiraDB', $this->post->dbName); + $this->session->set('jiraMethod', $method); + if($method == 'db') { $dbName = $this->post->dbName; if(!$dbName) return $this->send(array('result' => 'fail', 'message' => array('dbName' => sprintf($this->lang->error->notempty, $this->lang->convert->jira->database)))); if(!$this->convert->dbExists($dbName)) return $this->send(array('result' => 'fail', 'message' => array('dbName' => $this->lang->convert->jira->invalidDB))); if(!$this->convert->tableExistsOfJira($dbName, 'nodeassociation')) return $this->send(array('result' => 'fail', 'message' => array('dbName' => $this->lang->convert->jira->invalidTable))); + } + else + { + if($domain) + { + $this->convert->checkJiraApi(); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + } + $this->convert->deleteJiraFile(); + $jiraFilePath = $this->app->getTmpRoot() . 'jirafile/'; + if(!is_readable($jiraFilePath) || !is_writable($jiraFilePath)) return $this->send(array('result' => 'fail', 'message' => sprintf($this->lang->convert->jira->notReadAndWrite, $jiraFilePath))); + if(!file_exists($jiraFilePath . 'entities.xml')) return $this->send(array('result' => 'fail', 'message' => sprintf($this->lang->convert->jira->notExistEntities, $jiraFilePath . 'entities.xml'))); - $this->session->set('jiraDB', $dbName); - $link = $this->createLink('convert', 'mapJira2Zentao', "method=db&dbName={$this->post->dbName}"); - return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => $link)); + /* 解析entities.xml文件。 */ + $this->convert->splitFile(); } - $this->convert->deleteJiraFile(); - $jiraFilePath = $this->app->getTmpRoot() . 'jirafile/'; - if(!is_readable($jiraFilePath) || !is_writable($jiraFilePath)) return $this->send(array('result' => 'fail', 'message' => sprintf($this->lang->convert->jira->notReadAndWrite, $jiraFilePath))); - if(!file_exists($jiraFilePath . 'entities.xml')) return $this->send(array('result' => 'fail', 'message' => sprintf($this->lang->convert->jira->notExistEntities, $jiraFilePath . 'entities.xml'))); - - $this->convert->splitFile(); - - $link = $this->createLink('convert', 'mapJira2Zentao', 'method=file'); + $link = $this->createLink('convert', 'mapJira2Zentao', "method={$method}&dbName={$this->post->dbName}"); return $this->send(array('result' => 'success', 'load' => $link)); } - $this->view->title = $this->lang->convert->jira->method; - $this->view->method = $method; + $this->view->title = $this->lang->convert->jira->method; + $this->view->method = $method; + $this->view->jiraApi = !empty($_SESSION['jiraApi']) ? json_decode($this->session->jiraApi) : array(); $this->display(); } + /** + * 获取下一步。 + * Get next key. + * + * @param array $array + * @param string $currentKey + * @access public + * @return void + */ + public function getNextKey($array, $currentKey) + { + $keys = array_keys($array); + $currentIndex = array_search($currentKey, $keys); + if($currentIndex !== false && isset($keys[$currentIndex + 1])) return $keys[$currentIndex + 1]; + return false; + } + + /** + * 获取上一步。 + * Get back key. + * + * @param array $array + * @param string $currentKey + * @access public + * @return void + */ + public function getBackKey($array, $currentKey) + { + $keys = array_keys($array); + $currentIndex = array_search($currentKey, $keys); + if($currentIndex !== false && isset($keys[$currentIndex - 1])) return $keys[$currentIndex - 1]; + return false; + } + /** * 将jira对象映射到zentao。 * Map jira objects to zentao. * * @param string $method db|file * @param string $dbName - * @param int $step + * @param string $step * @access public * @return void */ - public function mapJira2Zentao(string $method = 'db', string $dbName = '', int $step = 1) + public function mapJira2Zentao(string $method = 'db', string $dbName = '', string $step = 'object') { - $this->app->loadLang('story'); - $this->app->loadLang('bug'); - $this->app->loadLang('task'); + $stepStatus = $this->session->stepStatus; + $stepStatus = $stepStatus ? json_decode($stepStatus, true) : array(); + $jiraRelation = $this->session->jiraRelation; + $jiraRelation = $jiraRelation ? json_decode($jiraRelation, true) : array(); + if($step != 'object' && empty($jiraRelation)) $this->locate(inlink('index')); if($_POST) { - foreach($_POST as $key => $value) $_SESSION['jiraRelation'][$key] = $value; + $this->convert->checkImportJira($step); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); - $link = $step == 4 ? inlink('initJiraUser', "method={$method}") : inlink('mapJira2Zentao', "method={$method}&dbName={$dbName}&step=" . ++$step); + foreach($_POST as $key => $value) $jiraRelation[$key] = $value; + $this->session->set('jiraRelation', json_encode($jiraRelation)); + + $stepStatus[$step] = 'done'; + $this->session->set('stepStatus', json_encode($stepStatus)); + + /* 提交后要获取新的步骤列表。 */ + $stepList = $this->convert->getJiraStepList($jiraRelation); + $nextSteps = $this->getNextKey($stepList, $step); + + $link = $step == 'relation' ? inlink('initJiraUser', "method={$method}&dbName={$dbName}") : inlink('mapJira2Zentao', "method={$method}&dbName={$dbName}&step={$nextSteps}"); return $this->send(array('result' => 'success', 'load' => $link)); } - if($method == 'db') - { - $originDBH = $this->dbh; - $dbh = $this->convert->connectDB($dbName); - - $issueTypeList = $this->dao->dbh($dbh)->select('*')->from(JIRA_ISSUETYPE)->fetchAll('ID'); - $linkTypeList = $this->dao->dbh($dbh)->select('*, LINKNAME as linkname')->from(JIRA_ISSUELINKTYPE)->fetchAll('ID'); - $resolutionList = $this->dao->dbh($dbh)->select('*')->from(JIRA_RESOLUTION)->fetchAll('ID'); - $statusList = $this->dao->dbh($dbh)->select('*')->from(JIRA_ISSUESTATUS)->fetchAll('ID'); - - $this->dao->dbh($originDBH); - } - else - { - $issueTypeList = $this->convert->getJiraDataFromFile('issuetype'); - $linkTypeList = $this->convert->getJiraDataFromFile('issuelinktype'); - $resolutionList = $this->convert->getJiraDataFromFile('resolution'); - $statusList = $this->convert->getJiraDataFromFile('status'); - } + $this->loadModel('story'); + $this->loadModel('bug'); + $this->loadModel('task'); + $objectRelation = !empty($jiraRelation['zentaoObject']) && in_array($step, array_keys($jiraRelation['zentaoObject'])); + $resolutionList = $objectRelation ? $this->convert->getJiraData($method, 'resolution') : array(); + $statusList = $objectRelation ? $this->convert->getJiraStatusList($step, $jiraRelation) : array(); + $jiraFields = $objectRelation ? $this->convert->getJiraCustomField($step, $jiraRelation) : array(); + $issueTypeList = $this->convert->getJiraTypeList(); + $linkTypeList = $step == 'relation' ? $this->convert->getJiraData($method, 'issuelinktype') : array(); + $stepList = $this->convert->getJiraStepList($jiraRelation, $issueTypeList); + $backSteps = $this->getBackKey($stepList, $step); $this->view->title = $this->lang->convert->jira->mapJira2Zentao; - $this->view->issueTypeList = $issueTypeList; - $this->view->linkTypeList = $linkTypeList; - $this->view->resolutionList = $resolutionList; - $this->view->statusList = $statusList; $this->view->method = $method; $this->view->step = $step; $this->view->dbName = $dbName; + $this->view->stepStatus = $stepStatus; + $this->view->stepList = $stepList; + $this->view->jiraRelation = $jiraRelation; + $this->view->issueTypeList = $issueTypeList; + $this->view->zentaoObjects = $this->convert->getZentaoObjectList();; + $this->view->fieldList = $jiraFields; + $this->view->statusList = $statusList; + $this->view->jiraActions = $this->convert->getJiraWorkflowActions(); + $this->view->resolutionList = $resolutionList; + $this->view->defaultValue = $this->convert->getObjectDefaultValue($step); + $this->view->linkTypeList = $linkTypeList; + $this->view->backUrl = $backSteps ? inlink('mapJira2Zentao', "method={$method}&dbName={$dbName}&step={$backSteps}") : ''; $this->display(); } @@ -364,32 +440,51 @@ class convert extends control * Init jira user. * * @param string $method db|file + * @param string $dbName * @access public * @return void */ - public function initJiraUser(string $method = 'db') + public function initJiraUser(string $method = 'db', string $dbName = '') { $this->app->loadLang('user'); + $stepStatus = $this->session->stepStatus; + $stepStatus = $stepStatus ? json_decode($stepStatus, true) : array(); if($_POST) { $errors = array(); if(!$this->post->password1) $errors['password1'][] = sprintf($this->lang->error->notempty, $this->lang->user->password); - if(!$this->post->password2) $errors['password2'][] = sprintf($this->lang->error->notempty, $this->lang->user->abbr->password2); + if(!$this->post->password2) $errors['password2'][] = sprintf($this->lang->error->notempty, $this->lang->user->password2); if($this->post->password1 && strlen(trim($this->post->password1)) < 6) $errors['password1'][] = $this->lang->convert->jira->passwordLess; if($this->post->password1 && $this->post->password2 && $this->post->password1 != $this->post->password2) $errors['password2'][] = $this->lang->convert->jira->passwordDifferent; if($errors) return $this->send(array('result' => 'fail', 'message' => $errors)); $jiraUser['password'] = md5($this->post->password1); $jiraUser['group'] = $this->post->group; + $jiraUser['mode'] = $this->post->mode; $this->session->set('jiraUser', $jiraUser); + $stepStatus['user'] = 'done'; + $this->session->set('stepStatus', json_encode($stepStatus)); + return $this->send(array('result' => 'success', 'load' => inlink('importJira', "method={$method}"))); } - $this->view->title = $this->lang->convert->jira->initJiraUser; - $this->view->groups = $this->loadModel('group')->getPairs(); - $this->view->method = $method; + $jiraRelation = $this->session->jiraRelation; + $jiraRelation = $jiraRelation ? json_decode($jiraRelation, true) : array(); + if(empty($jiraRelation)) $this->locate(inlink('index')); + + $stepList = $this->convert->getJiraStepList($jiraRelation); + $backSteps = $this->getBackKey($stepList, 'user'); + + $this->view->title = $this->lang->convert->jira->initJiraUser; + $this->view->method = $method; + $this->view->dbName = $dbName; + $this->view->step = 'user'; + $this->view->stepList = $stepList; + $this->view->stepStatus = $stepStatus; + $this->view->backUrl = $backSteps ? inlink('mapJira2Zentao', "method={$method}&dbName={$dbName}&step={$backSteps}") : ''; + $this->view->groups = $this->loadModel('group')->getPairs(); $this->display(); } @@ -411,8 +506,7 @@ class convert extends control if($mode == 'import') { - $importFunc = 'importJiraFrom' . $method; - $result = $this->convert->$importFunc($type, $lastID, $createTable); + $result = $this->convert->importJiraData($type, $lastID, $createTable); if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); if(!empty($result['finished'])) return $this->send(array('result' => 'finished', 'message' => $this->lang->convert->jira->importSuccessfully)); @@ -427,8 +521,23 @@ class convert extends control return $this->send($response); } - $this->view->title = $this->lang->convert->jira->importJira; - $this->view->method = $method; + $jiraRelation = $this->session->jiraRelation; + $jiraRelation = $jiraRelation ? json_decode($jiraRelation, true) : array(); + if(empty($jiraRelation)) $this->locate(inlink('index')); + + $stepStatus = $this->session->stepStatus; + $stepStatus = $stepStatus ? json_decode($stepStatus, true) : array(); + + $stepList = $this->convert->getJiraStepList($jiraRelation); + $backSteps = $this->getBackKey($stepList, 'user'); + + $this->view->title = $this->lang->convert->jira->importJira; + $this->view->method = $method; + $this->view->dbName = $this->session->jiraDB; + $this->view->step = 'confirme'; + $this->view->stepList = $stepList; + $this->view->stepStatus = $stepStatus; + $this->view->backUrl = $backSteps ? inlink('initJiraUser', "method={$method}&dbName={$this->session->jiraDB}") : ''; $this->display(); } } diff --git a/module/convert/js/importjira.ui.js b/module/convert/js/importjira.ui.js index a50f29fc16..32375ab101 100644 --- a/module/convert/js/importjira.ui.js +++ b/module/convert/js/importjira.ui.js @@ -4,29 +4,37 @@ function importJira(event, url, hide) $('#importResult .importing').removeClass('hidden'); - $.getJSON(url, function(response) + $.get(url, function(data) { - if(response.result == 'finished') + try { - $('#importResult').append("
  • " + response.message + '
  • '); - $('#importResult .importing').addClass('hidden'); - return false; - } - else - { - className = response.type + 'count'; - $typeCount = $('#importResult .' + className) - if($typeCount.length == 0) + let response = JSON.parse(data); + if(response.result == 'finished') { $('#importResult').append("
  • " + response.message + '
  • '); + $('#importResult .importing').addClass('hidden'); + return false; } else { - count = parseInt($typeCount.html()) + parseInt(response.count); - $typeCount.html(count); - } + const className = response.type + 'count'; + const $typeCount = $('#importResult .' + className) + if($typeCount.length == 0) + { + $('#importResult').append("
  • " + response.message + '
  • '); + } + else + { + const count = parseInt($typeCount.html()) + parseInt(response.count); + $typeCount.html(count); + } - return importJira(event, response.next); + return importJira(event, response.next); + } + } + catch(e) + { + $('#importResult').append("
  • " + data + '
  • '); } }); return false; diff --git a/module/convert/lang/de.php b/module/convert/lang/de.php index dab09aefbd..25ef0cf6c0 100644 --- a/module/convert/lang/de.php +++ b/module/convert/lang/de.php @@ -9,7 +9,7 @@ * @version $Id: en.php 4129 2013-01-18 01:58:14Z wwccss $ * @link https://www.zentao.net */ -$lang->convert->common = 'Importiert'; +$lang->convert->common = 'Imported'; $lang->convert->index = 'Home'; $lang->convert->start = 'Start'; @@ -18,112 +18,128 @@ $lang->convert->desc = <<There are risks in the conversion, so it is strongly recommended that you back up your databse and relavant files before conversion, and make sure that no one is using either system. EOT; -$lang->convert->setConfig = 'Source Konfiguration'; -$lang->convert->setBugfree = 'Bugfree Konfiguration'; -$lang->convert->setRedmine = 'Redmine Konfiguration'; -$lang->convert->checkBugFree = 'Prüfe Bugfree'; -$lang->convert->checkRedmine = 'Prüfe Redmine'; -$lang->convert->convertRedmine = 'Konvertiere Redmine'; -$lang->convert->convertBugFree = 'Konvertiere BugFree'; +$lang->convert->setConfig = 'Source Config'; +$lang->convert->setBugfree = 'Bugfree Config'; +$lang->convert->setRedmine = 'Redmine Config'; +$lang->convert->checkBugFree = 'Check Bugfree'; +$lang->convert->checkRedmine = 'Check Redmine'; +$lang->convert->convertRedmine = 'Convert Redmine'; +$lang->convert->convertBugFree = 'Convert BugFree'; -$lang->convert->selectSource = 'Quellsystem und Version wählen'; -$lang->convert->mustSelectSource = "Sie müssen eine Quellsystem wählen."; +$lang->convert->selectSource = 'Select source system and its version'; +$lang->convert->mustSelectSource = "You must select a source system."; -$lang->convert->direction = "{$lang->executionCommon} konvertieren nach"; -$lang->convert->questionTypeOfRedmine = 'Typ in Redmine'; -$lang->convert->aimTypeOfZentao = 'Konvertieren nach Typ in Zentao'; +$lang->convert->direction = "{$lang->executionCommon} converted to"; +$lang->convert->questionTypeOfRedmine = 'Type in Redmine'; +$lang->convert->aimTypeOfZentao = 'Convert to Type in ZenTao'; + +$lang->convert->jiraUserMode = array(); +$lang->convert->jiraUserMode['account'] = 'Use Jira Account'; +$lang->convert->jiraUserMode['email'] = 'Use Jira Email'; + +$lang->convert->confluenceUserMode = array(); +$lang->convert->confluenceUserMode['account'] = 'Use Confluence Account'; +$lang->convert->confluenceUserMode['email'] = 'Use Confluence Email'; $lang->convert->directionList['bug'] = 'Bug'; -$lang->convert->directionList['task'] = 'Aufgabe'; -$lang->convert->directionList['story'] = 'Story'; +$lang->convert->directionList['task'] = 'Task'; +$lang->convert->directionList['story'] = $lang->SRCommon; $lang->convert->sourceList['BugFree'] = array('bugfree_1' => '1.x', 'bugfree_2' => '2.x'); $lang->convert->sourceList['Redmine'] = array('Redmine_1.1' => '1.1'); -$lang->convert->setting = 'Einstellungen'; -$lang->convert->checkConfig = 'Prüfe Einstellungen'; +$lang->convert->setting = 'Settings'; +$lang->convert->checkConfig = 'Check Settings'; +$lang->convert->add = 'Add'; +$lang->convert->title = 'Title'; $lang->convert->ok = ' OK '; -$lang->convert->fail = ' Fehlgeschlagen'; +$lang->convert->fail = ' Failed'; -$lang->convert->dbHost = 'Datenbank Server'; +$lang->convert->dbHost = 'Database Server'; $lang->convert->dbPort = 'Server Port'; -$lang->convert->dbUser = 'Datenbank User Name'; -$lang->convert->dbPassword = 'Datenbank Passwort'; -$lang->convert->dbName = 'Datenbank benutzt in %s'; -$lang->convert->dbCharset = '%s Datenbank Code'; -$lang->convert->dbPrefix = '%s Tabellen Prefix'; -$lang->convert->installPath = '%s Installationspfad'; +$lang->convert->dbUser = 'Database User Name'; +$lang->convert->dbPassword = 'Database Password'; +$lang->convert->dbName = 'Database used in %s'; +$lang->convert->dbCharset = '%s Database Coding'; +$lang->convert->dbPrefix = '%s Table Prefix'; +$lang->convert->installPath = '%s Installation Root Directory'; -$lang->convert->checkDB = 'Datenbank'; -$lang->convert->checkTable = 'Tabelle'; -$lang->convert->checkPath = 'Installationspfad'; +$lang->convert->checkDB = 'Database'; +$lang->convert->checkTable = 'Table'; +$lang->convert->checkPath = 'Installation Path'; -$lang->convert->execute = 'Konvertieren'; -$lang->convert->item = 'Objekt konvertiert'; -$lang->convert->count = 'Zähler'; +$lang->convert->execute = 'Convert'; +$lang->convert->item = 'Item Converted'; +$lang->convert->count = 'No.'; $lang->convert->info = 'Info'; $lang->convert->bugfree = new stdclass(); -$lang->convert->bugfree->users = 'Benutzer'; +$lang->convert->bugfree->users = 'User'; $lang->convert->bugfree->executions = $lang->executionCommon; -$lang->convert->bugfree->modules = 'Modul'; +$lang->convert->bugfree->modules = 'Module'; $lang->convert->bugfree->bugs = 'Bug'; -$lang->convert->bugfree->cases = 'Testfälle'; -$lang->convert->bugfree->results = 'Ergebnis'; -$lang->convert->bugfree->actions = 'Verlauf'; -$lang->convert->bugfree->files = 'Datei'; +$lang->convert->bugfree->cases = 'Test Case'; +$lang->convert->bugfree->results = 'Result'; +$lang->convert->bugfree->actions = 'History'; +$lang->convert->bugfree->files = 'Files'; $lang->convert->redmine = new stdclass(); -$lang->convert->redmine->users = 'Benutzer'; -$lang->convert->redmine->groups = 'Gruppe'; +$lang->convert->redmine->users = 'User'; +$lang->convert->redmine->groups = 'Group'; $lang->convert->redmine->products = $lang->productCommon; $lang->convert->redmine->executions = $lang->executionCommon; $lang->convert->redmine->stories = 'Story'; -$lang->convert->redmine->tasks = 'Aufgabe'; +$lang->convert->redmine->tasks = 'Task'; $lang->convert->redmine->bugs = 'Bug'; $lang->convert->redmine->productPlans = $lang->productCommon . 'Plan'; $lang->convert->redmine->teams = 'Team'; $lang->convert->redmine->releases = 'Release'; $lang->convert->redmine->builds = 'Build'; -$lang->convert->redmine->docLibs = 'Dok Bibliothek'; -$lang->convert->redmine->docs = 'Dok'; -$lang->convert->redmine->files = 'Datei'; +$lang->convert->redmine->docLibs = 'Doc Lib'; +$lang->convert->redmine->docs = 'Doc'; +$lang->convert->redmine->files = 'Files'; -$lang->convert->errorFileNotExits = 'Datei %s nicht gefunden.'; -$lang->convert->errorUserExists = 'Benutzer %s existiert.'; -$lang->convert->errorGroupExists = 'Gruppe %s existiert.'; -$lang->convert->errorBuildExists = 'Build %s existiert.'; -$lang->convert->errorReleaseExists = 'Release %s existiert.'; -$lang->convert->errorCopyFailed = 'Datei %s Kopiervorgang fehlgeschlagen.'; +$lang->convert->errorFileNotExits = 'File %s is not found.'; +$lang->convert->errorUserExists = 'User %s existed.'; +$lang->convert->errorGroupExists = 'Group %s existed.'; +$lang->convert->errorBuildExists = 'Build %s existed.'; +$lang->convert->errorReleaseExists = 'Release %s existed.'; +$lang->convert->errorCopyFailed = 'File %s copy failed.'; -$lang->convert->setParam = 'Bitte setzen Sie die Parameter.'; +$lang->convert->setParam = 'Set parameters.'; $lang->convert->statusType = new stdclass(); $lang->convert->priType = new stdclass(); -$lang->convert->aimType = 'Konvertiere Probleme'; -$lang->convert->statusType->bug = 'Konvertiere Status (Bug Status)'; -$lang->convert->statusType->story = 'Konvertiere Status (Story Status)'; -$lang->convert->statusType->task = 'Konvertiere Status (Aufgaben Status)'; -$lang->convert->priType->bug = 'Konvertiere Priorität (Bug Status)'; -$lang->convert->priType->story = 'Konvertiere Priorität (Story Status)'; -$lang->convert->priType->task = 'Konvertiere Priorität (Aufgaben Status)'; +$lang->convert->aimType = 'Convert Issue'; +$lang->convert->statusType->bug = 'Convert Status (Bug Status)'; +$lang->convert->statusType->story = 'Convert Status (Story Status)'; +$lang->convert->statusType->task = 'Convert Status (Task Status)'; +$lang->convert->priType->bug = 'Convert Priority (Bug Status)'; +$lang->convert->priType->story = 'Convert Priority (Story Status)'; +$lang->convert->priType->task = 'Convert Priority (Task Status)'; $lang->convert->issue = new stdclass(); $lang->convert->issue->redmine = 'Redmine'; $lang->convert->issue->zentao = 'ZenTao'; -$lang->convert->issue->goto = 'Konvertieren nach'; +$lang->convert->issue->goto = 'Convert To'; $lang->convert->jira = new stdclass(); $lang->convert->jira->method = 'Chose Import Method'; +$lang->convert->jira->back = 'Back'; $lang->convert->jira->next = 'Next'; $lang->convert->jira->importFromDB = 'Import From Database'; $lang->convert->jira->importFromFile = 'Import From File'; $lang->convert->jira->mapJira2Zentao = 'Map Jira To Zentao'; $lang->convert->jira->database = 'Jira Database'; +$lang->convert->jira->domain = 'Jira Domain'; +$lang->convert->jira->admin = 'Jira Account'; +$lang->convert->jira->token = 'Jira Passwork/Token'; $lang->convert->jira->dbNameNotice = "Please enter the Jira database name."; $lang->convert->jira->importNotice = 'Notice: Importing data is risky! Make sure to complete the following steps in sequence before merging.'; +$lang->convert->jira->accountNotice = 'Those who use email will use the string before @ as their username, and those exceeding 30 characters will be truncated.'; +$lang->convert->jira->apiError = 'Unable to connect to Jira API interface, please check your Jira domain name and account, password/Token information.'; $lang->convert->jira->dbDesc = 'If your Jira uses MySQL database, please choose this way.'; $lang->convert->jira->fileDesc = 'Choose this method if your Jira uses a non-MySQL database.'; $lang->convert->jira->jiraObject = 'Jira Issues'; @@ -138,6 +154,20 @@ $lang->convert->jira->storyStatus = 'Zentao Story Status'; $lang->convert->jira->storyStage = 'Zentao Story Stage'; $lang->convert->jira->bugStatus = 'Zentao Bug Status'; $lang->convert->jira->taskStatus = 'Zentao Task Status'; +$lang->convert->jira->objectField = 'Field mapping'; +$lang->convert->jira->objectStatus = 'Status mapping'; +$lang->convert->jira->objectAction = 'Action mapping'; +$lang->convert->jira->objectResolution = 'Resolution mapping'; +$lang->convert->jira->jiraField = 'Jira %s Field'; +$lang->convert->jira->jiraStatus = 'Jira %s Status'; +$lang->convert->jira->jiraAction = 'Jira %s Action'; +$lang->convert->jira->jiraResolution = 'Jira %s Resolution'; +$lang->convert->jira->zentaoField = 'Zentao %s Field'; +$lang->convert->jira->zentaoStatus = 'Zentao %s Status'; +$lang->convert->jira->zentaoStage = 'Zentao %s Stage'; +$lang->convert->jira->zentaoAction = 'Zentao %s Action'; +$lang->convert->jira->zentaoReason = 'Zentao %s Closed Reason'; +$lang->convert->jira->zentaoResolution = 'Zentao %s Resolution'; $lang->convert->jira->initJiraUser = 'Init Jira User'; $lang->convert->jira->importJira = 'Import Jira'; $lang->convert->jira->start = 'Start'; @@ -147,8 +177,13 @@ $lang->convert->jira->invalidDB = 'Invalid database name!'; $lang->convert->jira->invalidTable = 'This database is not a Jira database!'; $lang->convert->jira->notReadAndWrite = 'Please modify the %s directory permissions!'; $lang->convert->jira->notExistEntities = 'The %s file does not exist!'; -$lang->convert->jira->passwordNotice = 'Set the default password of Jira users after they are imported into ZenTao. Users can modify the password in ZenTao later.'; -$lang->convert->jira->groupNotice = 'Set the default permission grouping of Jira users after importing into ZenTao.'; +$lang->convert->jira->passwordNotice = 'Set the default password of users after they are imported into ZenTao. Users can modify the password in ZenTao later.'; +$lang->convert->jira->groupNotice = 'Set the default permission grouping of users after importing into ZenTao.'; +$lang->convert->jira->mapObjectNotice = 'When selecting a mapping relationship, if you choose to add a new workflow, a new object will be automatically created in the workflow after import.'; +$lang->convert->jira->mapFieldNotice = 'The built-in fields in JIRA have been automatically matched. Please select the mapping relationship for custom fields. When selecting the mapping relationship, if you choose to add, new fields will be automatically created after import, and unselected fields will not be imported.'; +$lang->convert->jira->mapStatusNotice = 'When selecting a mapping relationship, unselected states are imported and automatically matched to %s.'; +$lang->convert->jira->mapReasonNotice = 'When selecting a mapping relationship, if "Add" is chosen, a new solution will be automatically created after import. Solutions not selected will be matched as "Completed" by default after import.'; +$lang->convert->jira->mapRelationNotice = 'When selecting a mapping relationship, if you choose to add, an association relationship will be automatically created after import, and unselected associations will not be imported.'; $lang->convert->jira->passwordDifferent = 'Password do not match!'; $lang->convert->jira->passwordEmpty = 'Password can not be empty!'; $lang->convert->jira->passwordLess = 'Password must not be less than six characters!'; @@ -157,11 +192,14 @@ $lang->convert->jira->importResult = "Import %sconvert->jira->importing = 'Data is being imported, please do not switch to other pages.'; $lang->convert->jira->importingAB = 'Data Import'; $lang->convert->jira->imported = 'Data Import Complete'; +$lang->convert->jira->restore = 'The last import of information was not completed. Do you want to continue filling in from the last process?'; -$lang->convert->jira->zentaoObjectList[''] = ''; -$lang->convert->jira->zentaoObjectList['task'] = 'Task'; +$lang->convert->jira->zentaoObjectList[''] = ''; +$lang->convert->jira->zentaoObjectList['epic'] = 'Epic'; $lang->convert->jira->zentaoObjectList['requirement'] = 'Requirement'; $lang->convert->jira->zentaoObjectList['story'] = 'Story'; +$lang->convert->jira->zentaoObjectList['task'] = 'Task'; +$lang->convert->jira->zentaoObjectList['testcase'] = 'Case'; $lang->convert->jira->zentaoObjectList['bug'] = 'Bug'; $lang->convert->jira->zentaoLinkTypeList['subTaskLink'] = 'Parent-Child Task'; @@ -169,10 +207,11 @@ $lang->convert->jira->zentaoLinkTypeList['subStoryLink'] = 'Parent-Child Story'; $lang->convert->jira->zentaoLinkTypeList['duplicate'] = 'Duplicate'; $lang->convert->jira->zentaoLinkTypeList['relates'] = 'Relates'; -$lang->convert->jira->steps[1] = 'Objects'; -$lang->convert->jira->steps[2] = 'Relates'; -$lang->convert->jira->steps[3] = 'Resolution'; -$lang->convert->jira->steps[4] = 'Status'; +$lang->convert->jira->steps['object'] = 'Objects'; +$lang->convert->jira->steps['objectData'] = 'Object Data'; +$lang->convert->jira->steps['relation'] = 'Relation'; +$lang->convert->jira->steps['user'] = 'Import Jira User'; +$lang->convert->jira->steps['confirme'] = 'Import Data'; $lang->convert->jira->importSteps['db'][1] = 'Backup ZenTao database, backup Jira database.'; $lang->convert->jira->importSteps['db'][2] = 'Using ZenTao when importing data will cause performance pressure on the server, please try to ensure that no one is using ZenTao when importing data.'; @@ -184,12 +223,20 @@ $lang->convert->jira->importSteps['file'][1] = 'Backup ZenTao database, backup J $lang->convert->jira->importSteps['file'][2] = 'Using ZenTao when importing data will cause performance pressure on the server, please try to ensure that no one is using ZenTao when importing data.'; $lang->convert->jira->importSteps['file'][3] = "Put the Jira backup file entities.xml under %s."; $lang->convert->jira->importSteps['file'][4] = "Put the Jira attachments directory under %s, Make sure you have enough disk space on the ZenTao server."; -$lang->convert->jira->importSteps['file'][5] = "After the above steps are completed, click Next."; +$lang->convert->jira->importSteps['file'][5] = "To ensure the integrity of the imported data, please enter the domain name, administrator account, password/ToKen of the current Jira environment."; +$lang->convert->jira->importSteps['file'][6] = "After the above steps are completed, click Next."; $lang->convert->jira->objectList['user'] = 'User'; $lang->convert->jira->objectList['project'] = 'Project'; $lang->convert->jira->objectList['issue'] = 'Issue'; $lang->convert->jira->objectList['build'] = 'Build'; $lang->convert->jira->objectList['issuelink'] = 'Issue Link'; +$lang->convert->jira->objectList['worklog'] = 'Worklog'; $lang->convert->jira->objectList['action'] = 'Action'; $lang->convert->jira->objectList['file'] = 'File'; + +$lang->convert->jira->buildinFields = array(); +$lang->convert->jira->buildinFields['summary'] = array('name'=> 'Summary', 'jiraField' => 'SUMMARY', 'control' => 'input', 'type' => 'varchar', 'length' => '255'); +$lang->convert->jira->buildinFields['pri'] = array('name'=> 'Pri', 'jiraField' => 'PRIORITY', 'control' => 'select', 'type' => 'int', 'length' => '3'); +$lang->convert->jira->buildinFields['resolution'] = array('name'=> 'Resolution', 'jiraField' => 'RESOLUTION', 'control' => 'select', 'type' => 'varchar', 'length' => '255'); +$lang->convert->jira->buildinFields['desc'] = array('name'=> 'Desc', 'jiraField' => 'DESCRIPTION', 'control' => 'richtext', 'type' => 'mediumtext', 'length' => '0'); diff --git a/module/convert/lang/en.php b/module/convert/lang/en.php index 8f02fb746e..e2e231cb1f 100644 --- a/module/convert/lang/en.php +++ b/module/convert/lang/en.php @@ -33,15 +33,25 @@ $lang->convert->direction = "{$lang->executionCommon} converted to"; $lang->convert->questionTypeOfRedmine = 'Type in Redmine'; $lang->convert->aimTypeOfZentao = 'Convert to Type in ZenTao'; +$lang->convert->jiraUserMode = array(); +$lang->convert->jiraUserMode['account'] = 'Use Jira Account'; +$lang->convert->jiraUserMode['email'] = 'Use Jira Email'; + +$lang->convert->confluenceUserMode = array(); +$lang->convert->confluenceUserMode['account'] = 'Use Confluence Account'; +$lang->convert->confluenceUserMode['email'] = 'Use Confluence Email'; + $lang->convert->directionList['bug'] = 'Bug'; $lang->convert->directionList['task'] = 'Task'; -$lang->convert->directionList['story'] = 'Story'; +$lang->convert->directionList['story'] = $lang->SRCommon; $lang->convert->sourceList['BugFree'] = array('bugfree_1' => '1.x', 'bugfree_2' => '2.x'); $lang->convert->sourceList['Redmine'] = array('Redmine_1.1' => '1.1'); $lang->convert->setting = 'Settings'; $lang->convert->checkConfig = 'Check Settings'; +$lang->convert->add = 'Add'; +$lang->convert->title = 'Title'; $lang->convert->ok = ' OK '; $lang->convert->fail = ' Failed'; @@ -117,13 +127,19 @@ $lang->convert->issue->goto = 'Convert To'; $lang->convert->jira = new stdclass(); $lang->convert->jira->method = 'Chose Import Method'; +$lang->convert->jira->back = 'Back'; $lang->convert->jira->next = 'Next'; $lang->convert->jira->importFromDB = 'Import From Database'; $lang->convert->jira->importFromFile = 'Import From File'; $lang->convert->jira->mapJira2Zentao = 'Map Jira To Zentao'; $lang->convert->jira->database = 'Jira Database'; +$lang->convert->jira->domain = 'Jira Domain'; +$lang->convert->jira->admin = 'Jira Account'; +$lang->convert->jira->token = 'Jira Passwork/Token'; $lang->convert->jira->dbNameNotice = "Please enter the Jira database name."; $lang->convert->jira->importNotice = 'Notice: Importing data is risky! Make sure to complete the following steps in sequence before merging.'; +$lang->convert->jira->accountNotice = 'Those who use email will use the string before @ as their username, and those exceeding 30 characters will be truncated.'; +$lang->convert->jira->apiError = 'Unable to connect to Jira API interface, please check your Jira domain name and account, password/Token information.'; $lang->convert->jira->dbDesc = 'If your Jira uses MySQL database, please choose this way.'; $lang->convert->jira->fileDesc = 'Choose this method if your Jira uses a non-MySQL database.'; $lang->convert->jira->jiraObject = 'Jira Issues'; @@ -138,6 +154,20 @@ $lang->convert->jira->storyStatus = 'Zentao Story Status'; $lang->convert->jira->storyStage = 'Zentao Story Stage'; $lang->convert->jira->bugStatus = 'Zentao Bug Status'; $lang->convert->jira->taskStatus = 'Zentao Task Status'; +$lang->convert->jira->objectField = 'Field mapping'; +$lang->convert->jira->objectStatus = 'Status mapping'; +$lang->convert->jira->objectAction = 'Action mapping'; +$lang->convert->jira->objectResolution = 'Resolution mapping'; +$lang->convert->jira->jiraField = 'Jira %s Field'; +$lang->convert->jira->jiraStatus = 'Jira %s Status'; +$lang->convert->jira->jiraAction = 'Jira %s Action'; +$lang->convert->jira->jiraResolution = 'Jira %s Resolution'; +$lang->convert->jira->zentaoField = 'Zentao %s Field'; +$lang->convert->jira->zentaoStatus = 'Zentao %s Status'; +$lang->convert->jira->zentaoStage = 'Zentao %s Stage'; +$lang->convert->jira->zentaoAction = 'Zentao %s Action'; +$lang->convert->jira->zentaoReason = 'Zentao %s Closed Reason'; +$lang->convert->jira->zentaoResolution = 'Zentao %s Resolution'; $lang->convert->jira->initJiraUser = 'Init Jira User'; $lang->convert->jira->importJira = 'Import Jira'; $lang->convert->jira->start = 'Start'; @@ -147,8 +177,13 @@ $lang->convert->jira->invalidDB = 'Invalid database name!'; $lang->convert->jira->invalidTable = 'This database is not a Jira database!'; $lang->convert->jira->notReadAndWrite = 'Please modify the %s directory permissions!'; $lang->convert->jira->notExistEntities = 'The %s file does not exist!'; -$lang->convert->jira->passwordNotice = 'Set the default password of Jira users after they are imported into ZenTao. Users can modify the password in ZenTao later.'; -$lang->convert->jira->groupNotice = 'Set the default permission grouping of Jira users after importing into ZenTao.'; +$lang->convert->jira->passwordNotice = 'Set the default password of users after they are imported into ZenTao. Users can modify the password in ZenTao later.'; +$lang->convert->jira->groupNotice = 'Set the default permission grouping of users after importing into ZenTao.'; +$lang->convert->jira->mapObjectNotice = 'When selecting a mapping relationship, if you choose to add a new workflow, a new object will be automatically created in the workflow after import.'; +$lang->convert->jira->mapFieldNotice = 'The built-in fields in JIRA have been automatically matched. Please select the mapping relationship for custom fields. When selecting the mapping relationship, if you choose to add, new fields will be automatically created after import, and unselected fields will not be imported.'; +$lang->convert->jira->mapStatusNotice = 'When selecting a mapping relationship, unselected states are imported and automatically matched to %s.'; +$lang->convert->jira->mapReasonNotice = 'When selecting a mapping relationship, if "Add" is chosen, a new solution will be automatically created after import. Solutions not selected will be matched as "Completed" by default after import.'; +$lang->convert->jira->mapRelationNotice = 'When selecting a mapping relationship, if you choose to add, an association relationship will be automatically created after import, and unselected associations will not be imported.'; $lang->convert->jira->passwordDifferent = 'Password do not match!'; $lang->convert->jira->passwordEmpty = 'Password can not be empty!'; $lang->convert->jira->passwordLess = 'Password must not be less than six characters!'; @@ -157,11 +192,14 @@ $lang->convert->jira->importResult = "Import %sconvert->jira->importing = 'Data is being imported, please do not switch to other pages.'; $lang->convert->jira->importingAB = 'Data Import'; $lang->convert->jira->imported = 'Data Import Complete'; +$lang->convert->jira->restore = 'The last import of information was not completed. Do you want to continue filling in from the last process?'; -$lang->convert->jira->zentaoObjectList[''] = ''; -$lang->convert->jira->zentaoObjectList['task'] = 'Task'; +$lang->convert->jira->zentaoObjectList[''] = ''; +$lang->convert->jira->zentaoObjectList['epic'] = 'Epic'; $lang->convert->jira->zentaoObjectList['requirement'] = 'Requirement'; $lang->convert->jira->zentaoObjectList['story'] = 'Story'; +$lang->convert->jira->zentaoObjectList['task'] = 'Task'; +$lang->convert->jira->zentaoObjectList['testcase'] = 'Case'; $lang->convert->jira->zentaoObjectList['bug'] = 'Bug'; $lang->convert->jira->zentaoLinkTypeList['subTaskLink'] = 'Parent-Child Task'; @@ -169,10 +207,11 @@ $lang->convert->jira->zentaoLinkTypeList['subStoryLink'] = 'Parent-Child Story'; $lang->convert->jira->zentaoLinkTypeList['duplicate'] = 'Duplicate'; $lang->convert->jira->zentaoLinkTypeList['relates'] = 'Relates'; -$lang->convert->jira->steps[1] = 'Objects'; -$lang->convert->jira->steps[2] = 'Relates'; -$lang->convert->jira->steps[3] = 'Resolution'; -$lang->convert->jira->steps[4] = 'Status'; +$lang->convert->jira->steps['object'] = 'Objects'; +$lang->convert->jira->steps['objectData'] = 'Object Data'; +$lang->convert->jira->steps['relation'] = 'Relation'; +$lang->convert->jira->steps['user'] = 'Import Jira User'; +$lang->convert->jira->steps['confirme'] = 'Import Data'; $lang->convert->jira->importSteps['db'][1] = 'Backup ZenTao database, backup Jira database.'; $lang->convert->jira->importSteps['db'][2] = 'Using ZenTao when importing data will cause performance pressure on the server, please try to ensure that no one is using ZenTao when importing data.'; @@ -184,12 +223,20 @@ $lang->convert->jira->importSteps['file'][1] = 'Backup ZenTao database, backup J $lang->convert->jira->importSteps['file'][2] = 'Using ZenTao when importing data will cause performance pressure on the server, please try to ensure that no one is using ZenTao when importing data.'; $lang->convert->jira->importSteps['file'][3] = "Put the Jira backup file entities.xml under %s."; $lang->convert->jira->importSteps['file'][4] = "Put the Jira attachments directory under %s, Make sure you have enough disk space on the ZenTao server."; -$lang->convert->jira->importSteps['file'][5] = "After the above steps are completed, click Next."; +$lang->convert->jira->importSteps['file'][5] = "To ensure the integrity of the imported data, please enter the domain name, administrator account, password/Token of the current Jira environment."; +$lang->convert->jira->importSteps['file'][6] = "After the above steps are completed, click Next."; $lang->convert->jira->objectList['user'] = 'User'; $lang->convert->jira->objectList['project'] = 'Project'; $lang->convert->jira->objectList['issue'] = 'Issue'; $lang->convert->jira->objectList['build'] = 'Build'; $lang->convert->jira->objectList['issuelink'] = 'Issue Link'; +$lang->convert->jira->objectList['worklog'] = 'Worklog'; $lang->convert->jira->objectList['action'] = 'Action'; $lang->convert->jira->objectList['file'] = 'File'; + +$lang->convert->jira->buildinFields = array(); +$lang->convert->jira->buildinFields['summary'] = array('name'=> 'Summary', 'jiraField' => 'SUMMARY', 'control' => 'input', 'type' => 'varchar', 'length' => '255'); +$lang->convert->jira->buildinFields['pri'] = array('name'=> 'Pri', 'jiraField' => 'PRIORITY', 'control' => 'select', 'type' => 'int', 'length' => '3'); +$lang->convert->jira->buildinFields['resolution'] = array('name'=> 'Resolution', 'jiraField' => 'RESOLUTION', 'control' => 'select', 'type' => 'varchar', 'length' => '255'); +$lang->convert->jira->buildinFields['desc'] = array('name'=> 'Desc', 'jiraField' => 'DESCRIPTION', 'control' => 'richtext', 'type' => 'mediumtext', 'length' => '0'); diff --git a/module/convert/lang/fr.php b/module/convert/lang/fr.php index 60f297c1c3..7a3d229188 100644 --- a/module/convert/lang/fr.php +++ b/module/convert/lang/fr.php @@ -9,10 +9,10 @@ * @version $Id: en.php 4129 2013-01-18 01:58:14Z wwccss $ * @link https://www.zentao.net */ -$lang->convert->common = 'Importé'; -$lang->convert->index = 'Accueil'; +$lang->convert->common = 'Imported'; +$lang->convert->index = 'Home'; -$lang->convert->start = 'Commencer'; +$lang->convert->start = 'Start'; $lang->convert->desc = <<Welcome to the System Conversion Wizard, this program will assist you to convert data to ZenTao.

    There are risks in the conversion, so it is strongly recommended that you back up your databse and relavant files before conversion, and make sure that no one is using either system. @@ -26,22 +26,32 @@ $lang->convert->checkRedmine = 'Check Redmine'; $lang->convert->convertRedmine = 'Convert Redmine'; $lang->convert->convertBugFree = 'Convert BugFree'; -$lang->convert->selectSource = 'Sélectionnez le système source et sa version'; -$lang->convert->mustSelectSource = "Vous devez sélectionner un système source."; +$lang->convert->selectSource = 'Select source system and its version'; +$lang->convert->mustSelectSource = "You must select a source system."; -$lang->convert->direction = "{$lang->executionCommon} convertis vers"; -$lang->convert->questionTypeOfRedmine = 'Type dans Redmine'; -$lang->convert->aimTypeOfZentao = 'Convertir vers Type dans ZenTao'; +$lang->convert->direction = "{$lang->executionCommon} converted to"; +$lang->convert->questionTypeOfRedmine = 'Type in Redmine'; +$lang->convert->aimTypeOfZentao = 'Convert to Type in ZenTao'; + +$lang->convert->jiraUserMode = array(); +$lang->convert->jiraUserMode['account'] = 'Use Jira Account'; +$lang->convert->jiraUserMode['email'] = 'Use Jira Email'; + +$lang->convert->confluenceUserMode = array(); +$lang->convert->confluenceUserMode['account'] = 'Use Confluence Account'; +$lang->convert->confluenceUserMode['email'] = 'Use Confluence Email'; $lang->convert->directionList['bug'] = 'Bug'; $lang->convert->directionList['task'] = 'Task'; -$lang->convert->directionList['story'] = 'Story'; +$lang->convert->directionList['story'] = $lang->SRCommon; $lang->convert->sourceList['BugFree'] = array('bugfree_1' => '1.x', 'bugfree_2' => '2.x'); $lang->convert->sourceList['Redmine'] = array('Redmine_1.1' => '1.1'); -$lang->convert->setting = 'Paramètres'; -$lang->convert->checkConfig = 'Vérifier les paramètres'; +$lang->convert->setting = 'Settings'; +$lang->convert->checkConfig = 'Check Settings'; +$lang->convert->add = 'Add'; +$lang->convert->title = 'Title'; $lang->convert->ok = ' OK '; $lang->convert->fail = ' Failed'; @@ -50,7 +60,7 @@ $lang->convert->dbHost = 'Database Server'; $lang->convert->dbPort = 'Server Port'; $lang->convert->dbUser = 'Database User Name'; $lang->convert->dbPassword = 'Database Password'; -$lang->convert->dbName = 'Database utilisée dans %s'; +$lang->convert->dbName = 'Database used in %s'; $lang->convert->dbCharset = '%s Database Coding'; $lang->convert->dbPrefix = '%s Table Prefix'; $lang->convert->installPath = '%s Installation Root Directory'; @@ -59,9 +69,9 @@ $lang->convert->checkDB = 'Database'; $lang->convert->checkTable = 'Table'; $lang->convert->checkPath = 'Installation Path'; -$lang->convert->execute = 'Convertir'; -$lang->convert->item = 'Item Convertis'; -$lang->convert->count = 'Non.'; +$lang->convert->execute = 'Convert'; +$lang->convert->item = 'Item Converted'; +$lang->convert->count = 'No.'; $lang->convert->info = 'Info'; $lang->convert->bugfree = new stdclass(); @@ -69,14 +79,14 @@ $lang->convert->bugfree->users = 'User'; $lang->convert->bugfree->executions = $lang->executionCommon; $lang->convert->bugfree->modules = 'Module'; $lang->convert->bugfree->bugs = 'Bug'; -$lang->convert->bugfree->cases = 'CasTests'; -$lang->convert->bugfree->results = 'Résultats'; -$lang->convert->bugfree->actions = 'Historique'; -$lang->convert->bugfree->files = 'Fichiers'; +$lang->convert->bugfree->cases = 'Test Case'; +$lang->convert->bugfree->results = 'Result'; +$lang->convert->bugfree->actions = 'History'; +$lang->convert->bugfree->files = 'Files'; $lang->convert->redmine = new stdclass(); $lang->convert->redmine->users = 'User'; -$lang->convert->redmine->groups = 'Groupe'; +$lang->convert->redmine->groups = 'Group'; $lang->convert->redmine->products = $lang->productCommon; $lang->convert->redmine->executions = $lang->executionCommon; $lang->convert->redmine->stories = 'Story'; @@ -88,42 +98,48 @@ $lang->convert->redmine->releases = 'Release'; $lang->convert->redmine->builds = 'Build'; $lang->convert->redmine->docLibs = 'Doc Lib'; $lang->convert->redmine->docs = 'Doc'; -$lang->convert->redmine->files = 'Fichiers'; +$lang->convert->redmine->files = 'Files'; -$lang->convert->errorFileNotExits = 'Fichier %s non trouvé.'; -$lang->convert->errorUserExists = 'Utilisation %s existant.'; -$lang->convert->errorGroupExists = 'Groupe %s existant.'; -$lang->convert->errorBuildExists = 'Build %s existant.'; -$lang->convert->errorReleaseExists = 'Release %s existante.'; -$lang->convert->errorCopyFailed = 'Echec de la copie du fichier %s.'; +$lang->convert->errorFileNotExits = 'File %s is not found.'; +$lang->convert->errorUserExists = 'User %s existed.'; +$lang->convert->errorGroupExists = 'Group %s existed.'; +$lang->convert->errorBuildExists = 'Build %s existed.'; +$lang->convert->errorReleaseExists = 'Release %s existed.'; +$lang->convert->errorCopyFailed = 'File %s copy failed.'; -$lang->convert->setParam = 'Fixer les paramètres.'; +$lang->convert->setParam = 'Set parameters.'; $lang->convert->statusType = new stdclass(); $lang->convert->priType = new stdclass(); -$lang->convert->aimType = 'Problème de conversion'; -$lang->convert->statusType->bug = 'Convert Statut (Bug Statut)'; -$lang->convert->statusType->story = 'Convert Statut (Story Statut)'; -$lang->convert->statusType->task = 'Convert Statut (Task Statut)'; -$lang->convert->priType->bug = 'Convert Priorité (Bug Statut)'; -$lang->convert->priType->story = 'Convert Priorité (Story Statut)'; -$lang->convert->priType->task = 'Convert Priorité (Task Statut)'; +$lang->convert->aimType = 'Convert Issue'; +$lang->convert->statusType->bug = 'Convert Status (Bug Status)'; +$lang->convert->statusType->story = 'Convert Status (Story Status)'; +$lang->convert->statusType->task = 'Convert Status (Task Status)'; +$lang->convert->priType->bug = 'Convert Priority (Bug Status)'; +$lang->convert->priType->story = 'Convert Priority (Story Status)'; +$lang->convert->priType->task = 'Convert Priority (Task Status)'; $lang->convert->issue = new stdclass(); $lang->convert->issue->redmine = 'Redmine'; $lang->convert->issue->zentao = 'ZenTao'; -$lang->convert->issue->goto = 'Convertir vers'; +$lang->convert->issue->goto = 'Convert To'; $lang->convert->jira = new stdclass(); $lang->convert->jira->method = 'Chose Import Method'; +$lang->convert->jira->back = 'Back'; $lang->convert->jira->next = 'Next'; $lang->convert->jira->importFromDB = 'Import From Database'; $lang->convert->jira->importFromFile = 'Import From File'; $lang->convert->jira->mapJira2Zentao = 'Map Jira To Zentao'; $lang->convert->jira->database = 'Jira Database'; +$lang->convert->jira->domain = 'Jira Domain'; +$lang->convert->jira->admin = 'Jira Account'; +$lang->convert->jira->token = 'Jira Passwork/Token'; $lang->convert->jira->dbNameNotice = "Please enter the Jira database name."; $lang->convert->jira->importNotice = 'Notice: Importing data is risky! Make sure to complete the following steps in sequence before merging.'; +$lang->convert->jira->accountNotice = 'Those who use email will use the string before @ as their username, and those exceeding 30 characters will be truncated.'; +$lang->convert->jira->apiError = 'Unable to connect to Jira API interface, please check your Jira domain name and account, password/Token information.'; $lang->convert->jira->dbDesc = 'If your Jira uses MySQL database, please choose this way.'; $lang->convert->jira->fileDesc = 'Choose this method if your Jira uses a non-MySQL database.'; $lang->convert->jira->jiraObject = 'Jira Issues'; @@ -138,6 +154,20 @@ $lang->convert->jira->storyStatus = 'Zentao Story Status'; $lang->convert->jira->storyStage = 'Zentao Story Stage'; $lang->convert->jira->bugStatus = 'Zentao Bug Status'; $lang->convert->jira->taskStatus = 'Zentao Task Status'; +$lang->convert->jira->objectField = 'Field mapping'; +$lang->convert->jira->objectStatus = 'Status mapping'; +$lang->convert->jira->objectAction = 'Action mapping'; +$lang->convert->jira->objectResolution = 'Resolution mapping'; +$lang->convert->jira->jiraField = 'Jira %s Field'; +$lang->convert->jira->jiraStatus = 'Jira %s Status'; +$lang->convert->jira->jiraAction = 'Jira %s Action'; +$lang->convert->jira->jiraResolution = 'Jira %s Resolution'; +$lang->convert->jira->zentaoField = 'Zentao %s Field'; +$lang->convert->jira->zentaoStatus = 'Zentao %s Status'; +$lang->convert->jira->zentaoStage = 'Zentao %s Stage'; +$lang->convert->jira->zentaoAction = 'Zentao %s Action'; +$lang->convert->jira->zentaoReason = 'Zentao %s Closed Reason'; +$lang->convert->jira->zentaoResolution = 'Zentao %s Resolution'; $lang->convert->jira->initJiraUser = 'Init Jira User'; $lang->convert->jira->importJira = 'Import Jira'; $lang->convert->jira->start = 'Start'; @@ -147,9 +177,14 @@ $lang->convert->jira->invalidDB = 'Invalid database name!'; $lang->convert->jira->invalidTable = 'This database is not a Jira database!'; $lang->convert->jira->notReadAndWrite = 'Please modify the %s directory permissions!'; $lang->convert->jira->notExistEntities = 'The %s file does not exist!'; -$lang->convert->jira->passwordNotice = 'Set the default password of Jira users after they are imported into ZenTao. Users can modify the password in ZenTao later.'; -$lang->convert->jira->groupNotice = 'Set the default permission grouping of Jira users after importing into ZenTao.'; +$lang->convert->jira->passwordNotice = 'Set the default password of users after they are imported into ZenTao. Users can modify the password in ZenTao later.'; +$lang->convert->jira->groupNotice = 'Set the default permission grouping of users after importing into ZenTao.'; $lang->convert->jira->passwordDifferent = 'Password do not match!'; +$lang->convert->jira->mapObjectNotice = 'When selecting a mapping relationship, if you choose to add a new workflow, a new object will be automatically created in the workflow after import.'; +$lang->convert->jira->mapFieldNotice = 'The built-in fields in JIRA have been automatically matched. Please select the mapping relationship for custom fields. When selecting the mapping relationship, if you choose to add, new fields will be automatically created after import, and unselected fields will not be imported.'; +$lang->convert->jira->mapStatusNotice = 'When selecting a mapping relationship, unselected states are imported and automatically matched to %s.'; +$lang->convert->jira->mapReasonNotice = 'When selecting a mapping relationship, if "Add" is chosen, a new solution will be automatically created after import. Solutions not selected will be matched as "Completed" by default after import.'; +$lang->convert->jira->mapRelationNotice = 'When selecting a mapping relationship, if you choose to add, an association relationship will be automatically created after import, and unselected associations will not be imported.'; $lang->convert->jira->passwordEmpty = 'Password can not be empty!'; $lang->convert->jira->passwordLess = 'Password must not be less than six characters!'; $lang->convert->jira->importSuccessfully = 'Done!'; @@ -157,11 +192,14 @@ $lang->convert->jira->importResult = "Import %sconvert->jira->importing = 'Data is being imported, please do not switch to other pages.'; $lang->convert->jira->importingAB = 'Data Import'; $lang->convert->jira->imported = 'Data Import Complete'; +$lang->convert->jira->restore = 'The last import of information was not completed. Do you want to continue filling in from the last process?'; -$lang->convert->jira->zentaoObjectList[''] = ''; -$lang->convert->jira->zentaoObjectList['task'] = 'Task'; +$lang->convert->jira->zentaoObjectList[''] = ''; +$lang->convert->jira->zentaoObjectList['epic'] = 'Epic'; $lang->convert->jira->zentaoObjectList['requirement'] = 'Requirement'; $lang->convert->jira->zentaoObjectList['story'] = 'Story'; +$lang->convert->jira->zentaoObjectList['task'] = 'Task'; +$lang->convert->jira->zentaoObjectList['testcase'] = 'Case'; $lang->convert->jira->zentaoObjectList['bug'] = 'Bug'; $lang->convert->jira->zentaoLinkTypeList['subTaskLink'] = 'Parent-Child Task'; @@ -169,10 +207,11 @@ $lang->convert->jira->zentaoLinkTypeList['subStoryLink'] = 'Parent-Child Story'; $lang->convert->jira->zentaoLinkTypeList['duplicate'] = 'Duplicate'; $lang->convert->jira->zentaoLinkTypeList['relates'] = 'Relates'; -$lang->convert->jira->steps[1] = 'Objects'; -$lang->convert->jira->steps[2] = 'Relates'; -$lang->convert->jira->steps[3] = 'Resolution'; -$lang->convert->jira->steps[4] = 'Status'; +$lang->convert->jira->steps['object'] = 'Objects'; +$lang->convert->jira->steps['objectData'] = 'Object Data'; +$lang->convert->jira->steps['relation'] = 'Relation'; +$lang->convert->jira->steps['user'] = 'Import Jira User'; +$lang->convert->jira->steps['confirme'] = 'Import Data'; $lang->convert->jira->importSteps['db'][1] = 'Backup ZenTao database, backup Jira database.'; $lang->convert->jira->importSteps['db'][2] = 'Using ZenTao when importing data will cause performance pressure on the server, please try to ensure that no one is using ZenTao when importing data.'; @@ -184,12 +223,20 @@ $lang->convert->jira->importSteps['file'][1] = 'Backup ZenTao database, backup J $lang->convert->jira->importSteps['file'][2] = 'Using ZenTao when importing data will cause performance pressure on the server, please try to ensure that no one is using ZenTao when importing data.'; $lang->convert->jira->importSteps['file'][3] = "Put the Jira backup file entities.xml under %s."; $lang->convert->jira->importSteps['file'][4] = "Put the Jira attachments directory under %s, Make sure you have enough disk space on the ZenTao server."; -$lang->convert->jira->importSteps['file'][5] = "After the above steps are completed, click Next."; +$lang->convert->jira->importSteps['file'][5] = "To ensure the integrity of the imported data, please enter the domain name, administrator account, password/Token of the current Jira environment."; +$lang->convert->jira->importSteps['file'][6] = "After the above steps are completed, click Next."; $lang->convert->jira->objectList['user'] = 'User'; $lang->convert->jira->objectList['project'] = 'Project'; $lang->convert->jira->objectList['issue'] = 'Issue'; $lang->convert->jira->objectList['build'] = 'Build'; $lang->convert->jira->objectList['issuelink'] = 'Issue Link'; +$lang->convert->jira->objectList['worklog'] = 'Worklog'; $lang->convert->jira->objectList['action'] = 'Action'; $lang->convert->jira->objectList['file'] = 'File'; + +$lang->convert->jira->buildinFields = array(); +$lang->convert->jira->buildinFields['summary'] = array('name'=> 'Summary', 'jiraField' => 'SUMMARY', 'control' => 'input', 'type' => 'varchar', 'length' => '255'); +$lang->convert->jira->buildinFields['pri'] = array('name'=> 'Pri', 'jiraField' => 'PRIORITY', 'control' => 'select', 'type' => 'int', 'length' => '3'); +$lang->convert->jira->buildinFields['resolution'] = array('name'=> 'Resolution', 'jiraField' => 'RESOLUTION', 'control' => 'select', 'type' => 'varchar', 'length' => '255'); +$lang->convert->jira->buildinFields['desc'] = array('name'=> 'Desc', 'jiraField' => 'DESCRIPTION', 'control' => 'richtext', 'type' => 'mediumtext', 'length' => '0'); diff --git a/module/convert/lang/zh-cn.php b/module/convert/lang/zh-cn.php index 84498c99cb..94c396504f 100644 --- a/module/convert/lang/zh-cn.php +++ b/module/convert/lang/zh-cn.php @@ -33,6 +33,14 @@ $lang->convert->direction = "请选择{$lang->executionCommon}问题 $lang->convert->questionTypeOfRedmine = 'Redmine中问题类型'; $lang->convert->aimTypeOfZentao = '转化为Zentao中的类型'; +$lang->convert->jiraUserMode = array(); +$lang->convert->jiraUserMode['account'] = '使用Jira帐号'; +$lang->convert->jiraUserMode['email'] = '使用Jira邮箱'; + +$lang->convert->confluenceUserMode = array(); +$lang->convert->confluenceUserMode['account'] = '使用Confluence帐号'; +$lang->convert->confluenceUserMode['email'] = '使用Confluence邮箱'; + $lang->convert->directionList['bug'] = 'Bug'; $lang->convert->directionList['task'] = '任务'; $lang->convert->directionList['story'] = $lang->SRCommon; @@ -42,6 +50,8 @@ $lang->convert->sourceList['Redmine'] = array('Redmine_1.1' => '1.1'); $lang->convert->setting = '设置'; $lang->convert->checkConfig = '检查配置'; +$lang->convert->add = '新增'; +$lang->convert->title = '标题'; $lang->convert->ok = ' 检查通过'; $lang->convert->fail = ' 检查失败'; @@ -117,13 +127,19 @@ $lang->convert->issue->goto = '转换为'; $lang->convert->jira = new stdclass(); $lang->convert->jira->method = '选择导入方式'; +$lang->convert->jira->back = '上一步'; $lang->convert->jira->next = '下一步'; $lang->convert->jira->importFromDB = '从数据库导入'; $lang->convert->jira->importFromFile = '从文件导入'; $lang->convert->jira->mapJira2Zentao = '设置Jira与禅道数据对应关系'; $lang->convert->jira->database = 'Jira数据库'; +$lang->convert->jira->domain = 'Jira域名'; +$lang->convert->jira->admin = 'Jira管理员帐号'; +$lang->convert->jira->token = 'Jira密码/Token'; $lang->convert->jira->dbNameNotice = '请输入Jira数据库名字'; $lang->convert->jira->importNotice = '注意:导入数据有风险!请务必确保如下操作步骤依次完成,再进行合并。'; +$lang->convert->jira->accountNotice = '使用邮箱的会将@前面的字符串作为用户名,超过30位的会被截断处理。'; +$lang->convert->jira->apiError = '无法连接到JiraAPI接口,请检查您的Jira域名和帐号、密码/Token信息。'; $lang->convert->jira->dbDesc = '如果您的Jira使用Mysql数据库, 请选择此方式'; $lang->convert->jira->fileDesc = '如果您的Jira使用非Mysql数据库, 请选择此方式'; $lang->convert->jira->jiraObject = 'Jira Issues'; @@ -138,6 +154,20 @@ $lang->convert->jira->storyStatus = '禅道需求状态'; $lang->convert->jira->storyStage = '禅道需求阶段'; $lang->convert->jira->bugStatus = '禅道Bug状态'; $lang->convert->jira->taskStatus = '禅道任务状态'; +$lang->convert->jira->objectField = '对象字段映射'; +$lang->convert->jira->objectStatus = '对象状态映射'; +$lang->convert->jira->objectAction = '对象动作映射'; +$lang->convert->jira->objectResolution = '对象解决方案映射'; +$lang->convert->jira->jiraField = 'Jira%s字段'; +$lang->convert->jira->jiraStatus = 'Jira%s状态'; +$lang->convert->jira->jiraAction = 'Jira%s动作'; +$lang->convert->jira->jiraResolution = 'Jira%s解决方案'; +$lang->convert->jira->zentaoField = '禅道%s字段'; +$lang->convert->jira->zentaoStatus = '禅道%s状态'; +$lang->convert->jira->zentaoStage = '禅道%s阶段'; +$lang->convert->jira->zentaoAction = '禅道%s动作'; +$lang->convert->jira->zentaoReason = '禅道%s关闭原因'; +$lang->convert->jira->zentaoResolution = '禅道%s解决方案'; $lang->convert->jira->initJiraUser = '设置Jira用户'; $lang->convert->jira->importJira = '导入Jira'; $lang->convert->jira->start = '开始导入'; @@ -147,8 +177,13 @@ $lang->convert->jira->invalidDB = '无效的数据库名!'; $lang->convert->jira->invalidTable = '本数据库非Jira数据库!'; $lang->convert->jira->notReadAndWrite = '权限不足!请修改%s目录读写权限!'; $lang->convert->jira->notExistEntities = '%s 文件不存在!'; -$lang->convert->jira->passwordNotice = '设置Jira用户导入到禅道后的默认密码,用户后续可以在禅道中自行修改密码。'; -$lang->convert->jira->groupNotice = '设置Jira用户导入到禅道后的默认权限分组。'; +$lang->convert->jira->passwordNotice = '设置用户导入到禅道后的默认密码,用户后续可以在禅道中自行修改密码。'; +$lang->convert->jira->groupNotice = '设置用户导入到禅道后的默认权限分组。'; +$lang->convert->jira->mapObjectNotice = '选择映射关系时,如果选择新增成工作流,导入后将自动在工作流中创建一个新对象。'; +$lang->convert->jira->mapFieldNotice = 'jira内置字段已自动匹配,请选择自定义字段的映射关系,选择映射关系时,若选择新增,导入后将自动创建新字段,未选择的字段则不会导入。'; +$lang->convert->jira->mapStatusNotice = '选择映射关系时,未选择的状态导入后默认匹配为%s。'; +$lang->convert->jira->mapReasonNotice = '选择映射关系时,若选择新增,导入后将自动创建新解决方案,未选择的解决方案导入后默认匹配为已完成。'; +$lang->convert->jira->mapRelationNotice = '选择映射关系时,若选择新增,导入后将自动创建关联关系,未选择的关联关系不导入。'; $lang->convert->jira->passwordDifferent = '两次密码不一致!'; $lang->convert->jira->passwordEmpty = '密码不能为空!'; $lang->convert->jira->passwordLess = '密码不能少于六位!'; @@ -157,11 +192,14 @@ $lang->convert->jira->importResult = "导入 % $lang->convert->jira->importing = '数据导入中,请不要切换其它页面'; $lang->convert->jira->importingAB = '数据导入中'; $lang->convert->jira->imported = '数据导入完成'; +$lang->convert->jira->restore = '上次导入信息没有完成,是否从上次流程继续填写?'; -$lang->convert->jira->zentaoObjectList[''] = ''; -$lang->convert->jira->zentaoObjectList['task'] = '任务'; +$lang->convert->jira->zentaoObjectList[''] = ''; +$lang->convert->jira->zentaoObjectList['epic'] = '业务需求'; $lang->convert->jira->zentaoObjectList['requirement'] = '用户需求'; $lang->convert->jira->zentaoObjectList['story'] = '软件需求'; +$lang->convert->jira->zentaoObjectList['task'] = '任务'; +$lang->convert->jira->zentaoObjectList['testcase'] = '用例'; $lang->convert->jira->zentaoObjectList['bug'] = 'Bug'; $lang->convert->jira->zentaoLinkTypeList['subTaskLink'] = '父-子任务'; @@ -169,27 +207,36 @@ $lang->convert->jira->zentaoLinkTypeList['subStoryLink'] = '父-子需求'; $lang->convert->jira->zentaoLinkTypeList['duplicate'] = '重复对象'; $lang->convert->jira->zentaoLinkTypeList['relates'] = '互相关联'; -$lang->convert->jira->steps[1] = '对象'; -$lang->convert->jira->steps[2] = '对象关联关系'; -$lang->convert->jira->steps[3] = '解决方案'; -$lang->convert->jira->steps[4] = '状态'; +$lang->convert->jira->steps['object'] = '对象映射'; +$lang->convert->jira->steps['objectData'] = '对象数据映射'; +$lang->convert->jira->steps['relation'] = '全局关联关系映射'; +$lang->convert->jira->steps['user'] = '导入Jira用户'; +$lang->convert->jira->steps['confirme'] = '导入数据确认'; $lang->convert->jira->importSteps['db'][1] = '备份禅道数据库,备份Jira数据库。'; $lang->convert->jira->importSteps['db'][2] = '导入数据时使用禅道会给服务器造成性能压力,请尽量保证导入数据时无人使用禅道。'; $lang->convert->jira->importSteps['db'][3] = '将Jira数据库导入到禅道使用的Mysql中,名字和禅道数据库区别开来。'; $lang->convert->jira->importSteps['db'][4] = "将Jira附件目录 attachments 放到 %s 下,确保禅道服务器磁盘空间足够。"; - $lang->convert->jira->importSteps['db'][5] = "上述步骤完成后,请输入Jira数据库名字进行下一步。"; + $lang->convert->jira->importSteps['file'][1] = '备份禅道数据库,备份Jira数据库。'; $lang->convert->jira->importSteps['file'][2] = '导入数据时使用禅道会给服务器造成性能压力,请尽量保证导入数据时无人使用禅道。'; $lang->convert->jira->importSteps['file'][3] = "将Jira的备份文件 entities.xml 放到 %s 下,并给该目录读写权限。"; $lang->convert->jira->importSteps['file'][4] = "将Jira附件目录 attachments 放到 %s 下,确保禅道服务器磁盘空间足够。"; -$lang->convert->jira->importSteps['file'][5] = "上述步骤完成后,点击下一步。"; +$lang->convert->jira->importSteps['file'][5] = "为了保证导入数据的完整性,请输入当前Jira环境的域名、管理员帐号、密码/Token。"; +$lang->convert->jira->importSteps['file'][6] = "上述步骤完成后,点击下一步。"; $lang->convert->jira->objectList['user'] = '用户'; $lang->convert->jira->objectList['project'] = '项目'; $lang->convert->jira->objectList['issue'] = 'Issue'; $lang->convert->jira->objectList['build'] = '构建'; $lang->convert->jira->objectList['issuelink'] = '关联关系'; +$lang->convert->jira->objectList['worklog'] = '工作日志'; $lang->convert->jira->objectList['action'] = '历史记录'; $lang->convert->jira->objectList['file'] = '附件'; + +$lang->convert->jira->buildinFields = array(); +$lang->convert->jira->buildinFields['summary'] = array('name'=> '标题', 'jiraField' => 'SUMMARY', 'control' => 'input', 'type' => 'varchar', 'length' => '255'); +$lang->convert->jira->buildinFields['pri'] = array('name'=> '优先级', 'jiraField' => 'PRIORITY', 'control' => 'select', 'type' => 'int', 'length' => '3'); +$lang->convert->jira->buildinFields['resolution'] = array('name'=> '解决方案', 'jiraField' => 'RESOLUTION', 'control' => 'select', 'type' => 'varchar', 'length' => '255'); +$lang->convert->jira->buildinFields['desc'] = array('name'=> '描述', 'jiraField' => 'DESCRIPTION', 'control' => 'richtext', 'type' => 'mediumtext', 'length' => '0'); diff --git a/module/convert/lang/zh-tw.php b/module/convert/lang/zh-tw.php index bb7ee92b0d..a3177f4ba5 100644 --- a/module/convert/lang/zh-tw.php +++ b/module/convert/lang/zh-tw.php @@ -1,116 +1,221 @@ * @package convert - * @version $Id: zh-tw.php 4129 2013-01-18 01:58:14Z wwccss $ + * @version $Id: zh-cn.php 4129 2013-01-18 01:58:14Z wwccss $ * @link https://www.zentao.net */ -$lang->convert->common = '從其他系統導入'; -$lang->convert->index = '首頁'; +$lang->convert->common = '从其他系统导入'; +$lang->convert->index = '首页'; -$lang->convert->start = '開始轉換'; +$lang->convert->start = '开始转换'; $lang->convert->desc = <<歡迎使用系統轉換嚮導,本程序會幫助您將其他系統的數據轉換到禪道項目管理系統中。

    -轉換存在一定的風險,轉換之前,我們強烈建議您備份資料庫及相應的數據檔案,並保證轉換的時候,沒有其他人進行操作。 +

    欢迎使用系统转换向导,本程序会帮助您将其他系统的数据转换到禅道项目管理系统中。

    +转换存在一定的风险,转换之前,我们强烈建议您备份数据库及相应的数据文件,并保证转换的时候,没有其他人进行操作。 EOT; -$lang->convert->setConfig = '來源系統配置'; +$lang->convert->setConfig = '来源系统配置'; $lang->convert->setBugfree = 'Bugfree配置'; $lang->convert->setRedmine = 'Redmine配置'; -$lang->convert->checkBugFree = '檢查Bugfree'; -$lang->convert->checkRedmine = '檢查Redmine'; -$lang->convert->convertRedmine = '轉換Redmine'; -$lang->convert->convertBugFree = '轉換BugFree'; +$lang->convert->checkBugFree = '检查Bugfree'; +$lang->convert->checkRedmine = '检查Redmine'; +$lang->convert->convertRedmine = '转换Redmine'; +$lang->convert->convertBugFree = '转换BugFree'; -$lang->convert->selectSource = '選擇來源系統及版本'; -$lang->convert->mustSelectSource = "必須選擇一個來源。"; +$lang->convert->selectSource = '选择来源系统及版本'; +$lang->convert->mustSelectSource = "必须选择一个来源。"; -$lang->convert->direction = "請選擇{$lang->executionCommon}問題轉換方向"; -$lang->convert->questionTypeOfRedmine = 'Redmine中問題類型'; -$lang->convert->aimTypeOfZentao = '轉化為Zentao中的類型'; +$lang->convert->direction = "请选择{$lang->executionCommon}问题转换方向"; +$lang->convert->questionTypeOfRedmine = 'Redmine中问题类型'; +$lang->convert->aimTypeOfZentao = '转化为Zentao中的类型'; + +$lang->convert->jiraUserMode = array(); +$lang->convert->jiraUserMode['account'] = '使用Jira帐号'; +$lang->convert->jiraUserMode['email'] = '使用Jira邮箱'; $lang->convert->directionList['bug'] = 'Bug'; -$lang->convert->directionList['task'] = '任務'; +$lang->convert->directionList['task'] = '任务'; $lang->convert->directionList['story'] = $lang->SRCommon; $lang->convert->sourceList['BugFree'] = array('bugfree_1' => '1.x', 'bugfree_2' => '2.x'); $lang->convert->sourceList['Redmine'] = array('Redmine_1.1' => '1.1'); -$lang->convert->setting = '設置'; -$lang->convert->checkConfig = '檢查配置'; +$lang->convert->setting = '设置'; +$lang->convert->checkConfig = '检查配置'; +$lang->convert->add = '新增'; +$lang->convert->title = '标题'; -$lang->convert->ok = ' 檢查通過'; -$lang->convert->fail = ' 檢查失敗'; +$lang->convert->ok = ' 检查通过'; +$lang->convert->fail = ' 检查失败'; -$lang->convert->dbHost = '資料庫伺服器'; -$lang->convert->dbPort = '伺服器連接埠'; -$lang->convert->dbUser = '資料庫用戶名'; -$lang->convert->dbPassword = '資料庫密碼'; -$lang->convert->dbName = '%s使用的庫'; -$lang->convert->dbCharset = '%s資料庫編碼'; -$lang->convert->dbPrefix = '%s表首碼'; -$lang->convert->installPath = '%s安裝的根目錄'; +$lang->convert->dbHost = '数据库服务器'; +$lang->convert->dbPort = '服务器端口'; +$lang->convert->dbUser = '数据库用户名'; +$lang->convert->dbPassword = '数据库密码'; +$lang->convert->dbName = '%s使用的库'; +$lang->convert->dbCharset = '%s数据库编码'; +$lang->convert->dbPrefix = '%s表前缀'; +$lang->convert->installPath = '%s安装的根目录'; -$lang->convert->checkDB = '資料庫'; +$lang->convert->checkDB = '数据库'; $lang->convert->checkTable = '表'; -$lang->convert->checkPath = '安裝路徑'; +$lang->convert->checkPath = '安装路径'; -$lang->convert->execute = '執行轉換'; -$lang->convert->item = '轉換項'; -$lang->convert->count = '轉換數量'; -$lang->convert->info = '轉換信息'; +$lang->convert->execute = '执行转换'; +$lang->convert->item = '转换项'; +$lang->convert->count = '转换数量'; +$lang->convert->info = '转换信息'; $lang->convert->bugfree = new stdclass(); -$lang->convert->bugfree->users = '用戶'; +$lang->convert->bugfree->users = '用户'; $lang->convert->bugfree->executions = $lang->executionCommon; -$lang->convert->bugfree->modules = '模組'; +$lang->convert->bugfree->modules = '模块'; $lang->convert->bugfree->bugs = 'Bug'; -$lang->convert->bugfree->cases = '測試用例'; -$lang->convert->bugfree->results = '測試結果'; -$lang->convert->bugfree->actions = '歷史記錄'; +$lang->convert->bugfree->cases = '测试用例'; +$lang->convert->bugfree->results = '测试结果'; +$lang->convert->bugfree->actions = '历史记录'; $lang->convert->bugfree->files = '附件'; $lang->convert->redmine = new stdclass(); -$lang->convert->redmine->users = '用戶'; -$lang->convert->redmine->groups = '用戶分組'; +$lang->convert->redmine->users = '用户'; +$lang->convert->redmine->groups = '用户分组'; $lang->convert->redmine->products = $lang->productCommon; $lang->convert->redmine->executions = $lang->executionCommon; $lang->convert->redmine->stories = $lang->SRCommon; -$lang->convert->redmine->tasks = '任務'; +$lang->convert->redmine->tasks = '任务'; $lang->convert->redmine->bugs = 'Bug'; -$lang->convert->redmine->productPlans = $lang->productCommon . '計劃'; -$lang->convert->redmine->teams = '團隊'; -$lang->convert->redmine->releases = '發佈'; +$lang->convert->redmine->productPlans = $lang->productCommon . '计划'; +$lang->convert->redmine->teams = '团队'; +$lang->convert->redmine->releases = '发布'; $lang->convert->redmine->builds = 'Build'; -$lang->convert->redmine->docLibs = '文檔庫'; -$lang->convert->redmine->docs = '文檔'; +$lang->convert->redmine->docLibs = '文档库'; +$lang->convert->redmine->docs = '文档'; $lang->convert->redmine->files = '附件'; -$lang->convert->errorFileNotExits = '檔案 %s 不存在'; -$lang->convert->errorUserExists = '用戶 %s 已存在'; -$lang->convert->errorGroupExists = '分組 %s 已存在'; +$lang->convert->errorFileNotExits = '文件 %s 不存在'; +$lang->convert->errorUserExists = '用户 %s 已存在'; +$lang->convert->errorGroupExists = '分组 %s 已存在'; $lang->convert->errorBuildExists = 'Build %s 已存在'; -$lang->convert->errorReleaseExists = '發佈 %s 已存在'; -$lang->convert->errorCopyFailed = '檔案 %s 拷貝失敗'; +$lang->convert->errorReleaseExists = '发布 %s 已存在'; +$lang->convert->errorCopyFailed = '文件 %s 拷贝失败'; -$lang->convert->setParam = '請設置轉換參數'; +$lang->convert->setParam = '请设置转换参数'; $lang->convert->statusType = new stdclass(); $lang->convert->priType = new stdclass(); -$lang->convert->aimType = '問題類型轉換'; -$lang->convert->statusType->bug = '狀態類型轉換(Bug狀態)'; -$lang->convert->statusType->story = '狀態類型轉換(Story狀態)'; -$lang->convert->statusType->task = '狀態類型轉換(Task狀態)'; -$lang->convert->priType->bug = '優先順序類型轉換(Bug狀態)'; -$lang->convert->priType->story = '優先順序類型轉換(Story狀態)'; -$lang->convert->priType->task = '優先順序類型轉換(Task狀態)'; +$lang->convert->aimType = '问题类型转换'; +$lang->convert->statusType->bug = '状态类型转换(Bug状态)'; +$lang->convert->statusType->story = '状态类型转换(Story状态)'; +$lang->convert->statusType->task = '状态类型转换(Task状态)'; +$lang->convert->priType->bug = '优先级类型转换(Bug状态)'; +$lang->convert->priType->story = '优先级类型转换(Story状态)'; +$lang->convert->priType->task = '优先级类型转换(Task状态)'; $lang->convert->issue = new stdclass(); $lang->convert->issue->redmine = 'Redmine'; -$lang->convert->issue->zentao = '禪道'; -$lang->convert->issue->goto = '轉換為'; +$lang->convert->issue->zentao = '禅道'; +$lang->convert->issue->goto = '转换为'; + +$lang->convert->jira = new stdclass(); +$lang->convert->jira->method = '选择导入方式'; +$lang->convert->jira->back = '上一步'; +$lang->convert->jira->next = '下一步'; +$lang->convert->jira->importFromDB = '从数据库导入'; +$lang->convert->jira->importFromFile = '从文件导入'; +$lang->convert->jira->mapJira2Zentao = '设置Jira与禅道数据对应关系'; +$lang->convert->jira->database = 'Jira数据库'; +$lang->convert->jira->dbNameNotice = '请输入Jira数据库名字'; +$lang->convert->jira->importNotice = '注意:导入数据有风险!请务必确保如下操作步骤依次完成,再进行合并。'; +$lang->convert->jira->accountNotice = '使用邮箱的会将@前面的字符串作为用户名,超过30位的会被截断处理。'; +$lang->convert->jira->dbDesc = '如果您的Jira使用Mysql数据库, 请选择此方式'; +$lang->convert->jira->fileDesc = '如果您的Jira使用非Mysql数据库, 请选择此方式'; +$lang->convert->jira->jiraObject = 'Jira Issues'; +$lang->convert->jira->zentaoObject = '禅道对象'; +$lang->convert->jira->jiraLinkType = 'Jira 关联关系'; +$lang->convert->jira->zentaoLinkType = '禅道关联关系'; +$lang->convert->jira->jiraResolution = 'Jira 解决方案'; +$lang->convert->jira->zentaoResolution = '禅道Bug解决方案'; +$lang->convert->jira->zentaoReason = '禅道需求关闭原因'; +$lang->convert->jira->jiraStatus = 'Jira Issues 状态'; +$lang->convert->jira->storyStatus = '禅道需求状态'; +$lang->convert->jira->storyStage = '禅道需求阶段'; +$lang->convert->jira->bugStatus = '禅道Bug状态'; +$lang->convert->jira->taskStatus = '禅道任务状态'; +$lang->convert->jira->objectField = '对象字段映射'; +$lang->convert->jira->objectStatus = '对象状态映射'; +$lang->convert->jira->objectAction = '对象动作映射'; +$lang->convert->jira->objectResolution = '对象解决方案映射'; +$lang->convert->jira->jiraField = 'Jira%s字段'; +$lang->convert->jira->jiraStatus = 'Jira%s状态'; +$lang->convert->jira->jiraAction = 'Jira%s动作'; +$lang->convert->jira->jiraResolution = 'Jira%s解决方案'; +$lang->convert->jira->zentaoField = '禅道%s字段'; +$lang->convert->jira->zentaoStatus = '禅道%s状态'; +$lang->convert->jira->zentaoStage = '禅道%s阶段'; +$lang->convert->jira->zentaoAction = '禅道%s动作'; +$lang->convert->jira->zentaoReason = '禅道%s关闭原因'; +$lang->convert->jira->zentaoResolution = '禅道%s解决方案'; +$lang->convert->jira->initJiraUser = '设置Jira用户'; +$lang->convert->jira->importJira = '导入Jira'; +$lang->convert->jira->start = '开始导入'; + +$lang->convert->jira->dbNameEmpty = 'Jira数据库名字不能为空!'; +$lang->convert->jira->invalidDB = '无效的数据库名!'; +$lang->convert->jira->invalidTable = '本数据库非Jira数据库!'; +$lang->convert->jira->notReadAndWrite = '权限不足!请修改%s目录读写权限!'; +$lang->convert->jira->notExistEntities = '%s 文件不存在!'; +$lang->convert->jira->passwordNotice = '设置Jira用户导入到禅道后的默认密码,用户后续可以在禅道中自行修改密码。'; +$lang->convert->jira->groupNotice = '设置Jira用户导入到禅道后的默认权限分组。'; +$lang->convert->jira->passwordDifferent = '两次密码不一致!'; +$lang->convert->jira->passwordEmpty = '密码不能为空!'; +$lang->convert->jira->passwordLess = '密码不能少于六位!'; +$lang->convert->jira->importSuccessfully = 'Jira导入完成!'; +$lang->convert->jira->importResult = "导入 %s 数据, 已处理 %s 条记录;"; +$lang->convert->jira->importing = '数据导入中,请不要切换其它页面'; +$lang->convert->jira->importingAB = '数据导入中'; +$lang->convert->jira->imported = '数据导入完成'; +$lang->convert->jira->restore = '上次导入信息没有完成,是否从上次流程继续填写?'; + +$lang->convert->jira->zentaoObjectList[''] = ''; +$lang->convert->jira->zentaoObjectList['epic'] = '业务需求'; +$lang->convert->jira->zentaoObjectList['requirement'] = '用户需求'; +$lang->convert->jira->zentaoObjectList['story'] = '软件需求'; +$lang->convert->jira->zentaoObjectList['task'] = '任务'; +$lang->convert->jira->zentaoObjectList['testcase'] = '用例'; +$lang->convert->jira->zentaoObjectList['bug'] = 'Bug'; + +$lang->convert->jira->zentaoLinkTypeList['subTaskLink'] = '父-子任务'; +$lang->convert->jira->zentaoLinkTypeList['subStoryLink'] = '父-子需求'; +$lang->convert->jira->zentaoLinkTypeList['duplicate'] = '重复对象'; +$lang->convert->jira->zentaoLinkTypeList['relates'] = '互相关联'; + +$lang->convert->jira->steps['object'] = '对象映射'; +$lang->convert->jira->steps['objectData'] = '对象数据映射'; +$lang->convert->jira->steps['relation'] = '全局关联关系映射'; +$lang->convert->jira->steps['user'] = '导入Jira用户'; +$lang->convert->jira->steps['confirme'] = '导入数据确认'; + +$lang->convert->jira->importSteps['db'][1] = '备份禅道数据库,备份Jira数据库。'; +$lang->convert->jira->importSteps['db'][2] = '导入数据时使用禅道会给服务器造成性能压力,请尽量保证导入数据时无人使用禅道。'; +$lang->convert->jira->importSteps['db'][3] = '将Jira数据库导入到禅道使用的Mysql中,名字和禅道数据库区别开来。'; +$lang->convert->jira->importSteps['db'][4] = "将Jira附件目录 attachments 放到 %s 下,确保禅道服务器磁盘空间足够。"; +$lang->convert->jira->importSteps['db'][5] = "上述步骤完成后,请输入Jira数据库名字进行下一步。"; + +$lang->convert->jira->importSteps['file'][1] = '备份禅道数据库,备份Jira数据库。'; +$lang->convert->jira->importSteps['file'][2] = '导入数据时使用禅道会给服务器造成性能压力,请尽量保证导入数据时无人使用禅道。'; +$lang->convert->jira->importSteps['file'][3] = "将Jira的备份文件 entities.xml 放到 %s 下,并给该目录读写权限。"; +$lang->convert->jira->importSteps['file'][4] = "将Jira附件目录 attachments 放到 %s 下,确保禅道服务器磁盘空间足够。"; +$lang->convert->jira->importSteps['file'][5] = "上述步骤完成后,点击下一步。"; + +$lang->convert->jira->objectList['user'] = '用户'; +$lang->convert->jira->objectList['project'] = '项目'; +$lang->convert->jira->objectList['issue'] = 'Issue'; +$lang->convert->jira->objectList['build'] = '构建'; +$lang->convert->jira->objectList['issuelink'] = '关联关系'; +$lang->convert->jira->objectList['action'] = '历史记录'; +$lang->convert->jira->objectList['file'] = '附件'; diff --git a/module/convert/model.php b/module/convert/model.php index 54944af734..929c8e8d68 100644 --- a/module/convert/model.php +++ b/module/convert/model.php @@ -27,7 +27,7 @@ class convertModel extends model * 连接数据库。 * Connect to db. * - * @param string $dbName + * @param string $dbName * @access public * @return object|string */ @@ -57,7 +57,7 @@ class convertModel extends model * 检查数据库是否存在。 * Check database exits or not. * - * @param string $dbName + * @param string $dbName * @access public * @return object|false */ @@ -72,7 +72,7 @@ class convertModel extends model * 检查数据表是否存在。 * Check table exits or not. * - * @param string $table + * @param string $table * @access public * @return object|false */ @@ -86,8 +86,8 @@ class convertModel extends model * 检查jira数据库表是否存在。 * Check table of jira databases exits or not. * - * @param string $dbName - * @param string $table + * @param string $dbName + * @param string $table * @access public * @return object|false */ @@ -132,6 +132,72 @@ class convertModel extends model $this->session->set('state', $state); } + /** + * 获取jira的数据。 + * Get jira data. + * + * @param int $method + * @param string $module + * @param int $lastID + * @param int $limit + * @access public + * @return array + */ + public function getJiraData(string $method, string $module, int $lastID = 0, int $limit = 0): array + { + if($method == 'db') + { + $originDBH = $this->dbh; + $this->connectDB($this->session->jiraDB); + $result = $this->getJiraDataFromDB($module, $lastID, $limit); + $this->dao->dbh($originDBH); + return $result; + } + else + { + return $this->getJiraDataFromFile($module, $lastID, $limit); + } + } + + /** + * 从数据库获取jira数据。 + * Get jira data from db. + * + * @param string $module + * @param int $lastID + * @param int $limit + * @access public + * @return array + */ + public function getJiraDataFromDB(string $module = '', int $lastID = 0, int $limit = 0): array + { + $dataList = array(); + $table = zget($this->config->convert->objectTables, $module, ''); + if($module == 'user') + { + $dataList = $this->dao->dbh($this->sourceDBH)->select('t1.`ID`, t1.`lower_user_name` as account, t1.`lower_display_name` as realname, t1.`lower_email_address` as email, t1.created_date as `join`, t2.user_key as userCode')->from(JIRA_USERINFO)->alias('t1') + ->leftJoin(JIRA_USER)->alias('t2')->on('t1.`lower_user_name` = t2.`lower_user_name`') + ->where('1 = 1') + ->beginIF($lastID)->andWhere('t1.ID')->gt($lastID)->fi() + ->orderBy('t1.ID asc')->limit($limit) + ->fetchAll('ID'); + } + elseif($module == 'nodeassociation') + { + $dataList = $this->dao->dbh($this->sourceDBH)->select('*')->from($table)->limit($lastID, $limit)->fetchAll(); + } + elseif(!empty($table)) + { + $dataList = $this->dao->dbh($this->sourceDBH)->select('*')->from($table) + ->where('1 = 1') + ->beginIF($lastID)->andWhere('ID')->gt($lastID)->fi() + ->orderBy('ID asc')->limit($limit) + ->fetchAll('ID', false); + } + + return $dataList; + } + /** * 从文件中获取jira数据。 * Get jira data from file. @@ -156,7 +222,7 @@ class convertModel extends model $parsedXML = simplexml_load_string($xmlContent, 'SimpleXMLElement', LIBXML_NOCDATA); $dataList = array(); - $parsedXML = $this->convertTao->object2Array($parsedXML); + $parsedXML = $this->object2Array($parsedXML); foreach($parsedXML as $key => $xmlArray) { if(strtolower($key) != strtolower($fileName)) continue; @@ -168,18 +234,36 @@ class convertModel extends model $summary = isset($attributes['summary']) ? $attributes['summary'] : ''; $body = isset($attributes['body']) ? $attributes['body'] : ''; - foreach($attributes as $value) + $data = array(); + $dataID = 0; + foreach($attributes as $k => $value) { - if(!is_array($value)) continue; - if(!empty($desc)) $value['description'] = $desc; - if(!empty($summary)) $value['summary'] = $summary; - if(!empty($body)) $value['body'] = $body; - $dataList[$value['id']] = $value; + if(is_array($value)) + { + if(!empty($desc)) $value['description'] = $desc; + if(!empty($summary)) $value['summary'] = $summary; + if(!empty($body)) $value['body'] = $body; + $dataID = !empty($value['id']) ? $value['id'] : $key; + $data = array_merge($data, $value); + } + else + { + $data = array_merge($data, array($k => $value)); + } } + if($dataID) $dataList[$dataID] = $data; } else { - $dataList[$attributes['id']] = $attributes; + if(is_array($attributes)) + { + $dataID = !empty($attributes['id']) ? $attributes['id'] : $key; + $dataList[$dataID] = $attributes; + } + else + { + if(!empty($dataID)) $dataList[$dataID][$key] = $attributes; + } } } } @@ -195,117 +279,12 @@ class convertModel extends model if(!in_array($module, array_keys($this->config->convert->objectTables))) continue; $buildFunction = 'build' . ucfirst($module) . 'Data'; - $dataList[$key] = $this->convertTao->$buildFunction($data); + $dataList[$key] = $this->$buildFunction($data); } return $dataList; } - /** - * 从DB文件中导入jira数据。 - * Import jira from db. - * - * @param string $type user|project|issue|build|issuelink|action|file - * @param int $lastID - * @param bool $createTable - * @access public - * @return array - */ - public function importJiraFromDB(string $type = '', int $lastID = 0, bool $createTable = false): array - { - if($createTable) $this->createTmpTable4Jira(); - - $this->connectDB($this->session->jiraDB); - - $limit = 1000; - $nextObject = false; - if(empty($type)) $type = key($this->lang->convert->jira->objectList); - - foreach(array_keys($this->lang->convert->jira->objectList) as $module) - { - if($module != $type && !$nextObject) continue; - if($module == $type) $nextObject = true; - - $this->convertTao->sourceDBH = $this->sourceDBH; - - while(true) - { - $dataList = $this->convertTao->getJiraDataFromDB($module, $lastID, $limit); - - if(empty($dataList)) - { - $lastID = 0; - break; - } - - if($module == 'user') $this->convertTao->importJiraUser($dataList); - if($module == 'project') $this->convertTao->importJiraProject($dataList); - if($module == 'issue') $this->convertTao->importJiraIssue($dataList); - if($module == 'build') $this->convertTao->importJiraBuild($dataList); - if($module == 'issuelink') $this->convertTao->importJiraIssueLink($dataList); - if($module == 'action') $this->convertTao->importJiraAction($dataList); - if($module == 'file') $this->convertTao->importJiraFile($dataList); - - return array('type' => $module, 'count' => count($dataList), 'lastID' => max(array_keys($dataList))); - } - } - - $this->afterExec(); - return array('finished' => true); - } - - /** - * 从文件中导入jira数据。 - * Import jira from file. - * - * @param string $type user|project|issue|build|issuelink|action|file - * @param int $lastID - * @param bool $createTable - * @access public - * @return array - */ - public function importJiraFromFile(string $type = '', int $lastID = 0, bool $createTable = false): array - { - if($createTable) $this->createTmpTable4Jira(); - - $limit = 1000; - $nextObject = false; - if(empty($type)) $type = key($this->lang->convert->jira->objectList); - - foreach(array_keys($this->lang->convert->jira->objectList) as $module) - { - if($module != $type && !$nextObject) continue; - if($module == $type) $nextObject = true; - - $this->convertTao->sourceDBH = $this->sourceDBH; - - while(true) - { - $dataList = $this->getJiraDataFromFile($module, $lastID, $limit); - - if(empty($dataList)) - { - $lastID = 0; - break; - } - - if($module == 'user') $this->convertTao->importJiraUser($dataList); - if($module == 'project') $this->convertTao->importJiraProject($dataList, 'file'); - if($module == 'issue') $this->convertTao->importJiraIssue($dataList, 'file'); - if($module == 'build') $this->convertTao->importJiraBuild($dataList, 'file'); - if($module == 'issuelink') $this->convertTao->importJiraIssueLink($dataList, 'file'); - if($module == 'action') $this->convertTao->importJiraAction($dataList, 'file'); - if($module == 'file') $this->convertTao->importJiraFile($dataList, 'file'); - - $offset = $lastID + $limit; - return array('type' => $module, 'count' => count($dataList), 'lastID' => $offset); - } - } - - $this->afterExec('file'); - return array('finished' => true); - } - /** * 分割jira文件。 * Split jira file. @@ -320,7 +299,7 @@ class convertModel extends model $file = $filePath . $fileName; $handle = fopen($file, "r"); - $tagList = array(' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' ''); + $tagList = array(' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' '', ' ''); while(!feof($handle)) { @@ -388,15 +367,76 @@ EOT; catch(Exception $e){} } + /** + * 使用接口导入Jira数据。 + * Import jira from REST API. + * + * @param string $type user|project|issue|build|issuelink|action|file + * @param int $lastID + * @param bool $createTable + * @access public + * @return array + */ + public function importJiraData(string $type = '', int $lastID = 0, bool $createTable = false): array + { + if($createTable) $this->createTmpTable4Jira(); + + if($this->session->jiraMethod == 'db') $this->connectDB($this->session->jiraDB); + + $limit = 1000; + $nextObject = false; + if(empty($type)) $type = key($this->lang->convert->jira->objectList); + + foreach(array_keys($this->lang->convert->jira->objectList) as $module) + { + if($module != $type && !$nextObject) continue; + if($module == $type) $nextObject = true; + + $this->convertTao->sourceDBH = $this->sourceDBH; + + while(true) + { + $dataList = $this->getJiraData($this->session->jiraMethod, $module, $lastID, $limit); + + if(empty($dataList)) + { + $lastID = 0; + break; + } + + if($module == 'user') $this->convertTao->importJiraUser($dataList); + if($module == 'project') $this->convertTao->importJiraProject($dataList); + if($module == 'issue') $this->convertTao->importJiraIssue($dataList); + if($module == 'build') $this->convertTao->importJiraBuild($dataList); + if($module == 'issuelink') $this->convertTao->importJiraIssueLink($dataList); + if($module == 'worklog') $this->convertTao->importJiraWorkLog($dataList); + if($module == 'action') $this->convertTao->importJiraAction($dataList); + if($module == 'file') $this->convertTao->importJiraFile($dataList); + + $offset = $lastID + $limit; + + return array('type' => $module, 'count' => count($dataList), 'lastID' => $this->session->jiraMethod == 'db' ? max(array_keys($dataList)) : $offset); + } + } + + $this->afterExec(); + + unset($_SESSION['jiraDB']); + unset($_SESSION['jiraMethod']); + unset($_SESSION['jiraRelation']); + unset($_SESSION['stepStatus']); + unset($_SESSION['jiraUser']); + return array('finished' => true); + } + /** * 执行。 * After exec. * - * @param string $method db|file * @access public * @return void */ - public function afterExec(string $method = 'db'): void + public function afterExec(): void { /* Set project min start date. */ $minDate = date('Y-m-d', time() - 30 * 24 * 3600); @@ -411,9 +451,7 @@ EOT; $this->dao->update(TABLE_PROJECT)->set('begin')->eq($minOpenedDate)->where('id')->eq($projectID)->orWhere('id')->eq($executionID)->exec(); } - if($method == 'file') $this->deleteJiraFile(); - - $this->dbh->exec("DROP TABLE" . JIRA_TMPRELATION); + if($this->session->jiraMethod == 'file') $this->deleteJiraFile(); } /** @@ -425,7 +463,7 @@ EOT; */ public function deleteJiraFile(): void { - $fileList = array('action', 'project', 'status', 'resolution', 'user', 'issue', 'changegroup', 'changeitem', 'issuelink', 'issuelinktype', 'fileattachment', 'version', 'issuetype', 'nodeassociation', 'applicationuser'); + $fileList = array('action', 'project', 'status', 'resolution', 'user', 'issue', 'changegroup', 'changeitem', 'issuelink', 'issuelinktype', 'fileattachment', 'version', 'issuetype', 'nodeassociation', 'applicationuser', 'fieldscreenlayoutitem', 'workflow', 'workflowscheme', 'fieldconfigscheme', 'fieldconfigschemeissuetype', 'customfield', 'customfieldoption', 'customfieldvalue', 'ospropertyentry', 'worklog', 'auditlog', 'group', 'membership', 'projectroleactor', 'priority', 'configurationcontext', 'optionconfiguration'); foreach($fileList as $fileName) { $filePath = $this->app->getTmpRoot() . 'jirafile/' . $fileName . '.xml'; @@ -446,4 +484,711 @@ EOT; if(preg_match('/^[a-zA-Z][a-zA-Z0-9_]*$/', $dbName)) return true; return false; } + + /** + * 获取禅道对象列表。 + * Get zentao object list. + * + * @access public + * @return array + */ + public function getZentaoObjectList(): array + { + $objectList = array(); + foreach($this->lang->convert->jira->zentaoObjectList as $type => $text) $objectList[$type] = $text; + return $objectList; + } + + /** + * 获取禅道关联关系列表。 + * Get zentao relation list. + * + * @access public + * @return array + */ + public function getZentaoRelationList(): array + { + return $this->lang->convert->jira->zentaoLinkTypeList; + } + + /** + * 获取禅道字段列表。 + * Get zentao fields. + * + * @param string $module + * @access public + * @return array + */ + public function getZentaoFields(string $module): array + { + $this->app->loadLang($module); + + $fields = array(); + if(isset($this->config->convert->objectFields[$module])) + { + foreach($this->config->convert->objectFields[$module] as $field) + { + $fields[$field] = $this->lang->{$module}->$field; + } + } + + if(in_array($module, array('story', 'epic', 'requirement'))) $fields['spec'] = $this->lang->story->spec; + return $fields; + } + + /** + * 获取禅道状态列表。 + * Get zentao status. + * + * @param string $module + * @access public + * @return array + */ + public function getZentaoStatus(string $module): array + { + $this->loadModel($module); + $statusList = $this->lang->{$module}->statusList; + if($module == 'testcase') $statusList = array_merge($statusList, array('add_case_status' => $this->lang->convert->add)); + return $statusList; + } + + /** + * 获取导入jira的步骤列表。 + * Get import jira data steps. + * + * @param array $jiraData + * @param array $issueTypeList + * @access public + * @return array + */ + public function getJiraStepList(array $jiraData, array $issueTypeList = array()): array + { + $stepList = array(); + $objectSteps = array(); + if(empty($issueTypeList)) $issueTypeList = $this->getJiraData($this->session->jiraMethod, 'issuetype'); + if(!empty($jiraData['jiraObject'])) + { + foreach($jiraData['jiraObject'] as $objectID) + { + if($jiraData['zentaoObject'][$objectID] == 'add_custom') continue; + if($objectID) $objectSteps[$objectID] = zget($issueTypeList[$objectID], 'pname', '') . $this->lang->convert->jira->steps['objectData']; + } + } + + foreach($this->lang->convert->jira->steps as $currentStep => $stepLabel) + { + if($currentStep == 'objectData') + { + if(!empty($objectSteps)) + { + $stepList = $stepList + $objectSteps; + continue; + } + } + $stepList[$currentStep] = $stepLabel; + } + return $stepList; + } + + /** + * 校验导入jira数据时配置信息完整性。 + * Check import jira. + * + * @param string $step + * @access public + * @return bool + */ + public function checkImportJira(string $step): bool + { + $data = fixer::input('post')->get(); + if($step == 'object') + { + $jiraObject = $data->jiraObject; + $zentaoObject = $data->zentaoObject; + foreach($jiraObject as $typeID) + { + if(empty($zentaoObject[$typeID])) dao::$errors['message'] = sprintf($this->lang->error->notempty, $this->lang->convert->jira->zentaoObject); + } + } + + return !dao::isError(); + } + + /** + * 获取导入Jira时禅道的对象属性默认值。 + * Get object default value. + * + * @param string $step + * @access public + * @return array + */ + public function getObjectDefaultValue(string $step): array + { + if($step == 'object') return $this->config->convert->importDeafaultValue; + + $jiraRelation = $this->session->jiraRelation; + $jiraRelation = $jiraRelation ? json_decode($jiraRelation, true) : array(); + if(empty($jiraRelation['zentaoObject'][$step])) return array(); + $zentaoObject = $jiraRelation['zentaoObject'][$step]; + + $defaultValue = array(); + $importDeafaultValue = $this->config->convert->importDeafaultValue; + $defaultValue["zentaoField$step"] = !empty($importDeafaultValue[$zentaoObject]['field']) ? $importDeafaultValue[$zentaoObject]['field'] : array(); + $defaultValue["zentaoStatus$step"] = !empty($importDeafaultValue[$zentaoObject]['status']) ? $importDeafaultValue[$zentaoObject]['status'] : array(); + $defaultValue["zentaoAction$step"] = !empty($importDeafaultValue[$zentaoObject]['action']) ? $importDeafaultValue[$zentaoObject]['action'] : array(); + $defaultValue["zentaoResolution$step"] = !empty($importDeafaultValue[$zentaoObject]['resolution']) ? $importDeafaultValue[$zentaoObject]['resolution'] : array(); + $defaultValue["zentaoReason$step"] = !empty($importDeafaultValue[$zentaoObject]['reason']) ? $importDeafaultValue[$zentaoObject]['reason'] : array(); + return $defaultValue; + } + + /** + * 获取Jira事务类型列表。 + * Get jira type list. + * + * @access public + * @return array + */ + public function getJiraTypeList(): array + { + $issues = $this->getJiraData($this->session->jiraMethod, 'issue'); + $typeList = $this->getJiraData($this->session->jiraMethod, 'issuetype'); + + $jiraTypeList = array(); + foreach($issues as $issue) + { + if(empty($issue->issuetype) || empty($typeList[$issue->issuetype])) continue; + + $issueType = $typeList[$issue->issuetype]; + $jiraTypeList[$issue->issuetype] = $issueType; + } + return $jiraTypeList; + } + + /** + * 获取Jira状态列表。 + * Get jira status list. + * + * @param string|int $step + * @param array $relations + * @access public + * @return array + */ + public function getJiraStatusList(string|int $step, array $relations): array + { + if(empty($relations['zentaoObject']) || !in_array($step, array_keys($relations['zentaoObject']))) return array(); + + $issues = $this->getJiraData($this->session->jiraMethod, 'issue'); + $statusList = $this->getJiraData($this->session->jiraMethod, 'status'); + + $jiraStatusList = array(); + foreach($issues as $issue) + { + if($issue->issuetype != $step || empty($issue->issuestatus)) continue; + if(empty($statusList[$issue->issuestatus])) continue; + + $status = $statusList[$issue->issuestatus]; + $jiraStatusList[$issue->issuestatus] = $status->pname; + } + return $jiraStatusList; + } + + /** + * 获取Jira自定义字段列表。 + * Get jira custom fields. + * + * @param string|int $step + * @param array $relations + * @access public + * @return array + */ + public function getJiraCustomField(string|int $step, array $relations): array + { + if($this->config->edition == 'open') return array(); + if(empty($relations['zentaoObject']) || !in_array($step, array_keys($relations['zentaoObject']))) return array(); + + $issues = $this->getJiraData($this->session->jiraMethod, 'issue'); + $fields = $this->getJiraData($this->session->jiraMethod, 'customfield'); + $fieldValue = $this->getJiraData($this->session->jiraMethod, 'customfieldvalue'); + + $jiraFields = array(); + foreach($fieldValue as $value) + { + if(empty($issues[$value->ISSUE]) || empty($fields[$value->CUSTOMFIELD])) continue; + + $issue = $issues[$value->ISSUE]; + $field = $fields[$value->CUSTOMFIELD]; + if($issue->issuetype != $step) continue; + + if($field->CUSTOMFIELDTYPEKEY != 'com.pyxis.greenhopper.jira:gh-sprint') $jiraFields[$value->CUSTOMFIELD] = $field->cfname; + } + return $jiraFields; + } + + /** + * 获取Jira按照项目分组的自定义字段。 + * Get jira custom fields group by project. + * + * @param array $relations + * @access public + * @return array + */ + public function getJiraFieldGroupByProject($relations): array + { + $fieldList = $this->dao->dbh($this->dbh)->select('AID, extra, BID AS field')->from(JIRA_TMPRELATION)->where('AType')->eq('jcustomfield')->andWhere('BType')->eq('zworkflowfield')->fetchGroup('AID', 'extra'); + $issues = $this->getJiraData($this->session->jiraMethod, 'issue'); + $fieldValue = $this->getJiraData($this->session->jiraMethod, 'customfieldvalue'); + $jiraFields = array(); + foreach($fieldValue as $value) + { + if(empty($issues[$value->ISSUE])) continue; + + $issue = $issues[$value->ISSUE]; + $zentaoObject = $relations['zentaoObject'][$issue->issuetype]; + + if(!empty($fieldList[$value->CUSTOMFIELD][$zentaoObject])) + { + $field = $fieldList[$value->CUSTOMFIELD][$zentaoObject]; + $jiraFields[$issue->PROJECT][$zentaoObject][$field->field] = $field; + } + } + + return $jiraFields; + } + + /** + * 获取Jira工作流里的动作列表。 + * Get jira workflow actions. + * + * @access public + * @return array + */ + public function getJiraWorkflowActions(): array + { + if($this->config->edition == 'open') return array(); + + $workflows = $this->getJiraData($this->session->jiraMethod, 'workflow'); + $workflowActions = array(); + foreach($workflows as $workflowID => $workflow) + { + $descriptor = simplexml_load_string($workflow->DESCRIPTOR); + $descriptor = $this->object2Array($descriptor); + + foreach($descriptor as $id => $actions) + { + if(!empty($actions['action'])) + { + $actionInfo = array(); + foreach($actions['action'] as $key => $actionList) + { + if(is_numeric($key)) + { + foreach($actionList as $k => $action) + { + $actionInfo = array_merge($actionInfo, $k == '@attributes' ? $action : array($k => $action)); + $workflowActions[$actionInfo['id']] = $actionInfo; + } + } + else + { + $actionInfo = array_merge($actionInfo, $key == '@attributes' ? $actionList : array($key => $actionList)); + } + } + $workflowActions['actions'][$actionInfo['id']] = $actionInfo; + } + elseif(!empty($actions['step'])) + { + foreach($actions['step'] as $step) + { + if(empty($step['meta'])) continue; + $workflowActions['steps'][$step['@attributes']['id']] = $step['meta']; + } + } + } + } + return $workflowActions; + } + + /** + * 验证Jira api接口能否访问。 + * Check jira api. + * + * @access public + * @return bool + */ + public function checkJiraApi(): bool + { + $jiraApi = json_decode($this->session->jiraApi, true); + if(empty($jiraApi['domain'])) return false; + + $token = base64_encode("{$jiraApi['admin']}:{$jiraApi['token']}"); + $url = $jiraApi['domain'] . '/rest/agile/1.0/board'; + $boardList = json_decode(commonModel::http($url, array(), array(), array("Authorization: Basic $token"), 'json', 'GET', 10)); + if(empty($boardList->values)) + { + dao::$errors['message'] = $this->lang->convert->jira->apiError; + return false; + } + return true; + } + + /** + * 获取jira用户。 + * Get jira account. + * + * @param string $userKey + * @access public + * @return string + */ + public function getJiraAccount(string $userKey): string + { + if(empty($userKey)) return ''; + + $users = $this->getJiraData($this->session->jiraMethod, 'user'); + + if(strpos($userKey, 'JIRAUSER') !== false) + { + $userID = str_replace('JIRAUSER', '', $userKey); + return $this->processJiraUser($users[$userID]->account, $users[$userID]->email); + } + else + { + foreach($users as $user) + { + if($user->account == $userKey) return $this->processJiraUser($user->account, $user->email); + } + } + return $userKey; + } + + /** + * 处理Jira用户名为禅道格式。 + * Process jira user. + * + * @param string $jiraAccount + * @param string $jiraEmail + * @access public + * @return string + */ + public function processJiraUser(string $jiraAccount, string $jiraEmail): string + { + $userConfig = $this->session->jiraUser; + $account = substr($jiraAccount, 0, 30); + if($userConfig['mode'] == 'email' && $jiraEmail) + { + if(strpos($jiraEmail, '@') !== false) + { + $account = substr(substr($jiraEmail, 0, strpos($jiraEmail, '@')), 0, 30); + } + else + { + $account = substr($jiraEmail, 0, 30); + } + } + return $account; + } + + /** + * 从jira文件中获取版本信息。 + * Get version group from jira file. + * + * @access public + * @return array + */ + public function getVersionGroup(): array + { + $filePath = $this->app->getTmpRoot() . 'jirafile/nodeassociation.xml'; + if(!file_exists($filePath)) return array(); + + $xmlContent = file_get_contents($this->app->getTmpRoot() . 'jirafile/nodeassociation.xml'); + $xmlContent = preg_replace ('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $xmlContent); + $parsedXML = simplexml_load_string($xmlContent, 'SimpleXMLElement', LIBXML_NOCDATA); + if(empty($parsedXML)) return array(); + + $dataList = array(); + $parsedXML = $this->object2Array($parsedXML); + foreach($parsedXML as $key => $xmlArray) + { + if(strtolower($key) != 'nodeassociation') continue; + foreach($xmlArray as $key => $attributes) + { + foreach($attributes as $value) + { + if(!is_array($value)) continue; + if($value['sinkNodeEntity'] != 'Version') continue; + $dataList[$value['sinkNodeId']][] = $value['sourceNodeId']; + } + } + } + + return $dataList; + } + + /** + * 将对象转换为数组。 + * Convert object to array. + * + * @param object|array $parsedXML + * @access public + * @return array + */ + public function object2Array(object|array $parsedXML): array + { + if(is_object($parsedXML)) + { + $parsedXML = (array)$parsedXML; + } + + if(is_array($parsedXML)) + { + foreach($parsedXML as $key => $value) + { + if(!is_object($value) && !is_array($value)) continue; + $parsedXML[$key] = $this->object2Array($value); + } + } + + return $parsedXML; + } + + /** + * 转换需求阶段。 + * Convert stage. + * + * @param string $jiraStatus + * @access public + * @return string + */ + public function convertStage(string $jiraStatus, string $issueType): string + { + $jiraRelation = $this->session->jiraRelation; + $relations = $jiraRelation ? json_decode($jiraRelation, true) : array(); + + $stage = 'wait'; + if(!empty($relations["zentaoStage$issueType"][$jiraStatus])) $stage = $relations["zentaoStage$issueType"][$jiraStatus]; + + return $stage; + } + + /** + * 转换状态。 + * Convert jira status. + * + * @param string $objectType + * @param string $jiraStatus + * @param string $issueType + * @access public + * @return string + */ + public function convertStatus(string $objectType, string $jiraStatus, string $issueType): string + { + $jiraRelation = $this->session->jiraRelation; + $relations = $jiraRelation ? json_decode($jiraRelation, true) : array(); + + if(!empty($relations["zentaoStatus{$issueType}"][$jiraStatus])) return $relations["zentaoStatus{$issueType}"][$jiraStatus]; + + if($objectType == 'testcase' && empty($this->config->testcase->needReview)) return 'normal'; + if($objectType == 'feedback' && empty($this->config->feedback->needReview)) return 'normal'; + if($objectType == 'ticket' && empty($this->config->feedback->ticket)) return 'normal'; + + return in_array($objectType, array('task', 'testcase', 'feedback', 'ticket', 'flow')) ? 'wait' : 'active'; + } + + /** + * 获取Jira的所有冲刺。 + * Get jira sprint. + * + * @param array $projectList + * @access public + * @return array + */ + public function getJiraSprint(array $projectList): array + { + $sprintGroup = array(); + if($this->session->jiraMethod == 'file') + { + if(empty($_SESSION['jiraApi'])) return $sprintGroup; + $jiraApi = json_decode($this->session->jiraApi, true); + if(empty($jiraApi['domain'])) return $sprintGroup; + + $token = base64_encode("{$jiraApi['admin']}:{$jiraApi['token']}"); + foreach($projectList as $projectID) + { + $url = $jiraApi['domain'] . '/rest/agile/1.0/board?projectKeyOrId=' . $projectID; + $boardList = json_decode(commonModel::http($url, array(), array(), array("Authorization: Basic $token"), 'json', 'GET', 10)); + if(!empty($boardList->values)) + { + foreach($boardList->values as $board) + { + $url = $jiraApi['domain'] . "/rest/agile/1.0/board/$board->id/sprint"; + $sprintList = json_decode(commonModel::http($url, array(), array(), array("Authorization: Basic $token"), 'json', 'GET', 10)); + if(!empty($sprintList->values)) + { + foreach($sprintList->values as $sprint) + { + $sprintGroup[$projectID][$sprint->id] = $sprint; + } + } + } + } + } + } + else + { + $sprintGroup = $this->dao->dbh($this->sourceDBH)->select('project.id as pid, sprint.*')->from('ao_60db71_sprint')->alias('sprint') + ->leftJoin('ao_60db71_rapidview')->alias('rapview')->on('rapview.id=sprint.RAPID_VIEW_ID') + ->leftJoin('searchrequest')->alias('search')->on('search.ID=rapview.SAVED_FILTER_ID') + ->leftJoin('project')->on("search.reqcontent like concat('%project = ', project.pkey, ' O%')") + ->where('project.id')->notNULL() + ->fetchGroup('pid', 'ID'); + } + + return $sprintGroup; + } + + /** + * 获取Jira issue的所属sprint。 + * Get jira sprint issue. + * + * @access public + * @return array + */ + public function getJiraSprintIssue(): array + { + $issueGroup = array(); + $sprintRelation = $this->dao->dbh($this->dbh)->select('AID,BID')->from(JIRA_TMPRELATION)->where('AType')->eq('jsprint')->andWhere('BType')->eq('zexecution')->fetchPairs(); + if($this->session->jiraMethod == 'file') + { + if(empty($_SESSION['jiraApi'])) return $issueGroup; + $jiraApi = json_decode($this->session->jiraApi, true); + if(empty($jiraApi['domain'])) return $issueGroup; + + $token = base64_encode("{$jiraApi['admin']}:{$jiraApi['token']}"); + foreach($sprintRelation as $sprintID => $executionID) + { + $url = $jiraApi['domain'] . "/rest/agile/1.0/sprint/{$sprintID}/issue"; + $result = json_decode(commonModel::http($url, array(), array(), array("Authorization: Basic $token"), 'json', 'GET', 10)); + if(!empty($result->issues)) + { + foreach($result->issues as $issue) $issueGroup[$issue->id] = $executionID; + } + } + } + + return $issueGroup; + } + + /** + * 获取Jira已归档的项目。 + * Get jira archived project. + * + * @param array $dataList + * @access public + * @return array + */ + public function getJiraArchivedProject($dataList): array + { + $archivedProject = array(); + $auditLog = $this->getJiraData($this->session->jiraMethod, 'auditlog'); + if($auditLog) + { + foreach($auditLog as $log) + { + if($log->SUMMARY == 'Project archived' && $log->OBJECT_TYPE == 'project') $archivedProject[$log->OBJECT_ID] = $log->OBJECT_ID; + } + } + + if($this->session->jiraMethod == 'file') + { + if(empty($_SESSION['jiraApi'])) return $archivedProject; + $jiraApi = json_decode($this->session->jiraApi, true); + if(empty($jiraApi['domain'])) return $archivedProject; + + $token = base64_encode("{$jiraApi['admin']}:{$jiraApi['token']}"); + $url = $jiraApi['domain'] . '/rest/api/2/project'; + $result = json_decode(commonModel::http($url, array(), array(), array("Authorization: Basic $token"), 'json', 'GET', 10)); + + $projectList = array(); + if(!empty($result)) + { + foreach($result as $project) + { + if(!empty($project->id)) $projectList[$project->id] = $project; + } + } + + foreach($dataList as $project) + { + if(empty($projectList[$project->ID])) $archivedProject[$project->ID] = $project->ID; + } + } + else + { + $auditEntity = $this->dao->dbh($this->sourceDBH)->select('PRIMARY_RESOURCE_ID')->from('ao_c77861_audit_entity') + ->where('ACTION_T_KEY')->eq('jira.auditing.project.archived') + ->andWhere('PRIMARY_RESOURCE_TYPE')->eq('PROJECT') + ->fetchPairs(); + $archivedProject = array_merge($archivedProject, $auditEntity); + } + return $archivedProject; + } + + /** + * 获取Jira项目角色与成员。 + * Get jira project role actor. + * + * @access public + * @return array + */ + public function getJiraProjectRoleActor(): array + { + $projectRoleActor = $this->getJiraData($this->session->jiraMethod, 'projectroleactor'); + $memberShip = $this->getJiraData($this->session->jiraMethod, 'membership'); + + $projectMember = array(); + foreach($projectRoleActor as $role) + { + if(empty($role->PID)) continue; + if($role->ROLETYPE == 'atlassian-user-role-actor') + { + $projectMember[$role->PID][$role->ROLETYPEPARAMETER] = $role->ROLETYPEPARAMETER; + } + if($role->ROLETYPE == 'atlassian-group-role-actor') + { + foreach($memberShip as $member) + { + if($member->parent_name == $role->ROLETYPEPARAMETER) $projectMember[$role->PID]["JIRAUSER{$member->child_id}"] = 'JIRAUSER' . $member->child_id; + } + } + } + return $projectMember; + } + + /** + * 获取按照项目分组的Jira事务类型。 + * get Jira issue type group by project. + * + * @param array $relations + * @access public + * @return array + */ + public function getIssueTypeList(array $relations): array + { + $schemeproject = $this->getJiraData($this->session->jiraMethod, 'configurationcontext'); + $schemeissuetype = $this->getJiraData($this->session->jiraMethod, 'optionconfiguration'); + $projectIssueTypeList = array(); + foreach($schemeproject as $projectRelation) + { + if(!empty($projectRelation->PROJECT) && $projectRelation->customfield == 'issuetype') + { + foreach($schemeissuetype as $issueTypeRelation) + { + if($issueTypeRelation->FIELDCONFIG == $projectRelation->FIELDCONFIGSCHEME && $issueTypeRelation->FIELDID == 'issuetype' && !empty($issueTypeRelation->OPTIONID)) + { + if(!empty($relations['zentaoObject'][$issueTypeRelation->OPTIONID])) $projectIssueTypeList[$projectRelation->PROJECT][] = $relations['zentaoObject'][$issueTypeRelation->OPTIONID]; + } + } + } + } + return $projectIssueTypeList; + } } diff --git a/module/convert/tao.php b/module/convert/tao.php index 0315c80932..f9ee313554 100644 --- a/module/convert/tao.php +++ b/module/convert/tao.php @@ -13,6 +13,7 @@ class convertTao extends convertModel protected function buildUserData(array $data): object { $user = new stdclass(); + $user->ID = $data['id']; $user->account = isset($data['lowerUserName']) ? $data['lowerUserName'] : ''; $user->realname = isset($data['lowerDisplayName']) ? $data['lowerDisplayName'] : ''; $user->email = isset($data['emailAddress']) ? $data['emailAddress'] : ''; @@ -53,22 +54,104 @@ class convertTao extends convertModel protected function buildIssueData(array $data): object { $issue = new stdclass(); - $issue->ID = $data['id']; - $issue->SUMMARY = isset($data['summary']) ? $data['summary'] : ''; - $issue->PRIORITY = isset($data['priority']) ? $data['priority'] : ''; - $issue->PROJECT = isset($data['project']) ? $data['project'] : 0; - $issue->issuestatus = isset($data['status']) ? $data['status'] : ''; - $issue->CREATED = isset($data['created']) ? $data['created'] : ''; - $issue->CREATOR = isset($data['creator']) ? $data['creator'] : ''; - $issue->issuetype = isset($data['type']) ? $data['type'] : ''; - $issue->ASSIGNEE = isset($data['assignee']) ? $data['assignee'] : ''; - $issue->RESOLUTION = isset($data['resolution']) ? $data['resolution'] : ''; - $issue->issuenum = $data['number']; - $issue->DESCRIPTION = isset($data['description']) ? $data['description'] : ''; + $issue->ID = $data['id']; + $issue->SUMMARY = isset($data['summary']) ? $data['summary'] : ''; + $issue->PRIORITY = isset($data['priority']) ? $data['priority'] : ''; + $issue->PROJECT = isset($data['project']) ? $data['project'] : 0; + $issue->issuestatus = isset($data['status']) ? $data['status'] : ''; + $issue->CREATED = isset($data['created']) ? $data['created'] : ''; + $issue->CREATOR = isset($data['creator']) ? $data['creator'] : ''; + $issue->issuetype = isset($data['type']) ? $data['type'] : ''; + $issue->ASSIGNEE = isset($data['assignee']) ? $data['assignee'] : ''; + $issue->RESOLUTION = isset($data['resolution']) ? $data['resolution'] : ''; + $issue->TIMEORIGINALESTIMATE = isset($data['timeoriginalestimate']) ? $data['timeoriginalestimate'] : ''; + $issue->TIMEESTIMATE = isset($data['timeestimate']) ? $data['timeestimate'] : ''; + $issue->TIMESPENT = isset($data['timespent']) ? $data['timespent'] : ''; + $issue->issuenum = isset($data['number']) ? $data['number'] : ''; + $issue->DESCRIPTION = isset($data['description']) ? $data['description'] : ''; return $issue; } + /** + * 构建issue类型数据。 + * Build issue type data. + * + * @param array $data + * @access protected + * @return object + */ + protected function buildIssueTypeData(array $data): object + { + $issueType = new stdclass(); + $issueType->ID = $data['id']; + $issueType->pname = $data['name']; + $issueType->DESCRIPTION = zget($data, 'description', ''); + $issueType->pstyle = zget($data, 'style', ''); + $issueType->ICONURL = zget($data, 'iconurl', ''); + $issueType->AVATAR = zget($data, 'avatar', ''); + + return $issueType; + } + + /** + * 构建issue关联关系数据。 + * Build issue link type data. + * + * @param array $data + * @access protected + * @return object + */ + protected function buildIssueLinkTypeData(array $data): object + { + $issueLinkType = new stdclass(); + $issueLinkType->ID = $data['id']; + $issueLinkType->LINKNAME = $data['linkname']; + $issueLinkType->INWARD = zget($data, 'inward', ''); + $issueLinkType->OUTWARD = zget($data, 'outward', ''); + $issueLinkType->pstyle = zget($data, 'style', ''); + + return $issueLinkType; + } + + /** + * 构建解决方案数据。 + * Build resolution data. + * + * @param array $data + * @access protected + * @return object + */ + protected function buildResolutionData(array $data): object + { + $resolution = new stdclass(); + $resolution->ID = $data['id']; + $resolution->SEQUENCE = $data['sequence']; + $resolution->pname = $data['name']; + $resolution->DESCRIPTION = zget($data, 'description', ''); + + return $resolution; + } + + /** + * 构建issue状态数据。 + * Build issue status data. + * + * @param array $data + * @access protected + * @return object + */ + protected function buildStatusData(array $data): object + { + $status = new stdclass(); + $status->ID = $data['id']; + $status->SEQUENCE = $data['sequence']; + $status->pname = $data['name']; + $status->DESCRIPTION = zget($data, 'description', ''); + + return $status; + } + /** * 构建build数据。 * Build build data. @@ -84,6 +167,7 @@ class convertTao extends convertModel $build->PROJECT = isset($data['project']) ? $data['project'] : 0; $build->vname = isset($data['name']) ? $data['name'] : ''; $build->RELEASEDATE = isset($data['releasedate']) ? $data['releasedate'] : ''; + $build->RELEASED = isset($data['released']) ? $data['released'] : ''; $build->DESCRIPTION = isset($data['description']) ? $data['description'] : ''; return $build; @@ -100,6 +184,7 @@ class convertTao extends convertModel protected function buildIssuelinkData(array $data): object { $issueLink = new stdclass(); + $issueLink->ID = $data['id']; $issueLink->LINKTYPE = isset($data['linktype']) ? $data['linktype'] : ''; $issueLink->SOURCE = isset($data['source']) ? $data['source'] : ''; $issueLink->DESTINATION = isset($data['destination']) ? $data['destination'] : ''; @@ -111,13 +196,14 @@ class convertTao extends convertModel * 构建action数据。 * Build action data. * - * @param array $data + * @param array $data * @access protected * @return object */ protected function buildActionData(array $data): object { $action = new stdclass(); + $action->ID = $data['id']; $action->issueid = $data['issue']; $action->actionbody = isset($data['body']) ? $data['body'] : ''; $action->AUTHOR = isset($data['author']) ? $data['author'] : ''; @@ -130,7 +216,7 @@ class convertTao extends convertModel * 构建file数据。 * Build file data. * - * @param array $data + * @param array $data * @access protected * @return object */ @@ -142,243 +228,347 @@ class convertTao extends convertModel $file->FILENAME = isset($data['filename']) ? $data['filename'] : ''; $file->MIMETYPE = isset($data['mimetype']) ? $data['mimetype'] : ''; $file->FILESIZE = isset($data['filesize']) ? $data['filesize'] : ''; - $file->CREATED = isset($data['created']) ? $data['created'] : ''; - $file->AUTHOR = isset($data['author']) ? $data['author'] : ''; + $file->CREATED = isset($data['created']) ? $data['created'] : ''; + $file->AUTHOR = isset($data['author']) ? $data['author'] : ''; return $file; } /** - * 获取jira用户。 - * Get jira account. + * 构建优先级数据。 + * Build Priority data. * - * @param string $userKey - * @param string $method db|file + * @param array $data * @access protected - * @return string + * @return object */ - protected function getJiraAccount(string $userKey, string $method = 'db'): string + protected function buildPriorityData(array $data): object { - if(strpos($userKey, 'JIRAUSER') === false) return $userKey; + $priority = new stdclass(); + $priority->ID = $data['id']; + $priority->pname = isset($data['name']) ? $data['name'] : ''; - if($method == 'db') - { - return $this->dao->dbh($this->sourceDBH)->select('lower_user_name')->from(JIRA_USER)->where('user_key')->eq($userKey)->fetch('lower_user_name'); - } - - $appUsers = $this->getJiraAppUser(); - return zget($appUsers, $userKey, $userKey); + return $priority; } /** - * 获取jira用户键值对。 - * Get jira app user pairs. + * 构建自定义字段数据。 + * Build custom field data. * + * @param array $data * @access protected - * @return array + * @return object */ - protected function getJiraAppUser(): array + protected function buildCustomFieldData(array $data): object { - $xmlContent = file_get_contents($this->app->getTmpRoot() . 'jirafile/applicationuser.xml'); - $xmlContent = preg_replace ('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $xmlContent); - $parsedXML = simplexml_load_string($xmlContent, 'SimpleXMLElement', LIBXML_NOCDATA); + $field = new stdclass(); + $field->ID = $data['id']; + $field->cfname = $data['name']; + $field->DESCRIPTION = isset($data['description']) ? $data['description'] : ''; + $field->CUSTOMFIELDTYPEKEY = isset($data['customfieldtypekey']) ? $data['customfieldtypekey'] : ''; + $field->CUSTOMFIELDSEARCHERKEY = isset($data['customfieldsearcherkey']) ? $data['customfieldsearcherkey'] : ''; - $pairs = array(); - $parsedXML = $this->object2Array($parsedXML); - foreach($parsedXML as $key => $xmlArray) - { - if(strtolower($key) != 'applicationuser') continue; - foreach($xmlArray as $key => $attributes) - { - if(is_numeric($key)) - { - foreach($attributes as $value) - { - if(!is_array($value)) continue; - if(!isset($value['userKey'])) continue; - $pairs[$value['userKey']] = $value['lowerUserName']; - } - } - else - { - $pairs[$attributes['userKey']] = $attributes['lowerUserName']; - } - } - } - - return $pairs; + return $field; } /** - * 从数据库获取jira数据。 - * Get jira data from db. + * 构建工作流字段值数据。 + * Build custom field value data. * - * @param string $module - * @param int $lastID - * @param int $limit + * @param array $data * @access protected - * @return array + * @return object */ - protected function getJiraDataFromDB(string $module = '', int $lastID = 0, int $limit = 0): array + protected function buildCustomFieldValueData(array $data): object { - $dataList = array(); - $table = zget($this->config->convert->objectTables, $module, ''); - if($module == 'user') - { - $dataList = $this->dao->dbh($this->sourceDBH)->select('t1.`ID`, t1.`lower_user_name` as account, t1.`lower_display_name` as realname, t1.`lower_email_address` as email, t1.created_date as `join`, t2.user_key as userCode')->from(JIRA_USERINFO)->alias('t1') - ->leftJoin(JIRA_USER)->alias('t2')->on('t1.`lower_user_name` = t2.`lower_user_name`') - ->where('1 = 1') - ->beginIF($lastID)->andWhere('t1.ID')->gt($lastID)->fi() - ->orderBy('t1.ID asc')->limit($limit) - ->fetchAll('ID'); - } - elseif(!empty($table)) - { - $dataList = $this->dao->dbh($this->sourceDBH)->select('*')->from($table) - ->where('1 = 1') - ->beginIF($lastID)->andWhere('ID')->gt($lastID)->fi() - ->orderBy('ID asc')->limit($limit) - ->fetchAll('ID'); - } + $fieldValue = new stdclass(); + $fieldValue->ID = $data['id']; + $fieldValue->ISSUE = $data['issue']; + $fieldValue->CUSTOMFIELD = $data['customfield']; + $fieldValue->STRINGVALUE = zget($data, 'stringvalue', ''); - return $dataList; + return $fieldValue; } /** - * 从jira文件中获取版本信息。 - * Get version group from jira file. + * 构建工作流字段配置数据。 + * Build custom field option data. * + * @param array $data * @access protected - * @return array + * @return object */ - protected function getVersionGroup(): array + protected function buildCustomFieldOptionData(array $data): object { - $xmlContent = file_get_contents($this->app->getTmpRoot() . 'jirafile/nodeassociation.xml'); - $xmlContent = preg_replace ('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $xmlContent); - $parsedXML = simplexml_load_string($xmlContent, 'SimpleXMLElement', LIBXML_NOCDATA); - if(empty($parsedXML)) return array(); + $fieldOption = new stdclass(); + $fieldOption->ID = $data['id']; + $fieldOption->CUSTOMFIELD = $data['customfield']; + $fieldOption->CUSTOMFIELDCONFIG = $data['customfieldconfig']; + $fieldOption->customvalue = $data['value']; + $fieldOption->disabled = $data['disabled']; - $dataList = array(); - $parsedXML = $this->object2Array($parsedXML); - foreach($parsedXML as $key => $xmlArray) - { - if(strtolower($key) != 'nodeassociation') continue; - foreach($xmlArray as $key => $attributes) - { - foreach($attributes as $value) - { - if(!is_array($value)) continue; - if($value['sinkNodeEntity'] != 'Version') continue; - $dataList[$value['sinkNodeId']][] = $value['sinkNodeId']; - $dataList[$value['sinkNodeId']][] = $value['sourceNodeId']; - } - } - } - - return $dataList; + return $fieldOption; } /** - * 获取需求、任务、bug、对象类型。 - * Get stories and tasks and bugs and objectType. + * 构建属性条目数据。 + * Build os property entry data. + * + * @param array $data + * @access protected + * @return object + */ + protected function buildOSPropertyEntryData(array $data): object + { + $property = new stdclass(); + $property->ID = $data['id']; + $property->ENTITY_NAME = $data['entityName']; + $property->ENTITY_ID = $data['entityId']; + $property->PROPERTY_KEY = $data['propertyKey']; + $property->propertytype = $data['type']; + + return $property; + } + + /** + * 构建上下文数据。 + * Build configuration context data. + * + * @param array $data + * @access protected + * @return object + */ + protected function buildConfigurationcontextData(array $data): object + { + $config = new stdclass(); + $config->ID = $data['id']; + $config->customfield = isset($data['key']) ? $data['key'] : ''; + $config->FIELDCONFIGSCHEME = isset($data['fieldconfigscheme']) ? $data['fieldconfigscheme'] : ''; + $config->PROJECT = isset($data['project']) ? $data['project'] : ''; + + return $config; + } + + /** + * 构建配置选择数据。 + * Build option configuration data. + * + * @param array $data + * @access protected + * @return object + */ + protected function buildOptionconfigurationData(array $data): object + { + $option = new stdclass(); + $option->ID = $data['id']; + $option->FIELDID = isset($data['fieldid']) ? $data['fieldid'] : ''; + $option->OPTIONID = isset($data['optionid']) ? $data['optionid'] : ''; + $option->FIELDCONFIG = isset($data['fieldconfig']) ? $data['fieldconfig'] : ''; + + return $option; + } + + /** + * 构建日志数据。 + * Build audit log data. + * + * @param array $data + * @access protected + * @return object + */ + protected function buildAuditLogData(array $data): object + { + $auditLog = new stdclass(); + $auditLog->ID = $data['id']; + $auditLog->SUMMARY = isset($data['summary']) ? $data['summary'] : ''; + $auditLog->OBJECT_TYPE = isset($data['objectType']) ? $data['objectType'] : ''; + $auditLog->OBJECT_ID = isset($data['objectId']) ? $data['objectId'] : ''; + + return $auditLog; + } + + /** + * 构建项目用户角色数据。 + * Build project role actor data. + * + * @param array $data + * @access protected + * @return object + */ + protected function buildProjectRoleActorData(array $data): object + { + $projectRole = new stdclass(); + $projectRole->ID = $data['id']; + $projectRole->PID = isset($data['pid']) ? $data['pid'] : ''; + $projectRole->ROLETYPE = $data['roletype']; + $projectRole->ROLETYPEPARAMETER = $data['roletypeparameter']; + + return $projectRole; + } + + /** + * 构建角色成员数据。 + * Build member ship data. + * + * @param array $data + * @access protected + * @return object + */ + protected function buildMemberShipData(array $data): object + { + $memberShip = new stdclass(); + $memberShip->ID = $data['id']; + $memberShip->parent_id = $data['parentId']; + $memberShip->child_id = $data['childId']; + $memberShip->membership_type = $data['membershipType']; + $memberShip->parent_name = $data['parentName']; + $memberShip->child_name = $data['childName']; + + return $memberShip; + } + + /** + * 构建字段视图方案数据。 + * Build field screen layout data. + * + * @param array $data + * @access protected + * @return object + */ + protected function buildFieldScreenLayoutItemData(array $data): object + { + $layout = new stdclass(); + $layout->ID = $data['id']; + $layout->FIELDIDENTIFIER = $data['fieldidentifier']; + $layout->FIELDSCREENTAB = isset($data['fieldscreentab']) ? $data['fieldscreentab'] : ''; + + return $layout; + } + + /** + * 构建字段配置数据。 + * Build field config scheme data. + * + * @param array $data + * @access protected + * @return object + */ + protected function buildFieldConfigSchemeData(array $data): object + { + $fieldConfig = new stdclass(); + $fieldConfig->ID = $data['id']; + $fieldConfig->configname = $data['name']; + $fieldConfig->DESCRIPTION = isset($data['description']) ? $data['description'] : ''; + $fieldConfig->FIELDID = isset($data['fieldid']) ? $data['fieldid'] : ''; + + return $fieldConfig; + } + + /** + * 构建字段方案类型数据。 + * Build field config scheme issue type data. + * + * @param array $data + * @access protected + * @return object + */ + protected function buildFieldConfigSchemeIssueTypeData(array $data): object + { + $fieldIssueType = new stdclass(); + $fieldIssueType->ID = $data['id']; + $fieldIssueType->ISSUETYPE = isset($data['issuetype']) ? $data['issuetype'] : ''; + $fieldIssueType->FIELDCONFIGSCHEME = isset($data['fieldconfigscheme']) ? $data['fieldconfigscheme'] : ''; + $fieldIssueType->FIELDCONFIGURATION = isset($data['fieldconfiguration']) ? $data['fieldconfiguration'] : ''; + + return $fieldIssueType; + } + + /** + * 构建工作流数据。 + * Build workflow data. + * + * @param array $data + * @access protected + * @return object + */ + protected function buildWorkflowData(array $data): object + { + $workflow = new stdclass(); + $workflow->ID = $data['id']; + $workflow->workflowname = $data['name']; + $workflow->DESCRIPTOR = $data['descriptor']; + + return $workflow; + } + + /** + * 构建工作流方案数据。 + * Build workflow scheme data. + * + * @param array $data + * @access protected + * @return object + */ + protected function buildWorkflowSchemeData(array $data): object + { + $workflow = new stdclass(); + $workflow->ID = $data['id']; + $workflow->NAME = $data['name']; + $workflow->DESCRIPTION = zget($data, 'description', ''); + + return $workflow; + } + + /** + * 构建工作日志数据。 + * Build work log data. + * + * @param array $data + * @access protected + * @return object + */ + protected function buildWorklogData(array $data): object + { + $worklog = new stdclass(); + $worklog->ID = $data['id']; + $worklog->issueid = $data['issue']; + $worklog->AUTHOR = zget($data, 'author', ''); + $worklog->worklogbody = zget($data, 'body', ''); + $worklog->timeworked = zget($data, 'timeworked', 0); + $worklog->CREATED = zget($data, 'created', NULL); + + return $worklog; + } + + /** + * 构建节点关联数据。 + * Build node association data. + * + * @param array $data + * @access protected + * @return object + */ + protected function buildNodeAssociationData(array $data): object + { + $association = new stdclass(); + $association->SOURCE_NODE_ID = $data['sourceNodeId']; + $association->SOURCE_NODE_ENTITY = $data['sourceNodeEntity']; + $association->SINK_NODE_ID = $data['sinkNodeId']; + $association->SINK_NODE_ENTITY = $data['sinkNodeEntity']; + + return $association; + } + + /** + * 获取Jira事务与禅道数据的关联关系。 + * Get Jira issue and ZenTao object links. * * @access protected * @return array */ protected function getIssueData(): array { - $issueStories = $this->dao->dbh($this->dbh)->select('AID,BID')->from(JIRA_TMPRELATION) - ->where('AType')->eq('jstory') - ->andWhere('BType')->eq('zstory') - ->fetchPairs(); - - $issueTasks = $this->dao->dbh($this->dbh)->select('AID,BID')->from(JIRA_TMPRELATION) - ->where('AType')->eq('jtask') - ->andWhere('BType')->eq('ztask') - ->fetchPairs(); - - $issueBugs = $this->dao->dbh($this->dbh)->select('AID,BID')->from(JIRA_TMPRELATION) - ->where('AType')->eq('jbug') - ->andWhere('BType')->eq('zbug') - ->fetchPairs(); - - $issueObjectType = $this->dao->dbh($this->dbh)->select('AID,extra')->from(JIRA_TMPRELATION) - ->where('AType')->eq('jissueid') - ->andWhere('BType')->eq('zissuetype') - ->fetchPairs(); - - return array($issueStories, $issueTasks, $issueBugs, $issueObjectType); - } - - /** - * 将对象转换为数组。 - * Convert object to array. - * - * @param object|array $parsedXML - * @access protected - * @return array - */ - protected function object2Array(object|array $parsedXML): array - { - if(is_object($parsedXML)) - { - $parsedXML = (array)$parsedXML; - } - - if(is_array($parsedXML)) - { - foreach($parsedXML as $key => $value) - { - if(!is_object($value) && !is_array($value)) continue; - $parsedXML[$key] = $this->object2Array($value); - } - } - - return $parsedXML; - } - - /** - * 转换需求阶段。 - * Convert stage. - * - * @param string $jiraStatus - * @access protected - * @return string - */ - protected function convertStage(string $jiraStatus): string - { - $stage = 'wait'; - $relations = $this->session->jiraRelation; - - $jiraStatusList = array_flip($relations['jiraStatus']); - $stageID = $jiraStatusList[$jiraStatus]; - - if(!empty($relations['storyStage'][$stageID])) $stage = $relations['storyStage'][$stageID]; - - return $stage; - } - - /** - * 转换状态。 - * Convert jira status. - * - * @param string $objectType - * @param string $jiraStatus - * @access protected - * @return string - */ - protected function convertStatus(string $objectType, string $jiraStatus): string - { - $status = $objectType == 'task' ? 'wait' : 'active'; - $relations = $this->session->jiraRelation; - - $arrayKey = "{$objectType}Status"; - $jiraStatusList = array_flip($relations['jiraStatus']); - $statusID = $jiraStatusList[$jiraStatus]; - - if(!empty($relations[$arrayKey][$statusID])) $status = $relations[$arrayKey][$statusID]; - - return $status; + return $this->dao->dbh($this->dbh)->select('*')->from(JIRA_TMPRELATION)->where('BID')->ne('')->andWhere('extra')->eq('issue')->fetchAll('AID'); } /** @@ -387,15 +577,16 @@ class convertTao extends convertModel * * @param array $dataList * @access protected - * @return void + * @return bool */ - protected function importJiraUser(array $dataList): void + protected function importJiraUser(array $dataList): bool { $localUsers = $this->dao->dbh($this->dbh)->select('account')->from(TABLE_USER)->where('deleted')->eq('0')->fetchPairs(); $userConfig = $this->session->jiraUser; foreach($dataList as $data) { + $data->account = $this->processJiraUser($data->account, $data->email); if(isset($localUsers[$data->account])) continue; $user = new stdclass(); @@ -405,11 +596,11 @@ class convertTao extends convertModel $user->email = isset($data->email) ? $data->email : ''; $user->gender = 'm'; $user->type = 'inside'; - $user->join = isset($data->join) ? $data->join : ''; + $user->join = !empty($data->join) ? $data->join : helper::now(); $this->dao->dbh($this->dbh)->replace(TABLE_USER)->data($user, 'group')->exec(); - if(!dao::isError()) + if(!dao::isError() && !empty($userConfig['group'])) { $data = new stdclass(); $data->account = $user->account; @@ -418,141 +609,180 @@ class convertTao extends convertModel $this->dao->dbh($this->dbh)->replace(TABLE_USERGROUP)->data($data)->exec(); } + + $this->createTmpRelation('juser', $data->ID, 'zuser', $user->account); } + + return true; + } + + /** + * 记录Jira数据和禅道数据的关联关系。 + * Create data tmp relation. + * + * @param string $AType + * @param string|int $AID + * @param string $BType + * @param string|int $BID + * @param string $extra + * @access protected + * @return object + */ + protected function createTmpRelation(string $AType, string|int $AID, string $BType, string|int $BID, string $extra = ''): object + { + $relation = new stdclass(); + $relation->AType = $AType; + $relation->BType = $BType; + $relation->AID = $AID; + $relation->BID = $BID; + $relation->extra = $extra; + + $this->dao->dbh($this->dbh)->insert(JIRA_TMPRELATION)->data($relation)->exec(); + + return $relation; } /** * 导入project数据。 * Import jira project. * - * @param array $dataList - * @param string $method db|file + * @param array $dataList * @access protected - * @return void + * @return bool */ - protected function importJiraProject(array $dataList, string $method = 'db'): void + protected function importJiraProject(array $dataList): bool { - global $app; - $app->loadConfig('execution'); - $app->loadLang('doc'); + $this->app->loadConfig('execution'); + $this->app->loadLang('doc'); + $projectRoleActor = $this->getJiraProjectRoleActor(); + $archivedProject = $this->getJiraArchivedProject($dataList); + $sprintGroup = $this->getJiraSprint(array_keys($dataList)); + $jiraProjectRelation = $this->dao->dbh($this->dbh)->select('*')->from(JIRA_TMPRELATION)->where('AType')->eq('jproject')->fetchAll('AID'); foreach($dataList as $id => $data) { - $project = $this->createProject($data, $method); - $executionID = $this->createExecution($project); - $this->createProduct($project, (int)$executionID); + if(!empty($jiraProjectRelation[$id])) continue; - $projectRelation = array(); - $projectRelation['AType'] = 'jproject'; - $projectRelation['BType'] = 'zproject'; - $projectRelation['AID'] = $id; - $projectRelation['BID'] = $project->id; - $this->dao->dbh($this->dbh)->insert(JIRA_TMPRELATION)->data($projectRelation)->exec(); + $data->status = !empty($archivedProject[$data->ID]) ? 'closed' : 'wait'; - $executionRelation = array(); - $executionRelation['AType'] = 'jproject'; - $executionRelation['BType'] = 'zexecution'; - $executionRelation['AID'] = $id; - $executionRelation['BID'] = $executionID; - $this->dao->dbh($this->dbh)->insert(JIRA_TMPRELATION)->data($executionRelation)->exec(); + $project = $this->createProject($data, $projectRoleActor); + $executions = $this->createExecution($id, $project, $sprintGroup, $projectRoleActor); + $productID = $this->createProduct($project, $executions); - $keyRelation = array(); - $keyRelation['AType'] = 'joldkey'; - $keyRelation['BType'] = 'jnewkey'; - $keyRelation['AID'] = $data->ORIGINALKEY; - $keyRelation['BID'] = $data->pkey; - $keyRelation['extra'] = $data->ID; - $this->dao->dbh($this->dbh)->insert(JIRA_TMPRELATION)->data($keyRelation)->exec(); + $this->createTmpRelation('jproject', $id, 'zproject', $project->id); + $this->createTmpRelation('jproject', $id, 'zproduct', $productID); + $this->createTmpRelation('joldkey', $data->ORIGINALKEY, 'jnewkey', $data->pkey, $data->ID); } + + return true; } /** * 导入issue数据。 * Import jira issue. * - * @param array $dataList - * @param string $method db|file + * @param array $dataList * @access protected - * @return void + * @return bool */ - protected function importJiraIssue(array $dataList, string $method = 'db'): void + protected function importJiraIssue(array $dataList): bool { - $projectRelation = $this->dao->dbh($this->dbh)->select('AID,BID')->from(JIRA_TMPRELATION) - ->where('AType')->eq('jproject') - ->andWhere('BType')->eq('zproject') - ->fetchPairs(); + $jiraRelation = $this->session->jiraRelation; + $relations = $jiraRelation ? json_decode($jiraRelation, true) : array(); - $projectProduct = $this->dao->dbh($this->dbh)->select('project,product')->from(TABLE_PROJECTPRODUCT) - ->where('project')->in(array_values($projectRelation)) - ->fetchPairs(); - - $projectKeys = $this->dao->dbh($this->dbh)->select('extra as ID, AID as oldKey, BID as newKey')->from(JIRA_TMPRELATION) - ->where('AType')->eq('joldkey') - ->andWhere('BType')->eq('jnewkey') - ->fetchAll('ID'); - - $projectExecution = $this->dao->dbh($this->dbh)->select('project,id')->from(TABLE_PROJECT) - ->where('project')->in(array_values($projectRelation)) - ->fetchPairs(); - - $relations = $this->session->jiraRelation; - $issueTypeList = array(); - $reasonList = array(); - $resolutionList = array(); - foreach($relations['jiraObject'] as $id => $jiraCode) $issueTypeList[$jiraCode] = $relations['zentaoObject'][$id]; - foreach($relations['jiraResolution'] as $id => $jiraCode) - { - if(!empty($relations['zentaoReason'][$id])) $reasonList[$jiraCode] = $relations['zentaoReason'][$id]; - if(!empty($relations['zentaoResolution'][$id])) $resolutionList[$jiraCode] = $relations['zentaoResolution'][$id]; - } + $projectRelation = $this->dao->dbh($this->dbh)->select('AID,BID')->from(JIRA_TMPRELATION)->where('AType')->eq('jproject')->andWhere('BType')->eq('zproject')->fetchPairs(); + $productRelation = $this->dao->dbh($this->dbh)->select('AID,BID')->from(JIRA_TMPRELATION)->where('AType')->eq('jproject')->andWhere('BType')->eq('zproduct')->fetchPairs(); + $projectKeys = $this->dao->dbh($this->dbh)->select('extra,AID,BID')->from(JIRA_TMPRELATION)->where('AType')->eq('joldkey')->andWhere('BType')->eq('jnewkey')->fetchAll('extra'); + $sprintRelation = $this->dao->dbh($this->dbh)->select('AID,BID')->from(JIRA_TMPRELATION)->where('AType')->eq('jsprint')->andWhere('BType')->eq('zexecution')->fetchPairs(); + $defaultExecution = $this->dao->dbh($this->dbh)->select('AID,BID')->from(JIRA_TMPRELATION)->where('AType')->eq('jproject')->andWhere('BType')->eq('zexecution')->fetchPairs(); + $issueList = $this->getIssueData(); + $jiraSprintList = $this->getJiraSprintIssue(); + $jiraActions = $this->getJiraWorkflowActions(); + $customFields = $this->getJiraData($this->session->jiraMethod, 'customfield'); + $fieldValues = $this->getJiraData($this->session->jiraMethod, 'customfieldvalue'); + $fieldOptions = $this->getJiraData($this->session->jiraMethod, 'customfieldoption'); + $relations = $this->createWorkflow($relations, $jiraActions); + $relations = $this->createWorkflowField($relations, $customFields, $fieldOptions); + $relations = $this->createWorkflowStatus($relations); + $relations = $this->createWorkflowAction($relations, $jiraActions); + $relations = $this->createWorkflowGroup($relations, $projectRelation, $productRelation); + $relations = $this->createResolution($relations); + $workflows = $this->dao->dbh($this->dbh)->select('module,`table`')->from(TABLE_WORKFLOW)->where('buildin')->eq('0')->fetchPairs(); foreach($dataList as $id => $data) { + if(!empty($issueList[$data->ID])) continue; + $issueProject = $data->PROJECT; if(!isset($projectRelation[$issueProject])) continue; - $projectID = $projectRelation[$issueProject]; - $productID = $projectProduct[$projectID]; - $executionID = $projectExecution[$projectID]; + /* 将自定义字段数据赋值给issue对象。 */ + $data->execution = !empty($jiraSprintList[$data->ID]) ? $jiraSprintList[$data->ID] : ''; + foreach($fieldValues as $fieldValue) + { + if($fieldValue->ISSUE == $data->ID) + { + $data->{$fieldValue->CUSTOMFIELD} = $fieldValue->STRINGVALUE; - $issueType = isset($issueTypeList[$data->issuetype]) ? $issueTypeList[$data->issuetype] : 'task'; - if($issueType == 'requirement' || $issueType == 'story') - { - $this->createStory((int)$productID, (int)$projectID, (int)$executionID, $issueType, $data, $method, $reasonList); - } - elseif($issueType == 'task' || $issueType == 'subTask') - { - $this->createTask((int)$projectID, (int)$executionID, $data, $method, $reasonList); - } - elseif($issueType == 'bug') - { - $this->createBug((int)$productID, (int)$projectID, $data, $method, $resolutionList); + $fieldKey = $customFields[$fieldValue->CUSTOMFIELD]->CUSTOMFIELDTYPEKEY; + if($fieldKey == 'com.pyxis.greenhopper.jira:gh-sprint' && !empty($sprintRelation[$fieldValue->STRINGVALUE])) $data->execution = $sprintRelation[$fieldValue->STRINGVALUE]; + } } - $oldKey = zget($projectKeys[$issueProject], 'oldKey', ''); - $newKey = zget($projectKeys[$issueProject], 'newKey', ''); + $projectID = $projectRelation[$issueProject]; + $productID = $productRelation[$issueProject]; + $executionID = !empty($data->execution) ? $data->execution : $defaultExecution[$issueProject]; + $zentaoObject = $relations['zentaoObject'][$data->issuetype]; + + if($zentaoObject == 'requirement' || $zentaoObject == 'story' || $zentaoObject == 'epic') + { + $this->createStory((int)$productID, (int)$projectID, (int)$executionID, $zentaoObject, $data, $relations); + } + elseif($zentaoObject == 'task') + { + $this->createTask((int)$projectID, (int)$executionID, $data, $relations); + } + elseif($zentaoObject == 'bug') + { + $this->createBug((int)$productID, (int)$projectID, (int)$executionID, $data, $relations); + } + elseif($zentaoObject == 'testcase') + { + $this->createCase((int)$productID, (int)$projectID, (int)$executionID, $data, $relations); + } + elseif($zentaoObject == 'feedback') + { + $this->createFeedback((int)$productID, $data, $relations); + } + elseif($zentaoObject == 'ticket') + { + $this->createTicket((int)$productID, $data, $relations); + } + else + { + $this->createFlowData((int)$productID, (int)$projectID, $zentaoObject, $data, $relations, $workflows); + } + + $oldKey = zget($projectKeys[$issueProject], 'AID', ''); + $newKey = zget($projectKeys[$issueProject], 'BID', ''); $issueKey = $oldKey ? $oldKey . '-' . $data->issuenum : $newKey . '-' . $data->issuenum; - - $fileRelation['AType'] = 'jissueid'; - $fileRelation['BType'] = 'jfilepath'; - $fileRelation['AID'] = $data->ID; - $fileRelation['extra'] = "{$oldKey}/10000/{$issueKey}/"; - $this->dao->dbh($this->dbh)->insert(JIRA_TMPRELATION)->data($fileRelation)->exec(); + $this->createTmpRelation('jissueid', $data->ID, 'jfilepath', '', "{$oldKey}/10000/{$issueKey}/"); } + + return true; } /** * 导入版本数据。 * Import jira build. * - * @param array $dataList - * @param string $method db|file + * @param array $dataList * @access protected - * @return void + * @return bool */ - protected function importJiraBuild(array $dataList, string $method = 'db'): void + protected function importJiraBuild(array $dataList): bool { - list($issueStories, $issueTasks, $issueBugs, $issueObjectType) = $this->getIssueData(); + $issueList = $this->getIssueData(); $projectRelation = $this->dao->dbh($this->dbh)->select('AID,BID')->from(JIRA_TMPRELATION) ->where('AType')->eq('jproject') @@ -563,37 +793,55 @@ class convertTao extends convertModel ->where('project')->in(array_values($projectRelation)) ->fetchPairs(); - $versionGroup = $method == 'db' ? $this->dao->dbh($this->sourceDBH)->select('SINK_NODE_ID as versionID, SOURCE_NODE_ID as issueID, ASSOCIATION_TYPE as relation')->from(JIRA_NODEASSOCIATION)->where('SINK_NODE_ENTITY')->eq('Version')->fetchGroup('versionID') : $this->getVersionGroup(); + $productSystem = $this->dao->dbh($this->dbh)->select('id,product')->from(TABLE_SYSTEM)->fetchAll('product'); + if($this->session->jiraMethod == 'db') + { + $versionGroup = $this->dao->dbh($this->sourceDBH)->select('SINK_NODE_ID as versionID, SOURCE_NODE_ID as issueID, ASSOCIATION_TYPE as relation')->from(JIRA_NODEASSOCIATION)->where('SINK_NODE_ENTITY')->eq('Version')->fetchGroup('versionID'); + } + else + { + $versionGroup = $this->getVersionGroup(); + } + + $versionRelation = $this->dao->dbh($this->dbh)->select('*')->from(JIRA_TMPRELATION)->where('AType')->eq('jversion')->fetchAll('AID'); foreach($dataList as $data) { + if(!empty($versionRelation[$data->ID])) continue; + $buildProject = $data->PROJECT; $projectID = $projectRelation[$buildProject]; $productID = $projectProduct[$projectID]; - $build = $this->createBuild((int)$productID, (int)$projectID, $data, $versionGroup, $method, $issueObjectType, $issueBugs, $issueStories); - if(empty($data->RELEASEDATE)) continue; + $system = !empty($productSystem[$productID]) ? $productSystem[$productID]->id : 0; + $build = $this->createBuild((int)$productID, (int)$projectID, $system, $data, $versionGroup, $issueList); - $this->createRelease($build, $data, $versionGroup, $method, $issueObjectType, $issueBugs, $issueStories); + $this->createRelease($build, $data, $versionGroup, $issueList); + + $this->createTmpRelation('jversion', $data->ID, 'zbuild', $build->id); } + + return true; } /** * 导入issue link数据。 * Import jira issue link. * - * @param array $dataList + * @param array $dataList * @access protected - * @return void + * @return bool */ - protected function importJiraIssueLink(array $dataList): void + protected function importJiraIssueLink(array $dataList): bool { - list($issueStories, $issueTasks, $issueBugs, $issueObjectType) = $this->getIssueData(); - $issueLinkTypeList = array(); - $relations = $this->session->jiraRelation; - foreach($relations['jiraLinkType'] as $id => $jiraCode) $issueLinkTypeList[$jiraCode] = $relations['zentaoLinkType'][$id]; + $issueList = $this->getIssueData(); + $jiraRelation = $this->session->jiraRelation; + $relations = $jiraRelation ? json_decode($jiraRelation, true) : array(); + + if($this->config->edition != 'open') $relations = $this->createRelation($relations); $storyLink = $taskLink = $duplicateLink = $relatesLink = array(); + $issueLinkTypeList = $relations['zentaoLinkType']; foreach($dataList as $issueLink) { $linkType = $issueLink->LINKTYPE; @@ -603,82 +851,132 @@ class convertTao extends convertModel if($issueLinkTypeList[$linkType] == 'relates') $relatesLink[$issueLink->SOURCE] = $issueLink->DESTINATION; } - $this->updateSubStory($storyLink, $issueStories); - $this->updateSubTask($taskLink, $issueTasks); - $this->updateDuplicateStoryAndBug($duplicateLink, $issueObjectType, $issueStories, $issueBugs); - $this->updateRelatesObject($relatesLink, $issueObjectType, $issueStories, $issueTasks, $issueBugs); + $this->updateSubStory($storyLink, $issueList); + $this->updateSubTask($taskLink, $issueList); + $this->updateDuplicateStoryAndBug($duplicateLink, $issueList); + $this->updateRelatesObject($relatesLink, $issueList); + if($this->config->edition != 'open') $this->updateObjectRelation($issueLinkTypeList, $dataList, $issueList); + + return true; } /** - * 导入action数据。 + * 导入工作日志数据。 + * Import jira worklog. + * + * @param array $dataList + * @access protected + * @return bool + */ + protected function importJiraWorkLog(array $dataList): bool + { + $issueList = $this->getIssueData(); + $worklogRelation = $this->dao->dbh($this->dbh)->select('*')->from(JIRA_TMPRELATION)->where('AType')->eq('jworklog')->fetchAll('AID'); + foreach($dataList as $data) + { + if(!empty($worklogRelation[$data->ID])) continue; + + $issueID = $data->issueid; + if(!isset($issueList[$issueID])) continue; + + $objectType = zget($issueList[$issueID], 'BType', ''); + $objectID = zget($issueList[$issueID], 'BID', ''); + + if(empty($objectID)) continue; + + $effort = new stdclass(); + $effort->vision = $this->config->vision; + $effort->objectID = $objectID; + $effort->date = !empty($data->CREATED) ? substr($data->CREATED, 0, 10) : null; + $effort->account = $this->getJiraAccount(isset($data->AUTHOR) ? $data->AUTHOR : ''); + $effort->consumed = round($data->timeworked / 3600); + $effort->work = $data->worklogbody; + $effort->objectType = substr($objectType, 1); + $this->dao->dbh($this->dbh)->insert(TABLE_EFFORT)->data($effort)->exec(); + $effortID = $this->dao->dbh($this->dbh)->lastInsertID(); + + $this->createTmpRelation('jworklog', $data->ID, 'zeffort', $effortID); + } + + return true; + } + + /** + * 导入issue评论数据。 * Import jira action. * - * @param array $dataList - * @param string $method db|file + * @param array $dataList * @access protected - * @return void + * @return bool */ - protected function importJiraAction(array $dataList, string $method = 'db'): void + protected function importJiraAction(array $dataList): bool { - list($issueStories, $issueTasks, $issueBugs, $issueObjectType) = $this->getIssueData(); + $issueList = $this->getIssueData(); + + $actionRelation = $this->dao->dbh($this->dbh)->select('*')->from(JIRA_TMPRELATION)->where('AType')->eq('jaction')->fetchAll('AID'); foreach($dataList as $data) { + if(!empty($actionRelation[$data->ID])) continue; + $issueID = $data->issueid; $comment = $data->actionbody; if(empty($comment)) continue; - if(!isset($issueObjectType[$issueID])) continue; + if(!isset($issueList[$issueID])) continue; - $objectType = $issueObjectType[$issueID]; - if($objectType == 'task') $objectID = $issueTasks[$issueID]; - if($objectType == 'bug') $objectID = $issueBugs[$issueID]; - if($objectType == 'story') $objectID = $issueStories[$issueID]; + $objectType = zget($issueList[$issueID], 'BType', ''); + $objectID = zget($issueList[$issueID], 'BID', ''); if(empty($objectID)) continue; $action = new stdclass(); - $action->objectType = $objectType; + $action->objectType = substr($objectType, 1); $action->objectID = $objectID; - $action->actor = $this->getJiraAccount(isset($data->AUTHOR) ? $data->AUTHOR : '', $method); + $action->actor = $this->getJiraAccount(isset($data->AUTHOR) ? $data->AUTHOR : ''); $action->action = 'commented'; $action->date = isset($data->CREATED) ? substr($data->CREATED, 0, 19) : ''; $action->comment = $comment; $this->dao->dbh($this->dbh)->insert(TABLE_ACTION)->data($action)->exec(); + $actionID = $this->dao->dbh($this->dbh)->lastInsertID(); + + $this->createTmpRelation('jaction', $data->ID, 'zaction', $actionID); } + + return true; } /** * 导入file数据。 * Import jira file. * - * @param array $dataList - * @param string $method db|file + * @param array $dataList * @access protected - * @return void + * @return bool */ - protected function importJiraFile(array $dataList, string $method = 'db'): void + protected function importJiraFile(array $dataList): bool { $this->loadModel('file'); - list($issueStories, $issueTasks, $issueBugs, $issueObjectType) = $this->getIssueData(); + $issueList = $this->getIssueData(); $filePaths = $this->dao->dbh($this->dbh)->select('AID,extra')->from(JIRA_TMPRELATION) ->where('AType')->eq('jissueid') ->andWhere('BType')->eq('jfilepath') ->fetchPairs(); + $fileRelation = $this->dao->dbh($this->dbh)->select('*')->from(JIRA_TMPRELATION)->where('AType')->eq('jfile')->fetchAll('AID'); + foreach($dataList as $fileAttachment) { + if(!empty($fileRelation[$fileAttachment->ID])) continue; + $issueID = $fileAttachment->issueid; - if(!isset($issueObjectType[$issueID])) continue; + if(!isset($issueList[$issueID])) continue; - $objectType = $issueObjectType[$issueID]; - if($objectType != 'bug' && $objectType != 'task' && $objectType != 'story') continue; + $objectType = zget($issueList[$issueID], 'BType', ''); + $objectID = zget($issueList[$issueID], 'BID', ''); - if($objectType == 'bug') $objectID = $issueBugs[$issueID]; - if($objectType == 'task') $objectID = $issueTasks[$issueID]; - if($objectType == 'story') $objectID = $issueStories[$issueID]; if(empty($objectID)) continue; $fileID = $fileAttachment->ID; @@ -690,83 +988,148 @@ class convertTao extends convertModel $file->title = str_ireplace(".{$extension}", '', $fileName); $file->extension = $extension; $file->size = $fileAttachment->FILESIZE; - $file->objectType = $objectType; + $file->objectType = substr($objectType, 1); $file->objectID = $objectID; - $file->addedBy = $this->getJiraAccount(isset($fileAttachment->AUTHOR) ? $fileAttachment->AUTHOR : '', $method); - $file->addedDate = isset($fileAttachment->CREATED) ? substr($fileAttachment->CREATED, 0, 19) : null; + $file->extra = in_array($file->objectType, array('epic', 'requirement', 'story')) ? '1' : ''; + $file->addedBy = $this->getJiraAccount(isset($fileAttachment->AUTHOR) ? $fileAttachment->AUTHOR : ''); + $file->addedDate = !empty($fileAttachment->CREATED) ? substr($fileAttachment->CREATED, 0, 19) : null; $this->dao->dbh($this->dbh)->insert(TABLE_FILE)->data($file)->exec(); $jiraFile = $this->app->getTmpRoot() . 'attachments/' . $filePaths[$issueID] . $fileID; if(is_file($jiraFile)) copy($jiraFile, $this->file->savePath . $file->pathname); + + $fileID = $this->dao->dbh($this->dbh)->lastInsertID(); + + if(in_array($file->objectType, array('epic', 'requirement', 'story'))) + { + $this->dao->dbh($this->dbh)->update(TABLE_STORYSPEC)->set("files=IF(files IS NOT NULL, CONCAT(files,',{$fileID},'), '{$fileID}')")->where('story')->eq($file->objectID)->andWhere('version')->eq($file->extra)->exec(); + } + + $this->createTmpRelation('jfile', $fileAttachment->ID, 'zfile', $fileID); } + + return true; + } + + /** + * 创建团队成员。 + * Create team member. + * + * @param int $objectID + * @param string $createdBy + * @param string $type + * @access protected + * @return bool + */ + protected function createTeamMember(int $objectID, string $createdBy, string $type): bool + { + $member = new stdclass(); + $member->root = $objectID; + $member->account = $createdBy; + $member->role = ''; + $member->join = helper::now(); + $member->type = $type; + $member->days = 0; + $member->hours = $this->config->execution->defaultWorkhours; + + $this->dao->dbh($this->dbh)->insert(TABLE_TEAM)->data($member)->exec(); + + return true; + } + + /** + * 为项目或者迭代创建默认的文档库。 + * Create doc lib. + * + * @param int $projectID + * @param int $executionID + * @param string $name + * @param string $type + * @access protected + * @return boll + */ + protected function createDocLib(int $projectID, int $executionID, string $name, string $type): bool + { + $docLib = new stdclass(); + $docLib->project = $projectID; + $docLib->execution = $executionID; + $docLib->name = $name; + $docLib->type = $type; + $docLib->main = '1'; + $docLib->acl = 'default'; + + $this->dao->dbh($this->dbh)->insert(TABLE_DOCLIB)->data($docLib)->exec(); + + return true; } /** * 创建项目。 * Create project. * - * @param object $data - * @param string $method db|file + * @param object $data + * @param array $projectRoleActor * @access protected * @return object */ - protected function createProject(object $data, string $method): object + protected function createProject(object $data, array $projectRoleActor): object { /* Create project. */ $project = new stdclass(); - $project->name = $data->pname; + $project->name = substr($data->pname, 0, 90); $project->code = $data->pkey; $project->desc = isset($data->DESCRIPTION) ? $data->DESCRIPTION : ''; - $project->status = 'wait'; + $project->status = $data->status; $project->type = 'project'; $project->model = 'scrum'; $project->grade = 1; $project->acl = 'open'; $project->end = date('Y-m-d', time() + 30 * 24 * 3600); - $project->PM = $this->getJiraAccount(isset($data->LEAD) ? $data->LEAD : '', $method); - $project->openedBy = $this->getJiraAccount(isset($data->LEAD) ? $data->LEAD : '', $method); + $project->PM = $this->getJiraAccount(isset($data->LEAD) ? $data->LEAD : ''); + $project->openedBy = $this->getJiraAccount(isset($data->LEAD) ? $data->LEAD : ''); $project->openedDate = helper::now(); $project->openedVersion = $this->config->version; + $project->storyType = 'story,epic,requirement'; $this->dao->dbh($this->dbh)->insert(TABLE_PROJECT)->data($project)->exec(); + $projectID = $this->dao->dbh($this->dbh)->lastInsertID(); + $this->loadModel('action')->create('project', $projectID, 'opened'); + $this->dao->dbh($this->dbh)->update(TABLE_PROJECT)->set('`order`')->eq($projectID * 5)->set('path')->eq(",$projectID,")->where('id')->eq($projectID)->exec(); - /* Create member. */ - $member = new stdclass(); - $member->root = $projectID; - $member->account = $project->openedBy; - $member->role = ''; - $member->join = ''; - $member->type = 'project'; - $member->days = 0; - $member->hours = $this->config->execution->defaultWorkhours; - $this->dao->dbh($this->dbh)->insert(TABLE_TEAM)->data($member)->exec(); + $this->createTeamMember($projectID, $project->openedBy, 'project'); + /* 如果Jira项目有团队成员。 */ + if(!empty($projectRoleActor[$data->ID])) + { + foreach($projectRoleActor[$data->ID] as $userID) + { + $account = $this->getJiraAccount($userID); + if($account == $project->openedBy) continue; - /* Create doc lib. */ - $lib = new stdclass(); - $lib->project = $projectID; - $lib->name = $this->lang->doclib->main['project']; - $lib->type = 'project'; - $lib->main = '1'; - $lib->acl = 'default'; - $this->dao->dbh($this->dbh)->insert(TABLE_DOCLIB)->data($lib)->exec(); + $this->createTeamMember($projectID, $account, 'project'); + } + } + + $this->createDocLib($projectID, 0, $this->lang->doclib->main['project'], 'project'); $project->id = $projectID; return $project; } /** - * 创建执行。 - * Create execution. + * 为项目创建默认的同名执行。 + * Create default execution. * - * @param object $project + * @param int $jiraProjectID + * @param object $project + * @param array $projectRoleActor * @access protected * @return int */ - protected function createExecution(object $project): int + protected function createDefaultExecution(int $jiraProjectID, object $project, array $projectRoleActor): int { - /* Create execution. */ + /* Create default execution. */ $execution = new stdclass(); $execution->name = $project->name; $execution->code = $project->code; @@ -785,42 +1148,108 @@ class convertTao extends convertModel $this->dao->dbh($this->dbh)->insert(TABLE_PROJECT)->data($execution)->exec(); $executionID = $this->dao->dbh($this->dbh)->lastInsertID(); + $this->loadModel('action')->create('execution', $executionID, 'opened'); + $this->dao->dbh($this->dbh)->update(TABLE_PROJECT)->set('`order`')->eq($executionID * 5)->set('path')->eq(",{$project->id},$executionID,")->where('id')->eq($executionID)->exec(); - /* Create member. */ - $member = new stdclass(); - $member->root = $executionID; - $member->account = $execution->openedBy; - $member->role = ''; - $member->join = ''; - $member->type = 'execution'; - $member->days = 0; - $member->hours = $this->config->execution->defaultWorkhours; - $this->dao->dbh($this->dbh)->insert(TABLE_TEAM)->data($member)->exec(); + $this->createTeamMember($executionID, $execution->openedBy, 'execution'); - /* Create doc lib. */ - $lib = new stdclass(); - $lib->project = $project->id; - $lib->execution = $executionID; - $lib->name = $this->lang->doclib->main['execution']; - $lib->type = 'execution'; - $lib->main = '1'; - $lib->acl = 'default'; - $this->dao->dbh($this->dbh)->insert(TABLE_DOCLIB)->data($lib)->exec(); + /* 如果Jira项目有团队成员。 */ + if(!empty($projectRoleActor[$jiraProjectID])) + { + foreach($projectRoleActor[$jiraProjectID] as $userID) + { + $account = $this->getJiraAccount($userID); + if($account == $execution->openedBy) continue; + + $this->createTeamMember($executionID, $account, 'execution'); + } + } + + $this->createDocLib($project->id, $executionID, $this->lang->doclib->main['execution'], 'execution'); + $this->createTmpRelation('jproject', $jiraProjectID, 'zexecution', $executionID); return $executionID; } + /** + * 创建执行。 + * Create execution. + * + * @param int $jiraProjectID + * @param object $project + * @param array $sprintGroup + * @param array $projectRoleActor + * @access protected + * @return array + */ + protected function createExecution(int $jiraProjectID, object $project, array $sprintGroup, array $projectRoleActor): array + { + $executions = array(); + $executions[] = $this->createDefaultExecution($jiraProjectID, $project, $projectRoleActor); + + if(!empty($sprintGroup[$jiraProjectID])) + { + $zentaoStatus = array('future' => 'wait', 'active' => 'doing', 'closed' => 'closed'); + foreach($sprintGroup[$jiraProjectID] as $sprint) + { + /* Create execution. */ + $execution = new stdclass(); + $execution->name = $sprint->name; + $execution->code = 'jirasprint' . $sprint->id; + $execution->status = zget($zentaoStatus, $sprint->state, 'wait'); + $execution->project = $project->id; + $execution->parent = $project->id; + $execution->grade = 1; + $execution->desc = !empty($sprint->goal) ? $sprint->goal : ''; + $execution->type = 'sprint'; + $execution->acl = 'open'; + $execution->begin = !empty($sprint->startDate) ? substr($sprint->startDate, 0, 10) : helper::now(); + $execution->end = !empty($sprint->endDate) ? substr($sprint->endDate, 0, 10) : date('Y-m-d', time() + 24 * 3600); + $execution->PM = $project->PM; + $execution->openedBy = $project->openedBy; + $execution->openedDate = helper::now(); + $execution->openedVersion = $this->config->version; + + $this->dao->dbh($this->dbh)->insert(TABLE_PROJECT)->data($execution)->exec(); + $executionID = $this->dao->dbh($this->dbh)->lastInsertID(); + $this->loadModel('action')->create('execution', $executionID, 'opened'); + + $this->dao->dbh($this->dbh)->update(TABLE_PROJECT)->set('`order`')->eq($executionID * 5)->set('path')->eq(",{$project->id},$executionID,")->where('id')->eq($executionID)->exec(); + + $this->createTeamMember($executionID, $execution->openedBy, 'execution'); + + if(!empty($projectRoleActor[$jiraProjectID])) + { + foreach($projectRoleActor[$jiraProjectID] as $userID) + { + $account = $this->getJiraAccount($userID); + if($account == $execution->openedBy) continue; + + $this->createTeamMember($executionID, $account, 'execution'); + } + } + + $this->createDocLib($project->id, $executionID, $this->lang->doclib->main['execution'], 'execution'); + $this->createTmpRelation('jsprint', $sprint->id, 'zexecution', $executionID); + + $executions[] = $executionID; + } + } + + return $executions; + } + /** * 创建产品。 * Create product. * - * @param object $project - * @param int $executionID + * @param object $project + * @param array $executions * @access protected - * @return void + * @return int */ - protected function createProduct(object $project, int $executionID): void + protected function createProduct(object $project, array $executions): int { /* Create product. */ $product = new stdclass(); @@ -835,54 +1264,69 @@ class convertTao extends convertModel $this->dao->dbh($this->dbh)->insert(TABLE_PRODUCT)->data($product)->exec(); $productID = $this->dao->dbh($this->dbh)->lastInsertID(); + $this->loadModel('action')->create('product', $productID, 'opened'); - /* Create doc lib. */ - $lib = new stdclass(); - $lib->product = $productID; - $lib->name = $this->lang->doclib->main['product']; - $lib->type = 'product'; - $lib->main = '1'; - $lib->acl = 'default'; - $this->dao->dbh($this->dbh)->insert(TABLE_DOCLIB)->data($lib)->exec(); + /* 创建产品同名的应用。 */ + $system = new stdclass(); + $system->name = substr($product->name, 0, 80) . '-' . uniqid(); + $system->product = $productID; + $system->status = 'active'; + $system->desc = ''; + $system->integrated = 0; + $system->createdBy = $this->app->user->account; + $system->createdDate = helper::now(); + $this->loadModel('system')->create($system); $this->dao->dbh($this->dbh)->update(TABLE_PRODUCT)->set('`order`')->eq($productID * 5)->where('id')->eq($productID)->exec(); $this->dao->dbh($this->dbh)->replace(TABLE_PROJECTPRODUCT)->set('project')->eq($project->id)->set('product')->eq($productID)->set('branch')->eq('0')->exec(); - $this->dao->dbh($this->dbh)->replace(TABLE_PROJECTPRODUCT)->set('project')->eq($executionID)->set('product')->eq($productID)->set('branch')->eq('0')->exec(); + + $this->createDocLib($productID, 0, $this->lang->doclib->main['product'], 'product'); + + /* 关联产品与迭代。 */ + foreach($executions as $executionID) $this->dao->dbh($this->dbh)->replace(TABLE_PROJECTPRODUCT)->set('project')->eq($executionID)->set('product')->eq($productID)->set('branch')->eq('0')->exec(); + + return $productID; } /** * 创建需求。 * Create story. * - * @param int $productID - * @param int $projectID - * @param int $executionID - * @param string $type - * @param object $data - * @param string $method - * @param array $reasonList + * @param int $productID + * @param int $projectID + * @param int $executionID + * @param string $type + * @param object $data + * @param array $reasonList * @access protected - * @return void + * @return bool */ - protected function createStory(int $productID, int $projectID, int $executionID, string $type, object $data, string $method, array $reasonList): void + protected function createStory(int $productID, int $projectID, int $executionID, string $type, object $data, array $relations): bool { $this->app->loadLang('story'); - $story = new stdclass(); - $story->product = $productID; + $story = new stdclass(); + $jiraFields = !empty($relations["zentaoField{$data->issuetype}"]) ? $relations["zentaoField{$data->issuetype}"] : array(); + foreach($jiraFields as $jiraField => $zentaoField) + { + if(!empty($data->{$jiraField})) $story->{$zentaoField} = $data->{$jiraField}; + } + $story->title = $data->SUMMARY; $story->type = $type; + $story->product = $productID; $story->pri = $data->PRIORITY; $story->version = 1; - $story->stage = $this->convertStage($data->issuestatus); - $story->status = $this->convertStatus('story', $data->issuestatus); - $story->openedBy = $this->getJiraAccount(isset($data->CREATOR) ? $data->CREATOR : '', $method); - $story->openedDate = isset($data->CREATED) ? substr($data->CREATED, 0, 19) : null; - $story->assignedTo = $this->getJiraAccount(isset($data->ASSIGNEE) ? $data->ASSIGNEE : '', $method); + $story->grade = 1; + $story->stage = $this->convertStage($data->issuestatus, $data->issuetype); + $story->status = $this->convertStatus('story', $data->issuestatus, $data->issuetype); + $story->openedBy = $this->getJiraAccount(isset($data->CREATOR) ? $data->CREATOR : ''); + $story->openedDate = !empty($data->CREATED) ? substr($data->CREATED, 0, 19) : null; + $story->assignedTo = $this->getJiraAccount(isset($data->ASSIGNEE) ? $data->ASSIGNEE : ''); if($data->RESOLUTION) { - $story->closedReason = zget($reasonList, $data->RESOLUTION, ''); + $story->closedReason = zget($relations["zentaoReason{$data->issuetype}"], $data->RESOLUTION, ''); if($story->closedReason && !isset($this->lang->story->reasonList[$story->closedReason])) $story->closedReason = 'done'; } @@ -898,17 +1342,21 @@ class convertTao extends convertModel $storyDesc->title = $story->title; $storyDesc->spec = isset($data->DESCRIPTION) ? $data->DESCRIPTION : ''; $this->dao->dbh($this->dbh)->replace(TABLE_STORYSPEC)->data($storyDesc)->exec(); + $this->dao->dbh($this->dbh)->replace(TABLE_PROJECTSTORY)->set('project')->eq($projectID) ->set('product')->eq($productID) ->set('story')->eq($storyID) ->set('version')->eq('1') ->exec(); - $this->dao->dbh($this->dbh)->replace(TABLE_PROJECTSTORY)->set('project')->eq($executionID) - ->set('product')->eq($productID) - ->set('story')->eq($storyID) - ->set('version')->eq('1') - ->exec(); + if($type == 'story') + { + $this->dao->dbh($this->dbh)->replace(TABLE_PROJECTSTORY)->set('project')->eq($executionID) + ->set('product')->eq($productID) + ->set('story')->eq($storyID) + ->set('version')->eq('1') + ->exec(); + } /* Create opened action from openedDate. */ $action = new stdclass(); @@ -919,50 +1367,52 @@ class convertTao extends convertModel $action->date = $story->openedDate; $this->dao->dbh($this->dbh)->insert(TABLE_ACTION)->data($action)->exec(); - /* Record relation. */ - $storyRelation['AType'] = 'jstory'; - $storyRelation['BType'] = 'zstory'; - $storyRelation['AID'] = $data->ID; - $storyRelation['BID'] = $storyID; - $this->dao->dbh($this->dbh)->insert(JIRA_TMPRELATION)->data($storyRelation)->exec(); - - $issueRelation['AType'] = 'jissueid'; - $issueRelation['BType'] = 'zissuetype'; - $issueRelation['AID'] = $data->ID; - $issueRelation['extra'] = 'story'; - $this->dao->dbh($this->dbh)->insert(JIRA_TMPRELATION)->data($issueRelation)->exec(); + $this->createTmpRelation("j$type", $data->ID, "z$type", $storyID, 'issue'); + $this->createTmpRelation('jissueid', $data->ID, 'zissuetype', '', $type); } + + return true; } /** * 创建任务。 * Create task. * - * @param int $projectID - * @param int $executionID - * @param object $data - * @param string $method - * @param array $reasonList + * @param int $projectID + * @param int $executionID + * @param object $data + * @param array $relations * @access protected - * @return void + * @return bool */ - protected function createTask(int $projectID, int $executionID, object $data, string $method, array $reasonList): void + protected function createTask(int $projectID, int $executionID, object $data, array $relations): bool { $this->app->loadLang('task'); - $task = new stdclass(); + $task = new stdclass(); + $jiraFields = !empty($relations["zentaoField{$data->issuetype}"]) ? $relations["zentaoField{$data->issuetype}"] : array(); + foreach($jiraFields as $jiraField => $zentaoField) + { + if(!empty($data->{$jiraField})) $task->{$zentaoField} = $data->{$jiraField}; + } + $task->project = $projectID; $task->execution = $executionID; $task->name = $data->SUMMARY; $task->type = 'devel'; + $task->estimate = !empty($data->TIMEORIGINALESTIMATE) ? round($data->TIMEORIGINALESTIMATE / 3600) : 0; + $task->left = !empty($data->TIMEESTIMATE) ? round($data->TIMEESTIMATE / 3600) : 0; + $task->consumed = !empty($data->TIMESPENT) ? round($data->TIMESPENT / 3600) : 0; $task->pri = $data->PRIORITY; - $task->status = $this->convertStatus('task', $data->issuestatus); + $task->status = $this->convertStatus('task', $data->issuestatus, $data->issuetype); $task->desc = isset($data->DESCRIPTION) ? $data->DESCRIPTION: ''; - $task->openedBy = $this->getJiraAccount(isset($data->CREATOR) ? $data->CREATOR : '', $method); - $task->openedDate = isset($data->CREATED) ? substr($data->CREATED, 0, 19) : null; - $task->assignedTo = $this->getJiraAccount(isset($data->ASSIGNEE) ? $data->ASSIGNEE : '', $method); + $task->openedBy = $this->getJiraAccount(isset($data->CREATOR) ? $data->CREATOR : ''); + $task->openedDate = !empty($data->CREATED) ? substr($data->CREATED, 0, 19) : null; + $task->assignedTo = $this->getJiraAccount(isset($data->ASSIGNEE) ? $data->ASSIGNEE : ''); + if($data->RESOLUTION) { + $reasonList = $relations["zentaoReason{$data->issuetype}"]; $task->closedReason = zget($reasonList, $data->RESOLUTION, ''); if($task->closedReason && !isset($this->lang->task->reasonList[$task->closedReason])) $task->closedReason = 'cancel'; } @@ -979,49 +1429,50 @@ class convertTao extends convertModel $action->date = $task->openedDate; $this->dao->dbh($this->dbh)->insert(TABLE_ACTION)->data($action)->exec(); - $taskRelation['AType'] = 'jtask'; - $taskRelation['BType'] = 'ztask'; - $taskRelation['AID'] = $data->ID; - $taskRelation['BID'] = $taskID; - $this->dao->dbh($this->dbh)->insert(JIRA_TMPRELATION)->data($taskRelation)->exec(); + $this->createTmpRelation('jtask', $data->ID, 'ztask', $taskID, 'issue'); + $this->createTmpRelation('jissueid', $data->ID, 'zissuetype', '', 'task'); - $issueRelation['AType'] = 'jissueid'; - $issueRelation['BType'] = 'zissuetype'; - $issueRelation['AID'] = $data->ID; - $issueRelation['extra'] = 'task'; - $this->dao->dbh($this->dbh)->insert(JIRA_TMPRELATION)->data($issueRelation)->exec(); + return true; } /** * 创建BUG。 * Create bug. * - * @param int $productID - * @param int $projectID - * @param object $data - * @param string $method - * @param array $resolutionList + * @param int $productID + * @param int $projectID + * @param int $executionID + * @param object $data + * @param array $relations * @access protected - * @return void + * @return bool */ - protected function createBug(int $productID, int $projectID, object $data, string $method, array $resolutionList): void + protected function createBug(int $productID, int $projectID, int $executionID, object $data, array $relations): bool { $this->app->loadLang('bug'); - $bug = new stdclass(); + $bug = new stdclass(); + $jiraFields = !empty($relations["zentaoField{$data->issuetype}"]) ? $relations["zentaoField{$data->issuetype}"] : array(); + foreach($jiraFields as $jiraField => $zentaoField) + { + if(!empty($data->{$jiraField})) $bug->{$zentaoField} = $data->{$jiraField}; + } + $bug->product = $productID; $bug->project = $projectID; + $bug->execution = $executionID; $bug->title = $data->SUMMARY; $bug->pri = $data->PRIORITY; - $bug->status = $this->convertStatus('bug', $data->issuestatus); + $bug->status = $this->convertStatus('bug', $data->issuestatus, $data->issuetype); $bug->steps = isset($data->DESCRIPTION) ? $data->DESCRIPTION : ''; - $bug->openedBy = $this->getJiraAccount(isset($data->CREATOR) ? $data->CREATOR : '', $method); - $bug->openedDate = isset($data->CREATED) ? substr($data->CREATED, 0, 19) : null; + $bug->openedBy = $this->getJiraAccount(isset($data->CREATOR) ? $data->CREATOR : ''); + $bug->openedDate = !empty($data->CREATED) ? substr($data->CREATED, 0, 19) : null; $bug->openedBuild = 'trunk'; - $bug->assignedTo = $bug->status == 'closed' ? 'closed' : $this->getJiraAccount(isset($data->ASSIGNEE) ? $data->ASSIGNEE : '', $method); + $bug->assignedTo = $bug->status == 'closed' ? 'closed' : $this->getJiraAccount(isset($data->ASSIGNEE) ? $data->ASSIGNEE : ''); if($data->RESOLUTION) { + $resolutionList = $relations["zentaoResolution{$data->issuetype}"]; $bug->resolution = zget($resolutionList, $data->RESOLUTION, ''); if($bug->resolution && !isset($this->lang->bug->resolutionList[$bug->resolution])) $bug->resolution = 'fixed'; } @@ -1038,42 +1489,190 @@ class convertTao extends convertModel $action->date = $bug->openedDate; $this->dao->dbh($this->dbh)->insert(TABLE_ACTION)->data($action)->exec(); - $bugRelation['AType'] = 'jbug'; - $bugRelation['BType'] = 'zbug'; - $bugRelation['AID'] = $data->ID; - $bugRelation['BID'] = $bugID; - $this->dao->dbh($this->dbh)->insert(JIRA_TMPRELATION)->data($bugRelation)->exec(); + $this->createTmpRelation('jbug', $data->ID, 'zbug', $bugID, 'issue'); + $this->createTmpRelation('jissueid', $data->ID, 'zissuetype', '', 'bug'); - $issueRelation['AType'] = 'jissueid'; - $issueRelation['BType'] = 'zissuetype'; - $issueRelation['AID'] = $data->ID; - $issueRelation['extra'] = 'bug'; - $this->dao->dbh($this->dbh)->insert(JIRA_TMPRELATION)->data($issueRelation)->exec(); + return true; + } + + /** + * 创建测试用例。 + * Create case. + * + * @param int $productID + * @param int $projectID + * @param int $executionID + * @param object $data + * @param array $relations + * @access protected + * @return bool + */ + protected function createCase(int $productID, int $projectID, int $executionID, object $data, array $relations): bool + { + $case = new stdclass(); + $jiraFields = !empty($relations["zentaoField{$data->issuetype}"]) ? $relations["zentaoField{$data->issuetype}"] : array(); + foreach($jiraFields as $jiraField => $zentaoField) + { + if(!empty($data->{$jiraField})) $case->{$zentaoField} = $data->{$jiraField}; + } + + $case->product = $productID; + $case->execution = $executionID; + $case->type = 'feature'; + $case->version = '1'; + $case->title = $data->SUMMARY; + $case->pri = $data->PRIORITY; + $case->status = $this->convertStatus('testcase', $data->issuestatus, $data->issuetype); + $case->openedBy = $this->getJiraAccount(isset($data->CREATOR) ? $data->CREATOR : ''); + $case->openedDate = !empty($data->CREATED) ? substr($data->CREATED, 0, 19) : null; + + $this->dao->dbh($this->dbh)->insert(TABLE_CASE)->data($case)->exec(); + $caseID = $this->dao->dbh($this->dbh)->lastInsertID(); + + /* Create opened action from openedDate. */ + $action = new stdclass(); + $action->objectType = 'testcase'; + $action->objectID = $caseID; + $action->actor = $case->openedBy; + $action->action = 'Opened'; + $action->date = $case->openedDate; + $this->dao->dbh($this->dbh)->insert(TABLE_ACTION)->data($action)->exec(); + + $spec = new stdclass(); + $spec->case = $caseID; + $spec->version = '1'; + $spec->title = $case->title; + $spec->precondition = zget($case, 'precondition', ''); + $spec->files = ''; + $this->dao->insert(TABLE_CASESPEC)->data($spec)->exec(); + + $this->loadModel('testcase')->syncCase2Project($case, $caseID); + + $this->createTmpRelation('jcase', $data->ID, 'ztestcase', $caseID, 'issue'); + $this->createTmpRelation('jissueid', $data->ID, 'zissuetype', '', 'case'); + + return true; + } + + /** + * 创建反馈。 + * Create feedback. + * + * @param int $productID + * @param object $data + * @param array $relations + * @access protected + * @return bool + */ + protected function createFeedback(int $productID, object $data, array $relations): bool + { + $feedback = new stdclass(); + $jiraFields = !empty($relations["zentaoField{$data->issuetype}"]) ? $relations["zentaoField{$data->issuetype}"] : array(); + foreach($jiraFields as $jiraField => $zentaoField) + { + if(!empty($data->{$jiraField})) $feedback->{$zentaoField} = $data->{$jiraField}; + } + + $feedback->product = $productID; + $feedback->title = $data->SUMMARY; + $feedback->public = '1'; + $feedback->pri = $data->PRIORITY; + $feedback->status = $this->convertStatus('feedback', $data->issuestatus, $data->issuetype); + $feedback->desc = isset($data->DESCRIPTION) ? $data->DESCRIPTION : ''; + $feedback->openedBy = $this->getJiraAccount(isset($data->CREATOR) ? $data->CREATOR : ''); + $feedback->openedDate = !empty($data->CREATED) ? substr($data->CREATED, 0, 19) : null; + $feedback->assignedTo = $feedback->status == 'closed' ? 'closed' : $this->getJiraAccount(isset($data->ASSIGNEE) ? $data->ASSIGNEE : ''); + + $this->dao->dbh($this->dbh)->insert(TABLE_FEEDBACK)->data($feedback)->exec(); + $feedbackID = $this->dao->dbh($this->dbh)->lastInsertID(); + + /* Create opened action from openedDate. */ + $action = new stdclass(); + $action->objectType = 'feedback'; + $action->objectID = $feedbackID; + $action->actor = $feedback->openedBy; + $action->action = 'Opened'; + $action->date = $feedback->openedDate; + $this->dao->dbh($this->dbh)->insert(TABLE_ACTION)->data($action)->exec(); + + $this->loadModel('feedback')->updateSubStatus($feedbackID, $feedback->status); + + $this->createTmpRelation('jfeedback', $data->ID, 'zfeedback', $feedbackID, 'issue'); + $this->createTmpRelation('jissueid', $data->ID, 'zissuetype', '', 'feedback'); + + return true; + } + + /** + * 创建工单。 + * Create ticket. + * + * @param int $productID + * @param object $data + * @param array $relations + * @access protected + * @return bool + */ + protected function createTicket(int $productID, object $data, array $relations): bool + { + $ticket = new stdclass(); + $jiraFields = !empty($relations["zentaoField{$data->issuetype}"]) ? $relations["zentaoField{$data->issuetype}"] : array(); + foreach($jiraFields as $jiraField => $zentaoField) + { + if(!empty($data->{$jiraField})) $ticket->{$zentaoField} = $data->{$jiraField}; + } + + $ticket->product = $productID; + $ticket->title = $data->SUMMARY; + $ticket->type = 'code'; + $ticket->pri = $data->PRIORITY; + $ticket->status = $this->convertStatus('ticket', $data->issuestatus, $data->issuetype); + $ticket->desc = isset($data->DESCRIPTION) ? $data->DESCRIPTION : ''; + $ticket->openedBy = $this->getJiraAccount(isset($data->CREATOR) ? $data->CREATOR : ''); + $ticket->openedDate = !empty($data->CREATED) ? substr($data->CREATED, 0, 19) : null; + $ticket->assignedTo = $ticket->status == 'closed' ? 'closed' : $this->getJiraAccount(isset($data->ASSIGNEE) ? $data->ASSIGNEE : ''); + $ticket->openedBuild = 'trunk'; + + $this->dao->dbh($this->dbh)->insert(TABLE_TICKET)->data($ticket)->exec(); + $ticketID = $this->dao->dbh($this->dbh)->lastInsertID(); + + /* Create opened action from openedDate. */ + $action = new stdclass(); + $action->objectType = 'ticket'; + $action->objectID = $ticketID; + $action->actor = $ticket->openedBy; + $action->action = 'Opened'; + $action->date = $ticket->openedDate; + $this->dao->dbh($this->dbh)->insert(TABLE_ACTION)->data($action)->exec(); + + $this->createTmpRelation('jticket', $data->ID, 'zticket', $ticketID, 'issue'); + $this->createTmpRelation('jissueid', $data->ID, 'zissuetype', '', 'ticket'); + + return true; } /** * 创建版本。 * Create build. * - * @param int $productID - * @param int $projectID - * @param object $data - * @param array $versionGroup - * @param string $method - * @param array $issueObjectType - * @param array $issueBugs - * @param array $issueStories + * @param int $productID + * @param int $projectID + * @param int $systemID + * @param object $data + * @param array $versionGroup + * @param array $issueList * @access protected * @return object */ - protected function createBuild(int $productID, int $projectID, object $data, array $versionGroup, string $method, array $issueObjectType, array $issueBugs, array $issueStories): object + protected function createBuild(int $productID, int $projectID, int $systemID, object $data, array $versionGroup, array $issueList): object { /* Create build. */ $build = new stdclass(); $build->product = $productID; $build->project = $projectID; + $build->system = $systemID; $build->name = $data->vname; - $build->date = isset($data->RELEASEDATE) ? substr($data->RELEASEDATE, 0, 10) : ''; + $build->date = !empty($data->RELEASEDATE) ? substr($data->RELEASEDATE, 0, 10) : null; $build->builder = $this->app->user->account; $build->createdBy = $this->app->user->account; $build->createdDate = helper::now(); @@ -1087,18 +1686,17 @@ class convertTao extends convertModel { foreach($versionGroup[$versionID] as $issue) { - $issueID = $method == 'db' ? $issue->issueID : $issue; - $issueType = zget($issueObjectType, $issueID, ''); - if(!$issueType || ($issueType != 'story' && $issueType != 'bug')) continue; + $issueID = $this->session->jiraMethod == 'db' ? $issue->issueID : $issue; + $objectID = zget($issueList[$issueID], 'BID', ''); + $issueType = zget($issueList[$issueID], 'BType', ''); + if(!$issueType || ($issueType != 'zstory' && $issueType != 'zbug')) continue; - if($issueType == 'story') + if($issueType == 'zstory') { - $objectID = zget($issueStories, $issueID); $this->dao->dbh($this->dbh)->update(TABLE_BUILD)->set("stories = CONCAT(stories, ',$objectID')")->where('id')->eq($buildID)->exec(); } - else + if($issueType == 'zbug') { - $objectID = zget($issueBugs, $issueID); $this->dao->dbh($this->dbh)->update(TABLE_BUILD)->set("bugs = CONCAT(bugs, ',$objectID')")->where('id')->eq($buildID)->exec(); if(!isset($issue->relation)) continue; @@ -1122,28 +1720,28 @@ class convertTao extends convertModel * 创建发布。 * Create release. * - * @param object $build - * @param object $data - * @param array $versionGroup - * @param string $method - * @param array $issueObjectType - * @param array $issueBugs - * @param array $issueStories + * @param object $build + * @param object $data + * @param array $versionGroup + * @param array $issueList * @access protected - * @return void + * @return bool */ - protected function createRelease(object $build, object $data, array $versionGroup, string $method, array $issueObjectType, array $issueBugs, array $issueStories): void + protected function createRelease(object $build, object $data, array $versionGroup, array $issueList): bool { /* Create release. */ $release = new stdclass(); - $release->product = $build->product; - $release->build = $build->id; - $release->name = $build->name; - $release->date = $build->date; - $release->desc = isset($data->DESCRIPTION) ? $data->DESCRIPTION : ''; - $release->status = 'normal'; - $release->createdBy = $this->app->user->account; - $release->createdDate = helper::now(); + $release->product = $build->product; + $release->project = $build->project; + $release->system = $build->system; + $release->build = $build->id; + $release->name = $build->name; + $release->date = $build->date; + $release->desc = isset($data->DESCRIPTION) ? $data->DESCRIPTION : ''; + $release->status = empty($data->RELEASED) ? 'wait' : 'normal'; + $release->releasedDate = !empty($data->RELEASED) ? $release->date : NULL; + $release->createdBy = $this->app->user->account; + $release->createdDate = helper::now(); $this->dao->dbh($this->dbh)->insert(TABLE_RELEASE)->data($release)->exec(); $releaseID = $this->dao->dbh($this->dbh)->lastInsertID(); @@ -1154,154 +1752,757 @@ class convertTao extends convertModel { foreach($versionGroup[$versionID] as $issue) { - $issueID = $method == 'db' ? $issue->issueID : $issue; - $issueType = zget($issueObjectType, $issueID, ''); - if(!$issueType || ($issueType != 'story' && $issueType != 'bug')) continue; + $issueID = $this->session->jiraMethod == 'db' ? $issue->issueID : $issue; + $objectID = zget($issueList[$issueID], 'BID', ''); + $issueType = zget($issueList[$issueID], 'BType', ''); + if(!$issueType || ($issueType != 'zstory' && $issueType != 'zbug')) continue; - if($issueType == 'story') + if($issueType == 'zstory') { - $objectID = zget($issueStories, $issueID); $this->dao->dbh($this->dbh)->update(TABLE_RELEASE)->set("stories = CONCAT(stories, ',$objectID')")->where('id')->eq($releaseID)->exec(); } else { - $objectID = zget($issueBugs, $issueID); $this->dao->dbh($this->dbh)->update(TABLE_RELEASE)->set("bugs = CONCAT(bugs, ',$objectID')")->where('id')->eq($releaseID)->exec(); } } } + + return true; + } + + /** + * Jira内置字段新增成工作流字段。 + * Create buildin field. + * + * @param object $flow + * @param array $resolutions + * @param array $priList + * @access protected + * @return bool + */ + protected function createBuildinField(object $flow, array $resolutions, array $priList): bool + { + foreach($this->lang->convert->jira->buildinFields as $fieldCode => $buildinField) + { + $options = array('code' => array(), 'name' => array()); + if($fieldCode == 'pri') + { + foreach($priList as $pri) + { + $options['code'][] = $pri->ID; + $options['name'][] = $pri->pname; + } + } + if($fieldCode == 'resolution') + { + foreach($resolutions as $resolution) + { + $options['code'][] = $resolution->ID; + $options['name'][] = $resolution->pname; + } + } + + $field = new stdclass(); + $field->name = $buildinField['name']; + $field->field = $fieldCode; + $field->control = $buildinField['control']; + $field->type = $buildinField['type']; + $field->length = $buildinField['length']; + $field->integerDigits = in_array($field->type, $this->config->workflowfield->numberTypes) ? '10' : ''; + $field->decimalDigits = in_array($field->type, $this->config->workflowfield->numberTypes) ? '2' : '';; + $field->expression = ''; + $field->optionType = 'custom'; + $field->sql = ''; + $field->options = $options; + $field->default = ''; + $field->placeholder = ''; + $field->module = $flow->module; + $field->group = '0'; + $field->createdBy = $this->app->user->account; + $field->createdDate = helper::now(); + + $this->workflowfield->create($flow->module, $field, null, true); + } + + return true; + } + + /** + * 为工作流生成默认布局。 + * Create default layout. + * + * @param array $fields + * @param object $flow + * @param int $group + * @access protected + * @return bool + */ + protected function createDefaultLayout(array $fields, object $flow, int $group = 0): bool + { + foreach(array('browse', 'create', 'edit', 'view') as $action) + { + foreach($fields as $field) + { + if(($action == 'create' || $action == 'edit') && in_array($field->field, array('id', 'parent', 'createdBy', 'createdDate', 'editedBy', 'editedDate', 'assignedBy', 'assignedDate', 'deleted'))) continue; + + $layout = new stdclass(); + $layout->module = $flow->module; + $layout->action = $action; + $layout->field = $field->field; + $layout->vision = $this->config->vision; + $layout->group = $group; + if($action == 'view') $layout->position = 'info'; + $this->dao->insert(TABLE_WORKFLOWLAYOUT)->data($layout)->autoCheck()->exec(); + } + } + return true; + } + + /** + * Jira对象新增成工作流。 + * Create workflow. + * + * @param array $relations + * @param array $jiraActions + * @access protected + * @return array + */ + protected function createWorkflow(array $relations, array $jiraActions): array + { + if($this->config->edition == 'open') return $relations; + $this->loadModel('workflow'); + $this->loadModel('workflowfield'); + + $jiraResolutions = $this->getJiraData($this->session->jiraMethod, 'resolution'); + $jiraPriList = $this->getJiraData($this->session->jiraMethod, 'priority'); + $issueTypeList = $this->getJiraData($this->session->jiraMethod, 'issuetype'); + $statusList = $this->getJiraData($this->session->jiraMethod, 'status'); + $flowRelation = $this->dao->dbh($this->dbh)->select('*')->from(JIRA_TMPRELATION)->where('AType')->eq('jissuetype')->andWhere('BType')->eq('zworkflow')->fetchAll('AID'); + foreach($relations['zentaoObject'] as $jiraCode => $zentaoCode) + { + if($zentaoCode != 'add_custom') continue; + + /* Jira事务类型新增成禅道工作流。 */ + if(empty($flowRelation[$jiraCode])) + { + $flow = new stdclass(); + $flow->name = substr(zget($issueTypeList[$jiraCode], 'pname', ''), 0, 30); + $flow->icon = 'flow'; + $flow->module = 'jira' . $jiraCode; + $flow->approval = 'disabled'; + $flow->type = 'flow'; + $flow->table = 'zt_flow_' . $flow->module; + $flow->createdBy = $this->app->user->account; + $flow->createdDate = helper::now(); + $flow->vision = $this->config->vision; + $flow->role = 'custom'; + $flow->status = 'wait'; + $flow->belong = 'project'; + $this->dao->insert(TABLE_WORKFLOW)->data($flow)->autoCheck()->exec(); + + $flowID = $this->dao->lastInsertId(); + $this->loadModel('action')->create('workflow', $flowID, 'Created'); + + $flow = $this->workflow->getByID($flowID); + $this->workflow->createFields($flow); + $this->workflow->createActions($flow); + $this->workflow->createLabels($flow); + + $this->createBuildinField($flow, $jiraResolutions, $jiraPriList); + + $fields = $this->workflowfield->getList($flow->module); + $this->createDefaultLayout($fields, $flow); + + /* 将工作流发布到指定位置。 */ + $_POST['navigator'] = 'secondary'; + $_POST['app'] = 'scrum'; + $_POST['positionModule'] = 'settings'; + $_POST['position'] = 'position'; + $_POST['syncRelease'] = 'all'; + $_POST['module'] = $flow->module; + $this->workflow->release($flowID); + + $this->createTmpRelation('jissuetype', $jiraCode, 'zworkflow', $flow->module); + $relations['zentaoObject'][$jiraCode] = $flow->module; + } + else + { + $relations['zentaoObject'][$jiraCode] = $flowRelation[$jiraCode]->BID; + } + + $customField = $this->getJiraCustomField($jiraCode, $relations); + foreach($customField as $id => $field) + { + $relations["jiraField{$jiraCode}"][] = $id; + $relations["zentaoField{$jiraCode}"][$id] = 'add_field'; + } + foreach($statusList as $id => $status) + { + $relations["jiraStatus{$jiraCode}"][] = $id; + $relations["zentaoStatus{$jiraCode}"][$id] = 'add_flow_status'; + } + foreach($jiraActions['actions'] as $id => $action) + { + if(!empty($action['name']) && $action['name'] == 'Create') continue; + $relations["jiraAction{$jiraCode}"][] = $id; + $relations["zentaoAction{$jiraCode}"][$id] = 'add_action'; + } + } + + return $relations; + } + + /** + * Jira字段新增成工作流字段。 + * Create workflow field. + * + * @param array $relations + * @param array $fields + * @param array $fieldOptions + * @access protected + * @return array + */ + protected function createWorkflowField(array $relations, array $fields, array $fieldOptions): array + { + if($this->config->edition == 'open') return $relations; + + $this->loadModel('workflowfield'); + $jiraFieldControl = $this->config->convert->jiraFieldControl; + $fieldRelation = $this->dao->dbh($this->dbh)->select('*')->from(JIRA_TMPRELATION)->where('AType')->eq('jcustomfield')->fetchGroup('extra', 'AID'); + foreach($relations as $stepKey => $fieldList) + { + if(strpos($stepKey, 'zentaoField') === false) continue; + $jiraCode = str_replace('zentaoField', '', $stepKey); + $module = $relations['zentaoObject'][$jiraCode]; + foreach($fieldList as $jiraField => $zentaoField) + { + if($zentaoField != 'add_field') continue; + + if(empty($fieldRelation[$module][$jiraField])) + { + $controlCode = !empty($fields[$jiraField]->CUSTOMFIELDTYPEKEY) ? $fields[$jiraField]->CUSTOMFIELDTYPEKEY : 'com.atlassian.jira.plugin.system.customfieldtypes:textfield'; + + $options = array('code' => array(), 'name' => array()); + foreach($fieldOptions as $optionID => $fieldOption) + { + if($fieldOption->CUSTOMFIELD != $jiraField) continue; + $options['code'][] = $optionID; + $options['name'][] = $fieldOption->customvalue; + } + + if(empty($jiraFieldControl[$controlCode])) $controlCode = !empty($options['code']) ? 'com.atlassian.jira.plugin.system.customfieldtypes:select' : 'com.atlassian.jira.plugin.system.customfieldtypes:textfield'; + + $field = new stdclass(); + $field->name = substr(zget($fields[$jiraField], 'cfname', ''), 0, 60); + $field->field = 'jirafield' . str_replace(range(0, 9), range('a', 'z'), uniqid()); + $field->control = $jiraFieldControl[$controlCode]['control']; + $field->type = $jiraFieldControl[$controlCode]['type']; + $field->length = $jiraFieldControl[$controlCode]['length']; + $field->integerDigits = in_array($field->type, $this->config->workflowfield->numberTypes) ? '10' : ''; + $field->decimalDigits = in_array($field->type, $this->config->workflowfield->numberTypes) ? '2' : '';; + $field->expression = ''; + $field->optionType = strpos($controlCode, 'userpicker') === false ? 'custom' : 'user'; + $field->sql = ''; + $field->options = $options; + $field->default = ''; + $field->placeholder = ''; + $field->module = $module; + $field->group = '0'; + $field->createdBy = $this->app->user->account; + $field->createdDate = helper::now(); + + if(strpos(',select,multi-select,radio,checkbox,', ",$field->control,") !== false && empty($options['code'])) $field->control = 'text'; + $result = $this->workflowfield->create($module, $field, null, true); + + $relation = $this->createTmpRelation('jcustomfield', $jiraField, 'zworkflowfield', $field->field, $field->module); + + $fieldRelation[$module][$jiraField] = $relation; + $relations[$stepKey][$jiraField] = $field->field; + } + else + { + $relations[$stepKey][$jiraField] = $fieldRelation[$module][$jiraField]->BID; + } + + } + } + + return $relations; + } + + /** + * Jira状态新增成工作流状态。 + * Create workflow status. + * + * @param array $relations + * @access protected + * @return array + */ + protected function createWorkflowStatus(array $relations): array + { + if($this->config->edition == 'open') return $relations; + + $this->loadModel('custom'); + $currentLang = $this->app->getClientLang(); + $jiraStatusList = $this->getJiraData($this->session->jiraMethod, 'status'); + foreach($relations as $stepKey => $statusList) + { + if(strpos($stepKey, 'zentaoStatus') === false) continue; + $jiraCode = str_replace('zentaoStatus', '', $stepKey); + $module = $relations['zentaoObject'][$jiraCode]; + foreach($statusList as $jiraStatus => $zentaoStatus) + { + if($zentaoStatus != 'add_case_status' && $zentaoStatus != 'add_flow_status') continue; + + $zentaoCode = $jiraStatus; + if($zentaoStatus == 'add_case_status') + { + $items = $this->loadModel('setting')->getItem("module=testcase§ion=statusList"); + if(empty($items)) + { + $items = $this->lang->bug->statusList; + foreach($items as $key => $value) $this->custom->setItem("{$currentLang}.testcase.statusList.$zentaoCode.1", $value); + } + $this->custom->setItem("{$currentLang}.testcase.statusList.$zentaoCode.0", zget($jiraStatusList[$jiraStatus], 'pname', '')); + } + elseif($zentaoStatus == 'add_flow_status') + { + $fieldOptions = $this->dao->select('options')->from(TABLE_WORKFLOWFIELD)->where('module')->eq($module)->andWhere('field')->eq('status')->fetch('options'); + $fieldOptions = json_decode($fieldOptions); + if(!$fieldOptions) $fieldOptions = new stdclass(); + $fieldOptions->{$zentaoCode} = zget($jiraStatusList[$jiraStatus], 'pname', ''); + $this->dao->update(TABLE_WORKFLOWFIELD)->set('options')->eq(json_encode($fieldOptions))->where('module')->eq($module)->andWhere('field')->eq('status')->exec(); + } + $relations[$stepKey][$jiraStatus] = $zentaoCode; + } + } + return $relations; + } + + /** + * 导入jira状态变更为工作流扩展动作。 + * Process workflow hooks. + * + * @param array $jiraAction + * @param array $jiraStepList + * @param string $module + * @access protected + * @return array + */ + protected function processWorkflowHooks(array $jiraAction, array $jiraStepList, string $module): array + { + if(empty($jiraAction['results']['unconditional-result']['@attributes']['step'])) return array(); + + $hooks = array(); + $jiraStep = $jiraAction['results']['unconditional-result']['@attributes']['step']; + + $field = new stdclass(); + $field->field = 'status'; + $field->paramType = 'custom'; + $field->param = $jiraStepList[$jiraStep]; + + $where = new stdclass(); + $where->field = 'id'; + $where->logicalOperator = 'and'; + $where->operator = 'equal'; + $where->paramType = 'record'; + $where->param = 'id'; + + $hook = new stdclass(); + $hook->action = 'update'; + $hook->table = $module; + $hook->conditionType = 'data'; + $hook->sqlResult = 'empty'; + $hook->message = ''; + $hook->comment = ''; + $hook->fields = array($field); + $hook->conditions = array(); + $hook->wheres = array($where); + $hook->sqlVars = array(); + $hook->formVars = array(); + $hook->recordVars = array($where->param); + $hook->formulaVars = array(); + + list($sql, $error) = $this->workflowhook->check($hook); + $hook->sql = $sql; + + $hooks[] = $hook; + return $hooks; + } + + /** + * Jira动作新增成工作流动作。 + * Create workflow action. + * + * @param array $relations + * @param array $jiraActions + * @access protected + * @return array + */ + protected function createWorkflowAction(array $relations, array $jiraActions): array + { + if($this->config->edition == 'open') return $relations; + + $this->loadModel('workflowaction'); + $this->loadModel('workflowhook'); + $currentLang = $this->app->getClientLang(); + $flowActionRelation = $this->dao->dbh($this->dbh)->select('*')->from(JIRA_TMPRELATION)->where('AType')->eq('jflowaction')->fetchGroup('extra', 'AID'); + foreach($relations as $stepKey => $actionList) + { + if(strpos($stepKey, 'zentaoAction') === false) continue; + $jiraCode = str_replace('zentaoAction', '', $stepKey); + $module = $relations['zentaoObject'][$jiraCode]; + foreach($actionList as $jiraAction => $zentaoAction) + { + if($zentaoAction != 'add_action') continue; + + if(empty($flowActionRelation[$module][$jiraAction])) + { + $hooks = $this->processWorkflowHooks($jiraActions['actions'][$jiraAction], $jiraActions['steps'], $module); + + $action = new stdclass(); + $action->name = $jiraActions['actions'][$jiraAction]['name']; + $action->action = $module . 'action' . $jiraAction; + $action->type = 'single'; + $action->batchMode = 'same'; + $action->open = 'none'; + $action->position = 'browseandview'; + $action->show = 'dropdownlist'; + $action->desc = ''; + $action->module = $module; + $action->method = 'operate'; + $action->extensionType = 'override'; + $action->conditions = '[]'; + $action->hooks = helper::jsonEncode($hooks); + $action->status = 'disable'; + $action->createdBy = $this->app->user->account; + $action->createdDate = helper::now(); + $action->vision = $this->config->vision; + $action->group = '0'; + $action->order = '999'; + + $this->dao->insert(TABLE_WORKFLOWACTION)->data($action)->exec(); + + $actionID = $this->dao->lastInsertId(); + $action->id = $actionID; + $this->workflowaction->createFields($action); + + $relation = $this->createTmpRelation('jflowaction', $jiraAction, 'zworkflowaction', $action->action, $module); + + $flowActionRelation[$module][$jiraAction] = $relation; + $relations[$stepKey][$jiraAction] = $action->action; + + } + else + { + $relations[$stepKey][$jiraAction] = $flowActionRelation[$module][$jiraAction]->BID; + } + } + } + return $relations; + } + + /** + * 为项目创建工作流分组. + * Create group. + * + * @param string $type + * @param string $name + * @param array $objectList + * @param int $jiraProjectID + * @param int $zentaoProjectID + * @param array $productRelations + * @param array $projectFieldList + * @access protected + * @return bool + */ + protected function createGroup(string $type, string $name, array $objectList, int $jiraProjectID, int $zentaoProjectID, array $productRelations, array $projectFieldList): bool + { + $group = new stdclass(); + $group->name = substr($name, 0, 80) . $this->lang->workflowgroup->template; + $group->projectModel = $type == 'project' ? 'scrum' : ''; + $group->projectType = $type == 'project' ? 'product' : ''; + $group->type = $type; + $group->status = 'normal'; + $group->createdBy = $this->app->user->account; + $group->createdDate = helper::now(); + $group->disabledModules = ''; + $group->exclusive = '1'; + $groupID = $this->workflowgroup->create($group); + $this->action->create('workflowgroup', $groupID, 'created'); + + $group->id = $groupID; + $flows = $this->workflowgroup->getFlows($group); + $actionList = array('browse', 'create', 'edit', 'view'); + foreach($flows as $flow) + { + if(in_array($flow->module, $objectList)) + { + $this->workflowgroup->setExclusive($flow->id, $groupID); + + $this->dao->dbh($this->dbh)->update(TABLE_WORKFLOWACTION)->set('extensionType')->eq('extend')->where('module')->eq($flow->module)->andWhere('`group`')->eq($groupID)->andWhere('action')->in($actionList)->exec(); + + if(empty($projectFieldList[$jiraProjectID][$flow->module])) continue; + $this->createDefaultLayout($projectFieldList[$jiraProjectID][$flow->module], $flow, $groupID); + } + } + if($type == 'product') $this->dao->dbh($this->dbh)->update(TABLE_PRODUCT)->set('workflowGroup')->eq($groupID)->where('id')->eq($productRelations[$jiraProjectID])->exec(); + if($type == 'project') $this->dao->dbh($this->dbh)->update(TABLE_PROJECT)->set('workflowGroup')->eq($groupID)->where('id')->eq($zentaoProjectID)->exec(); + $this->createTmpRelation('jproject', $jiraProjectID, 'zworkflowgroup', $groupID); + + return true; + } + + /** + * 创建工作部流程模版。 + * Create workflow group. + * + * @param array $relations + * @param array $projectRelations + * @param array $productRelations + * @access protected + * @return array + */ + protected function createWorkflowGroup(array $relations, array $projectRelations, array $productRelations): array + { + if($this->config->edition == 'open') return $relations; + + $this->loadModel('action'); + $this->loadModel('workflowgroup'); + $groupRelations = $this->dao->dbh($this->dbh)->select('AID,BID')->from(JIRA_TMPRELATION)->where('AType')->eq('jproject')->andWhere('BType')->eq('zworkflowgroup')->fetchPairs(); + $projectList = $this->getJiraData($this->session->jiraMethod, 'project'); + $projectIssueTypeList = $this->getIssueTypeList($relations); + $projectFieldList = $this->getJiraFieldGroupByProject($relations); + foreach($projectRelations as $jiraProjectID => $zentaoProjectID) + { + if(!empty($groupRelations[$jiraProjectID])) continue; + if(empty($projectList[$jiraProjectID])) continue; + + $project = $projectList[$jiraProjectID]; + $issueTypeList = !empty($projectIssueTypeList[$jiraProjectID]) ? $projectIssueTypeList[$jiraProjectID] : array(); + + $this->createGroup('project', $project->pname, $issueTypeList, (int)$jiraProjectID, (int)$zentaoProjectID, $productRelations, $projectFieldList); + $this->createGroup('product', $project->pname, $issueTypeList, (int)$jiraProjectID, (int)$zentaoProjectID, $productRelations, $projectFieldList); + } + + return $relations; + } + + /** + * Jira解决方案新增成禅道关闭原因或解决方案。 + * Create zentao resolution. + * + * @param array $relations + * @access protected + * @return array + */ + protected function createResolution(array $relations): array + { + $this->loadModel('custom'); + $currentLang = $this->app->getClientLang(); + $jiraResolutions = $this->getJiraData($this->session->jiraMethod, 'resolution'); + foreach($relations as $stepKey => $resolutionList) + { + if(strpos($stepKey, 'zentaoResolution') === false && strpos($stepKey, 'zentaoReason') === false) continue; + $jiraCode = strpos($stepKey, 'zentaoResolution') !== false ? str_replace('zentaoResolution', '', $stepKey) : str_replace('zentaoReason', '', $stepKey); + $module = $relations['zentaoObject'][$jiraCode]; + foreach($resolutionList as $jiraResolution => $zentaoResolution) + { + if($zentaoResolution != 'add_resolution' && $zentaoResolution != 'add_reason') continue; + + $zentaoCode = 'jira' . $jiraResolution; + $section = $zentaoResolution == 'add_resolution' ? 'resolutionList' : 'reasonList'; + + $items = $this->loadModel('setting')->getItem("module={$module}§ion={$section}"); + if(empty($items)) + { + $items = $this->lang->story->reasonList; + if($module == 'bug') $items = $this->lang->bug->resolutionList; + if($module == 'task') $items = $this->lang->task->reasonList; + foreach($items as $key => $value) $this->custom->setItem("{$currentLang}.{$module}.{$section}.{$key}.1", $value); + } + $this->custom->setItem("{$currentLang}.{$module}.{$section}.{$zentaoCode}.0", zget($jiraResolutions[$jiraResolution], 'pname', '')); + + $relations[$stepKey][$jiraResolution] = $zentaoCode; + } + } + return $relations; } /** * 更新子需求。 * Update sub story. * - * @param array $storyLink - * @param array $issueStories + * @param array $storyLink + * @param array $issueList * @access protected - * @return void + * @return bool */ - protected function updateSubStory(array $storyLink, array $issueStories): void + protected function updateSubStory(array $storyLink, array $issueList): bool { foreach($storyLink as $source => $dest) { - if(!isset($issueStories[$source])) continue; + if(!isset($issueList[$source])) continue; - $parentID = $issueStories[$source]; - $this->dao->dbh($this->dbh)->update(TABLE_STORY)->set('parent')->eq('-1')->where('id')->eq($parentID)->exec(); + $parentType = zget($issueList[$source], 'BType', ''); + if(!in_array($parentType, array('zepic', 'zrequirement', 'zstory'))) continue; + + $parentID = zget($issueList[$source], 'BID', ''); + $this->dao->dbh($this->dbh)->update(TABLE_STORY) + ->set('parent')->eq('0') + ->set('isParent')->eq('1') + ->set('grade')->eq('1') + ->set('root')->eq($parentID) + ->set('path')->eq(",{$parentID},") + ->where('id')->eq($parentID) + ->exec(); foreach($dest as $childID) { - if(!isset($issueStories[$childID])) continue; - $this->dao->dbh($this->dbh)->update(TABLE_STORY)->set('parent')->eq($parentID)->where('id')->eq($issueStories[$childID])->exec(); + if(!isset($issueList[$childID])) continue; + + $childType = zget($issueList[$childID], 'BType', ''); + if(!in_array($childType, array('zepic', 'zrequirement', 'zstory'))) continue; + + $childrenID = zget($issueList[$childID], 'BID', ''); + $this->dao->dbh($this->dbh)->update(TABLE_STORY) + ->set('parent')->eq($parentID) + ->set('isParent')->eq('0') + ->set('grade')->eq('2') + ->set('root')->eq($parentID) + ->set('path')->eq(",{$parentID},{$childrenID},") + ->where('id')->eq($childrenID) + ->exec(); } } + + return true; } /** * 更新子任务。 * Update sub task. * - * @param array $taskLink - * @param array $issueTasks + * @param array $taskLink + * @param array $issueList * @access protected - * @return void + * @return bool */ - protected function updateSubTask(array $taskLink, array $issueTasks): void + protected function updateSubTask(array $taskLink, array $issueList): bool { foreach($taskLink as $source => $dest) { - if(!isset($issueTasks[$source])) continue; + if(!isset($issueList[$source])) continue; + if(zget($issueList[$source], 'BType', '') != 'ztask') continue; - $parentID = $issueTasks[$source]; - $this->dao->dbh($this->dbh)->update(TABLE_TASK)->set('parent')->eq('-1')->where('id')->eq($parentID)->exec(); + $parentID = zget($issueList[$source], 'BID', ''); + $this->dao->dbh($this->dbh)->update(TABLE_TASK) + ->set('parent')->eq('0') + ->set('isParent')->eq('1') + ->set('path')->eq(",{$parentID},") + ->where('id')->eq($parentID) + ->exec(); foreach($dest as $childID) { - if(!isset($issueTasks[$childID])) continue; - $this->dao->dbh($this->dbh)->update(TABLE_TASK)->set('parent')->eq($parentID)->where('id')->eq($issueTasks[$childID])->exec(); + if(!isset($issueList[$childID])) continue; + if(zget($issueList[$childID], 'BType', '') != 'ztask') continue; + + $childrenID = zget($issueList[$childID], 'BID', ''); + $this->dao->dbh($this->dbh)->update(TABLE_TASK) + ->set('parent')->eq($parentID) + ->set('isParent')->eq('0') + ->set('path')->eq(",{$parentID},{$childrenID},") + ->where('id')->eq($childrenID) + ->exec(); } } + + return true; } /** * 更新重复的需求和bug。 * Update duplicate story and bug. * - * @param array $duplicateLink - * @param array $issueObjectType - * @param array $issueStories - * @param array $issueBugs + * @param array $duplicateLink + * @param array $issueList * @access protected - * @return void + * @return bool */ - protected function updateDuplicateStoryAndBug(array $duplicateLink, array $issueObjectType, array $issueStories, array $issueBugs): void + protected function updateDuplicateStoryAndBug(array $duplicateLink, array $issueList): bool { foreach($duplicateLink as $source => $dest) { - $objectType = $issueObjectType[$source]; + $sourceObjectType = zget($issueList[$source], 'BType', ''); + $sourceObjectID = zget($issueList[$source], 'BID', ''); + $destObjectType = zget($issueList[$dest], 'BType', ''); + $destObjectID = zget($issueList[$dest], 'BID', ''); - if($objectType != 'story' && $objectType != 'bug') continue; - if($issueObjectType[$source] != $issueObjectType[$dest]) continue; + if(empty($sourceObjectType) || empty($destObjectType)) continue; + if($sourceObjectType != 'zstory' && $sourceObjectType != 'zbug') continue; + if($sourceObjectType != $destObjectType) continue; - if($objectType == 'story') + if($sourceObjectType == 'zstory') { - if(empty($issueStories[$dest]) || empty($issueStories[$source])) continue; - $this->dao->dbh($this->dbh)->update(TABLE_STORY)->set('duplicateStory')->eq($issueStories[$dest])->where('id')->eq($issueStories[$source])->exec(); + $this->dao->dbh($this->dbh)->update(TABLE_STORY)->set('duplicateStory')->eq($destObjectID)->where('id')->eq($sourceObjectID)->exec(); } - elseif($objectType == 'bug') + else { - if(empty($issueBugs[$dest]) || empty($issueBugs[$source])) continue; - $this->dao->dbh($this->dbh)->update(TABLE_BUG)->set('duplicateBug')->eq($issueBugs[$dest])->where('id')->eq($issueBugs[$source])->exec(); + $this->dao->dbh($this->dbh)->update(TABLE_BUG)->set('duplicateBug')->eq($destObjectID)->where('id')->eq($sourceObjectID)->exec(); } } + + return true; } /** * 更新相关对象数据。 * Update relates object. * - * @param array $relatesLink - * @param array $issueObjectType - * @param array $issueStories - * @param array $issueTasks - * @param array $issueBugs + * @param array $relatesLink + * @param array $issueList * @access protected - * @return void + * @return bool */ - protected function updateRelatesObject(array $relatesLink, array $issueObjectType, array $issueStories, array $issueTasks, array $issueBugs): void + protected function updateRelatesObject(array $relatesLink, array $issueList): bool { foreach($relatesLink as $source => $dest) { - if(empty($issueObjectType[$source]) || empty($issueObjectType[$dest])) continue; + $sourceObjectType = zget($issueList[$source], 'BType', ''); + $sourceObjectID = zget($issueList[$source], 'BID', ''); + $destObjectType = zget($issueList[$dest], 'BType', ''); + $destObjectID = zget($issueList[$dest], 'BID', ''); - $sourceObjectType = $issueObjectType[$source]; - $destObjectType = $issueObjectType[$dest]; + if(empty($sourceObjectType) || empty($destObjectType)) continue; - if($sourceObjectType == 'task' && $destObjectType == 'story') + if($sourceObjectType == 'ztask' && $destObjectType == 'zstory') { - $this->dao->dbh($this->dbh)->update(TABLE_TASK)->set('story')->eq($issueStories[$dest])->where('id')->eq($issueTasks[$source])->exec(); + $this->dao->dbh($this->dbh)->update(TABLE_TASK)->set('story')->eq($destObjectID)->where('id')->eq($sourceObjectID)->exec(); } - elseif($sourceObjectType == 'story' && $destObjectType == 'task') + elseif($sourceObjectType == 'zstory' && $destObjectType == 'ztask') { - $this->dao->dbh($this->dbh)->update(TABLE_TASK)->set('story')->eq($issueStories[$source])->where('id')->eq($issueTasks[$dest])->exec(); + $this->dao->dbh($this->dbh)->update(TABLE_TASK)->set('story')->eq($sourceObjectID)->where('id')->eq($destObjectID)->exec(); } - elseif($sourceObjectType == 'story' && $destObjectType == 'bug') + elseif($sourceObjectType == 'zstory' && $destObjectType == 'zbug') { - $this->dao->dbh($this->dbh)->update(TABLE_BUG)->set('story')->eq($issueStories[$source])->set('storyVersion')->eq(1)->where('id')->eq($issueBugs[$dest])->exec(); + $this->dao->dbh($this->dbh)->update(TABLE_BUG)->set('story')->eq($sourceObjectID)->set('storyVersion')->eq(1)->where('id')->eq($destObjectID)->exec(); } - elseif($sourceObjectType == 'bug' && $destObjectType == 'story') + elseif($sourceObjectType == 'zbug' && $destObjectType == 'zstory') { - $this->dao->dbh($this->dbh)->update(TABLE_BUG)->set('story')->eq($issueStories[$dest])->set('storyVersion')->eq(1)->where('id')->eq($issueBugs[$source])->exec(); + $this->dao->dbh($this->dbh)->update(TABLE_BUG)->set('story')->eq($destObjectID)->set('storyVersion')->eq(1)->where('id')->eq($sourceObjectID)->exec(); } - elseif($sourceObjectType == 'story' && $destObjectType == 'story') + elseif($sourceObjectType == 'zstory' && $destObjectType == 'zstory') { - $this->dao->dbh($this->dbh)->update(TABLE_STORY)->set("linkStories=concat(linkStories, ',{$issueStories[$dest]}')")->where('id')->eq($issueStories[$source])->exec(); + $this->dao->dbh($this->dbh)->update(TABLE_STORY)->set("linkStories=concat(linkStories, ',{$destObjectID}')")->where('id')->eq($sourceObjectID)->exec(); } - elseif($sourceObjectType == 'bug' && $destObjectType == 'bug') + elseif($sourceObjectType == 'zbug' && $destObjectType == 'zbug') { - $this->dao->dbh($this->dbh)->update(TABLE_BUG)->set("relatedBug=concat(relatedBug, ',{$issueBugs[$dest]}')")->where('id')->eq($issueBugs[$source])->exec(); + $this->dao->dbh($this->dbh)->update(TABLE_BUG)->set("relatedBug=concat(relatedBug, ',{$destObjectID}')")->where('id')->eq($sourceObjectID)->exec(); } } + + return true; } } diff --git a/module/convert/ui/convertjira.html.php b/module/convert/ui/convertjira.html.php deleted file mode 100644 index de6cbdaa37..0000000000 --- a/module/convert/ui/convertjira.html.php +++ /dev/null @@ -1,52 +0,0 @@ - - * @package convert - * @link https://www.zentao.net - */ -namespace zin; - -div -( - setClass('bg-white'), - div - ( - setClass('flex justify-between panel-form text-center mx-auto size-sm p-8'), - div - ( - setClass('border border-hover rounded-md cursor-pointer open-url p-4 w-72 h-28'), - set(array('data-url' => createLink('convert', 'importNotice', 'type=db'))), - div - ( - setClass('text-xl font-bold mb-4'), - $lang->convert->jira->importFromDB - ), - div - ( - setClass('text-gray mb-4'), - $lang->convert->jira->dbDesc - ) - ), - div - ( - setClass('border border-hover rounded-md cursor-pointer open-url p-4 w-72 h-28'), - set(array('data-url' => createLink('convert', 'importNotice', 'type=file'))), - div - ( - setClass('text-xl font-bold mb-4'), - $lang->convert->jira->importFromFile - ), - div - ( - setClass('text-gray mb-4'), - $lang->convert->jira->fileDesc - ) - ) - ) -); - -render(); diff --git a/module/convert/ui/importjira.html.php b/module/convert/ui/importjira.html.php index 8c1a286c06..e729f73f34 100644 --- a/module/convert/ui/importjira.html.php +++ b/module/convert/ui/importjira.html.php @@ -10,27 +10,40 @@ declare(strict_types=1); */ namespace zin; +include('jiraside.html.php'); + $importUrl = inlink('importJira', "method={$method}&mode=import&type=user&lastID=0&createTable=true"); -panel +div ( - set::title($lang->convert->jira->importJira), - h::ul + setClass('flex'), + panel ( - setID('importResult'), - setClass('mx-4'), - setStyle(array('list-style' => 'disc')), - li - ( - setClass('text-danger font-bold importing my-1 hidden'), - $lang->convert->jira->importing - ) + setClass('w-1/4 mr-4'), + $items ), - button + panel ( - on::click("importJira(event, '{$importUrl}', true)"), - setClass('btn primary'), - $lang->convert->jira->start + setClass('flex-1 p-4 overflow-y-scroll scrollbar-thin scrollbar-hover'), + setStyle(array('max-height' => 'calc(100vh - 130px)')), + div(setClass('panel-title text-lg'), $lang->convert->jira->steps['confirme']), + h::ul + ( + setID('importResult'), + setClass('mx-4 my-2'), + setStyle(array('list-style' => 'disc')), + li + ( + setClass('text-danger font-bold importing my-1 hidden'), + $lang->convert->jira->importing + ) + ), + button + ( + on::click("importJira(event, '{$importUrl}', true)"), + setClass('btn primary'), + $lang->convert->jira->start + ) ) ); diff --git a/module/convert/ui/importnotice.html.php b/module/convert/ui/importnotice.html.php deleted file mode 100644 index e8692fd519..0000000000 --- a/module/convert/ui/importnotice.html.php +++ /dev/null @@ -1,80 +0,0 @@ - - * @package convert - * @link https://www.zentao.net - */ -namespace zin; - -$labelWidth = $method == 'db' ? '80px' : '20px'; - -formPanel -( - set::title($method == 'db' ? $lang->convert->jira->importFromDB : $lang->convert->jira->importFromFile), - set::headingClass('justify-start'), - set::bodyClass('px-0'), - set::submitBtnText($lang->convert->jira->next), - set::backUrl(inlink('convertJira')), - to::heading - ( - span - ( - setClass('flex items-center text-gray'), - icon('exclamation text-warning mr-1'), - span($lang->convert->jira->importNotice) - ) - ), - formGroup - ( - setStyle(array('align-items' => 'center')), - set::label('1.'), - set::labelWidth($labelWidth), - $lang->convert->jira->importSteps[$method][1] - ), - formGroup - ( - setStyle(array('align-items' => 'center')), - set::label('2.'), - set::labelWidth($labelWidth), - $lang->convert->jira->importSteps[$method][2] - ), - formGroup - ( - setStyle(array('align-items' => 'center')), - set::label('3.'), - set::labelWidth($labelWidth), - $method == 'db' ? $lang->convert->jira->importSteps[$method][3] : html(sprintf($lang->convert->jira->importSteps[$method][3], $app->getTmpRoot() . 'jirafile')), - ), - formGroup - ( - setStyle(array('align-items' => 'center')), - set::label('4.'), - set::labelWidth($labelWidth), - html(sprintf($lang->convert->jira->importSteps[$method][4], $app->getTmpRoot())), - ), - formGroup - ( - setStyle(array('align-items' => 'center')), - set::label('5.'), - set::labelWidth($labelWidth), - $lang->convert->jira->importSteps[$method][5] - ), - $method == 'db' ? formGroup - ( - set::label($lang->convert->jira->database), - set::labelWidth($labelWidth), - set::required(true), - input - ( - setClass('w-72'), - set::name('dbName'), - set::placeholder($lang->convert->jira->dbNameNotice) - ) - ) : null -); - -render(); diff --git a/module/convert/ui/initjirauser.html.php b/module/convert/ui/initjirauser.html.php index 0c2c6fcbe4..bc60b11399 100644 --- a/module/convert/ui/initjirauser.html.php +++ b/module/convert/ui/initjirauser.html.php @@ -10,58 +10,96 @@ declare(strict_types=1); */ namespace zin; -formPanel +include('jiraside.html.php'); + +div ( - set::title($lang->convert->jira->initJiraUser), - set::submitBtnText($lang->convert->jira->next), - set::backUrl(inlink('mapJira2Zentao', "method={$method}&dbName={$this->session->jiraDB}&step=4")), - formRow + setClass('flex'), + panel ( - formGroup - ( - setClass('w-1/2'), - set::label($lang->user->password), - set::control('password'), - set::name('password1'), - set::placeholder($lang->user->placeholder->passwordStrength[0]), - set::required(true) - ), - formGroup - ( - setClass('w-1/2'), - icon('help self-center text-warning mr-1 pl-2'), - span - ( - setClass('self-center text-gray'), - $lang->convert->jira->passwordNotice - ) - ) + setClass('w-1/4 mr-4'), + $items ), - formGroup + panel ( - setClass('grow-0 w-1/2'), - set::label($lang->user->password2), - set::control('password'), - set::name('password2'), - set::required(true) - ), - formRow - ( - formGroup + setClass('flex-1 m-0 p-0 overflow-y-scroll scrollbar-thin scrollbar-hover'), + setStyle(array('max-height' => 'calc(100vh - 130px)')), + formPanel ( - setClass('w-1/2'), - set::label($lang->user->group), - set::name('group'), - set::items($groups) - ), - formGroup - ( - setClass('w-1/2'), - icon('help self-center text-warning mr-1 pl-2'), - span + set::title($lang->convert->jira->steps['user']), + set::actionsClass('hidden'), + formRow ( - setClass('self-center text-gray'), - $lang->convert->jira->groupNotice + formGroup + ( + setClass('w-1/2'), + set::label($lang->user->account), + set::control(array('control' => 'radioList', 'inline' => true)), + set::name('mode'), + set::items($lang->convert->jiraUserMode), + set::value(!empty($_SESSION['jiraUser']) ? zget($this->session->jiraUser, 'mode', 'account') : 'account') + ), + formGroup + ( + setClass('w-1/2'), + span + ( + icon('help self-center text-warning mr-1 pl-2'), + setClass('self-center text-gray'), + $lang->convert->jira->accountNotice + ) + ) + ), + formRow + ( + formGroup + ( + setClass('w-1/2'), + set::label($lang->user->password), + set::control('password'), + set::name('password1'), + set::placeholder($lang->user->placeholder->passwordStrength[0]), + set::required(true) + ), + formGroup + ( + setClass('w-1/2'), + span + ( + icon('help self-center text-warning mr-1 pl-2'), + setClass('self-center text-gray'), + $lang->convert->jira->passwordNotice + ) + ) + ), + formGroup + ( + setClass('grow-0 w-1/2'), + set::label($lang->user->password2), + set::control('password'), + set::name('password2'), + set::required(true) + ), + formRow + ( + formGroup + ( + setClass('w-1/2'), + set::label($lang->user->group), + set::name('group'), + set::items($groups), + set::value(!empty($_SESSION['jiraUser']) ? zget($this->session->jiraUser, 'group', '') : '') + ), + formGroup + ( + setClass('w-1/2'), + span + ( + icon('help self-center text-warning mr-1 pl-2'), + setClass('self-center text-gray'), + $lang->convert->jira->groupNotice + ) + ) ) ) ) diff --git a/module/convert/ui/mapjira2zentao.html.php b/module/convert/ui/mapjira2zentao.html.php index 39a8faa133..67aef60e63 100644 --- a/module/convert/ui/mapjira2zentao.html.php +++ b/module/convert/ui/mapjira2zentao.html.php @@ -10,41 +10,22 @@ declare(strict_types=1); */ namespace zin; -$items = array(); -foreach($lang->convert->jira->steps as $key => $label) -{ - $items[] = (object)array('text' => $label, 'active' => $key == $step); -} +include('jiraside.html.php'); -div -( - setClass('main-header relative center size-lg mx-auto my-4'), - setStyle(array('max-width' => 'var(--zt-panel-form-max-width)')), - div - ( - setClass('absolute left-0'), - $lang->convert->jira->mapJira2Zentao - ), - navigator - ( - set::items($items) - ), -); - -$buildHeader = function(string $label, int $count = 2): h +$buildHeader = function(string $label): h { return div ( - setClass("flex justify-center text-md font-bold w-1/{$count}"), + setClass("flex-1 text-center text-md font-bold"), $label ); }; -$buildJira = function(string $name, int $count, string $label, int $value): h +$buildJira = function(string $name, string $label, int $value): h { return div ( - setClass("flex justify-center w-1/{$count}"), + setClass("flex-1 text-center"), $label, input ( @@ -55,22 +36,35 @@ $buildJira = function(string $name, int $count, string $label, int $value): h ); }; -$buildZenTao = function(string $name, int $count, array $items): h +$buildZenTao = function(string $name, array $items, string|int $index = '', string $key = '', string $default = '') use($jiraRelation, $defaultValue): h { + $default = !empty($defaultValue[$name][$key]) ? $defaultValue[$name][$key] : $default; return div ( - setClass("ml-4 w-1/{$count}"), + setClass("flex-1 mx-2"), picker ( - set::name("{$name}[]"), - set::items($items) + set::name("{$name}[$index]"), + set::items($items), + set::value(!empty($jiraRelation[$name][$index]) ? $jiraRelation[$name][$index] : $default) ) ); }; $rows = array(); -if($step == 1) +if($step == 'object') { + $rows[] = div + ( + setClass('panel-title'), + span(setClass('text-lg'), $lang->convert->jira->steps[$step]), + span + ( + icon('help self-center text-warning mr-1 pl-2'), + setClass('self-center font-medium text-gray'), + $lang->convert->jira->mapObjectNotice + ) + ); $rows[] = formRow ( $buildHeader($lang->convert->jira->jiraObject), @@ -78,88 +72,190 @@ if($step == 1) ); foreach($issueTypeList as $id => $issueType) { - $value = $method == 'db' ? $issueType->pname : $issueType['name']; + $value = $issueType->pname; $rows[] = formRow ( - $buildJira('jiraObject', 2, $value, $id), - $buildZenTao('zentaoObject', 2, $lang->convert->jira->zentaoObjectList) + $buildJira('jiraObject', $value, $id), + $buildZenTao('zentaoObject', $zentaoObjects, $id, $value) ); } } -if($step == 2) +if(!empty($jiraRelation['zentaoObject']) && in_array($step, array_keys($jiraRelation['zentaoObject']))) { + if(!empty($fieldList)) + { + $rows[] = div + ( + setClass('panel-title'), + span(setClass('text-lg'), $lang->convert->jira->objectField), + span + ( + icon('help self-center text-warning mr-1 pl-2'), + setClass('self-center font-medium text-gray'), + $lang->convert->jira->mapFieldNotice + ) + ); + $rows[] = formRow + ( + $buildHeader(sprintf($lang->convert->jira->jiraField, zget($issueTypeList[$step], 'pname', ''))), + $buildHeader(sprintf($lang->convert->jira->zentaoField, zget($zentaoObjects, $jiraRelation['zentaoObject'][$step]))) + ); + + $zentaoFields = $this->convert->getZentaoFields($jiraRelation['zentaoObject'][$step]); + foreach($fieldList as $id => $field) + { + $rows[] = formRow + ( + $buildJira("jiraField$step", $field, $id), + $buildZenTao("zentaoField$step", $zentaoFields, $id, $field, 'add_field') + ); + } + $rows[] = divider(); + } + if(!empty($statusList)) + { + $zentaoStatusList = $this->convert->getZentaoStatus($jiraRelation['zentaoObject'][$step]); + $defaultStatus = $this->convert->convertStatus($jiraRelation['zentaoObject'][$step], '', ''); + $rows[] = div + ( + setClass('panel-title'), + span(setClass('text-lg'), $lang->convert->jira->objectStatus), + span + ( + icon('help self-center text-warning mr-1 pl-2'), + setClass('self-center font-medium text-gray'), + sprintf($lang->convert->jira->mapStatusNotice, zget($zentaoStatusList, $defaultStatus)) + ) + ); + $rows[] = formRow + ( + $buildHeader(sprintf($lang->convert->jira->jiraStatus, zget($issueTypeList[$step], 'pname', ''))), + $buildHeader(sprintf($lang->convert->jira->zentaoStatus, zget($zentaoObjects, $jiraRelation['zentaoObject'][$step]))), + in_array($jiraRelation['zentaoObject'][$step], array('requirement', 'story', 'epic')) ? $buildHeader(sprintf($lang->convert->jira->zentaoStage, zget($zentaoObjects, $jiraRelation['zentaoObject'][$step]))) : null + ); + + foreach($statusList as $id => $status) + { + $rows[] = formRow + ( + $buildJira("jiraStatus$step", $status, $id), + $buildZenTao("zentaoStatus$step", $zentaoStatusList, $id, $status), + in_array($jiraRelation['zentaoObject'][$step], array('requirement', 'story', 'epic')) ? $buildZenTao("zentaoStage$step", $lang->story->stageList, $id, $status) : null + ); + } + } + if(!empty($jiraActions)) + { + $rows[] = formRow + ( + setClass('hidden'), + $buildHeader(sprintf($lang->convert->jira->jiraAction, zget($issueTypeList[$step], 'pname', ''))), + $buildHeader(sprintf($lang->convert->jira->zentaoAction, zget($zentaoObjects, $jiraRelation['zentaoObject'][$step]))) + ); + + $zentaoActions = $this->convert->getZentaoActions($jiraRelation['zentaoObject'][$step]); + foreach($jiraActions['actions'] as $id => $action) + { + $value = $action['name']; + $rows[] = formRow + ( + setClass('hidden'), + $buildJira("jiraAction$step", $value, $id), + $buildZenTao("zentaoAction$step", $zentaoActions, $id, $value, 'add_action') + ); + } + } + + if(in_array($jiraRelation['zentaoObject'][$step], array('bug', 'task', 'story', 'requirement', 'epic'))) + { + $rows[] = divider(); + $rows[] = div + ( + setClass('panel-title'), + span(setClass('text-lg'), $lang->convert->jira->objectResolution), + span + ( + icon('help self-center text-warning mr-1 pl-2'), + setClass('self-center font-medium text-gray'), + $lang->convert->jira->mapReasonNotice + ) + ); + $rows[] = formRow + ( + $buildHeader(sprintf($lang->convert->jira->jiraResolution, zget($issueTypeList[$step], 'pname', ''))), + $buildHeader(sprintf($jiraRelation['zentaoObject'][$step] == 'bug' ? $lang->convert->jira->zentaoResolution : $lang->convert->jira->zentaoReason, zget($zentaoObjects, $jiraRelation['zentaoObject'][$step]))) + ); + + foreach($resolutionList as $id => $resolution) + { + $value = $resolution->pname; + $module = $jiraRelation['zentaoObject'][$step]; + if($module == 'epic' || $module == 'story' || $module == 'requirement') $module = 'story'; + $reasonList = $module == 'bug' ? $lang->bug->resolutionList + array('add_resolution' => $lang->convert->add) : $lang->{$module}->reasonList + array('add_reason' => $lang->convert->add); + if($module == 'epic' || $module == 'story' || $module == 'requirement') unset($reasonList['subdivided']); + $rows[] = formRow + ( + $buildJira("jiraResolution$step", $value, $id), + $module == 'bug' ? $buildZenTao("zentaoResolution$step", $reasonList, $id, $value) : $buildZenTao("zentaoReason$step", $reasonList, $id, $value) + ); + } + } +} + +if($step == 'relation') +{ + $rows[] = div + ( + setClass('panel-title'), + span(setClass('text-lg'), $lang->convert->jira->steps[$step]), + span + ( + icon('help self-center text-warning mr-1 pl-2'), + setClass('self-center font-medium text-gray'), + $lang->convert->jira->mapRelationNotice + ) + ); $rows[] = formRow ( $buildHeader($lang->convert->jira->jiraLinkType), $buildHeader($lang->convert->jira->zentaoLinkType) ); + $relationList = $this->convert->getZentaoRelationList(); foreach($linkTypeList as $id => $linkType) { - $value = $method == 'db' ? $linkType->linkname : $linkType['linkname']; + $value = $linkType->LINKNAME; $rows[] = formRow ( - $buildJira('jiraLinkType', 2, $value, $id), - $buildZenTao('zentaoLinkType', 2, $lang->convert->jira->zentaoLinkTypeList) + $buildJira('jiraLinkType', $value, $id), + $buildZenTao('zentaoLinkType', $relationList, $id) ); } } -if($step == 3) -{ - $rows[] = formRow - ( - $buildHeader($lang->convert->jira->jiraResolution, 3), - $buildHeader($lang->convert->jira->zentaoResolution, 3), - $buildHeader($lang->convert->jira->zentaoReason, 3) - ); - foreach($resolutionList as $id => $resolution) - { - $value = $method == 'db' ? $resolution->pname : $resolution['name']; - - $rows[] = formRow - ( - $buildJira('jiraResolution', 3, $value, $id), - $buildZenTao('zentaoResolution', 3, $lang->bug->resolutionList), - $buildZenTao('zentaoReason', 3, $lang->story->reasonList) - ); - } -} - -if($step == 4) -{ - $rows[] = formRow - ( - $buildHeader($lang->convert->jira->jiraStatus, 5), - $buildHeader($lang->convert->jira->storyStatus, 5), - $buildHeader($lang->convert->jira->storyStage, 5), - $buildHeader($lang->convert->jira->taskStatus, 5), - $buildHeader($lang->convert->jira->bugStatus, 5) - ); - foreach($statusList as $id => $status) - { - $value = $method == 'db' ? $status->pname : $status['name']; - - $rows[] = formRow - ( - $buildJira('jiraStatus', 5, $value, $id), - $buildZenTao('storyStatus', 5, $lang->story->statusList), - $buildZenTao('storyStage', 5, $lang->story->stageList), - $buildZenTao('taskStatus', 5, $lang->task->statusList), - $buildZenTao('bugStatus', 5, $lang->bug->statusList) - ); - } -} - -$backUrl = $step == 1 ? inlink('importNotice', "method={$method}") : inlink('mapJira2Zentao', "method={$method}&dbName={$dbName}&step=" . --$step); - -formPanel +div ( - set::submitBtnText($lang->convert->jira->next), - set::backUrl($backUrl), - $rows + setClass('flex'), + panel + ( + setClass('w-1/4 mr-4 overflow-y-scroll scrollbar-thin scrollbar-hover'), + setStyle(array('max-height' => 'calc(100vh - 130px)')), + $items + ), + panel + ( + setClass('flex-1 m-0 p-0 overflow-y-scroll scrollbar-thin scrollbar-hover'), + setStyle(array('max-height' => 'calc(100vh - 130px)')), + formPanel + ( + on::change('[name^="zentaoField"]', 'changeField'), + setClass('p-0'), + set::actionsClass('hidden'), + $rows + ) + ) ); render(); diff --git a/module/projectstory/ui/story.html.php b/module/projectstory/ui/story.html.php index 9708d28d8b..8adfae06a1 100644 --- a/module/projectstory/ui/story.html.php +++ b/module/projectstory/ui/story.html.php @@ -91,7 +91,6 @@ if(isset($setting['actions'])) unset($setting['actions']); foreach($setting as $key => $col) { if($key == 'assignedTo') $setting[$key]['type'] = 'user'; - $setting[$key]['sortType'] = false; if(isset($col['link'])) unset($setting[$key]['link']); if($key == 'pri') $setting[$key]['priList'] = $lang->story->priList; From c6f395029720702ff3b604c7a5902d26a7c2f6a3 Mon Sep 17 00:00:00 2001 From: wangyuting Date: Fri, 7 Mar 2025 15:25:28 +0800 Subject: [PATCH 18/53] * Merge import jira code. --- db/jira.sql | 3 + module/convert/js/importconfluence.ui.js | 33 +++++ module/convert/js/initconfluenceuser.ui.js | 4 + module/convert/js/initjirauser.ui.js | 4 + module/convert/js/mapconfluence2zentao.ui.js | 15 +++ module/convert/js/mapjira2zentao.ui.js | 46 +++++++ module/convert/ui/importjiranotice.html.php | 120 +++++++++++++++++++ module/convert/ui/index.html.php | 53 ++++++++ module/convert/ui/jiraside.html.php | 41 +++++++ 9 files changed, 319 insertions(+) create mode 100644 db/jira.sql create mode 100644 module/convert/js/importconfluence.ui.js create mode 100644 module/convert/js/initconfluenceuser.ui.js create mode 100644 module/convert/js/initjirauser.ui.js create mode 100644 module/convert/js/mapconfluence2zentao.ui.js create mode 100644 module/convert/js/mapjira2zentao.ui.js create mode 100644 module/convert/ui/importjiranotice.html.php create mode 100644 module/convert/ui/index.html.php create mode 100644 module/convert/ui/jiraside.html.php diff --git a/db/jira.sql b/db/jira.sql new file mode 100644 index 0000000000..8908380f71 --- /dev/null +++ b/db/jira.sql @@ -0,0 +1,3 @@ +ALTER TABLE `zt_workflowgroup` MODIFY `name` varchar(255) NOT NULL DEFAULT ''; +ALTER TABLE `zt_workflowgroup` ADD COLUMN `exclusive` enum('0','1') NOT NULL DEFAULT '0' AFTER `main`; +ALTER TABLE `zt_doclib` ADD COLUMN `archived` enum('0','1') NOT NULL DEFAULT '0'; diff --git a/module/convert/js/importconfluence.ui.js b/module/convert/js/importconfluence.ui.js new file mode 100644 index 0000000000..59188540b9 --- /dev/null +++ b/module/convert/js/importconfluence.ui.js @@ -0,0 +1,33 @@ +function importConfluence(event, url, hide) +{ + if(hide === true) $(event.target).hide(); + + $('#importResult .importing').removeClass('hidden'); + + $.getJSON(url, function(response) + { + if(response.result == 'finished') + { + $('#importResult').append("
  • " + response.message + '
  • '); + $('#importResult .importing').addClass('hidden'); + return false; + } + else + { + className = response.type + 'count'; + $typeCount = $('#importResult .' + className) + if($typeCount.length == 0) + { + $('#importResult').append("
  • " + response.message + '
  • '); + } + else + { + count = parseInt($typeCount.html()) + parseInt(response.count); + $typeCount.html(count); + } + + return importConfluence(event, response.next); + } + }); + return false; +}; diff --git a/module/convert/js/initconfluenceuser.ui.js b/module/convert/js/initconfluenceuser.ui.js new file mode 100644 index 0000000000..ca6a10881e --- /dev/null +++ b/module/convert/js/initconfluenceuser.ui.js @@ -0,0 +1,4 @@ +window.next = function() +{ + $('button[type=submit]').trigger('click'); +} diff --git a/module/convert/js/initjirauser.ui.js b/module/convert/js/initjirauser.ui.js new file mode 100644 index 0000000000..ca6a10881e --- /dev/null +++ b/module/convert/js/initjirauser.ui.js @@ -0,0 +1,4 @@ +window.next = function() +{ + $('button[type=submit]').trigger('click'); +} diff --git a/module/convert/js/mapconfluence2zentao.ui.js b/module/convert/js/mapconfluence2zentao.ui.js new file mode 100644 index 0000000000..b3ffed1434 --- /dev/null +++ b/module/convert/js/mapconfluence2zentao.ui.js @@ -0,0 +1,15 @@ +window.changeSpace = function(event) +{ + space = $(event.target).val(); + const link = $.createLink('convert', 'ajaxGetZentaoDocLib', 'spaceType=' + space); + $.getJSON(link, function(data) + { + $(event.target).closest('.form-row').find('[name^=zentaoDocLib]').zui('picker').render(data); + $(event.target).closest('.form-row').find('[name^=zentaoDocLib]').zui('picker').$.setValue(data.defaultValue); + }); +} + +window.next = function() +{ + $('button[type=submit]').trigger('click'); +} diff --git a/module/convert/js/mapjira2zentao.ui.js b/module/convert/js/mapjira2zentao.ui.js new file mode 100644 index 0000000000..efcfe799f8 --- /dev/null +++ b/module/convert/js/mapjira2zentao.ui.js @@ -0,0 +1,46 @@ +$(function() +{ + $('.picker-box').last().on('inited', function(){updateItems();}); +}); + +window.next = function() +{ + $('button[type=submit]').trigger('click'); +} + +window.changeField = function(e) +{ + updateItems(); +} + +window.updateItems = function() +{ + let chosenFields = []; + $("[name^='zentaoField']").each(function() + { + const field = $(this).val(); + if(field == 'add_field') return; + if(field && chosenFields.indexOf(field) == -1) chosenFields.push(field); + }); + + let allItems = zui.Picker.query("[name^='zentaoField']")?.options?.items; + if(typeof allItems == 'undefined') return; + + let fieldItems = []; + for(i = 0; i < allItems.length; i++) + { + allItems[i].disabled = false; + if(chosenFields.includes(allItems[i].value)) allItems[i].disabled = true; + fieldItems[allItems[i].value] = Object.assign({},allItems[i]); + fieldItems[allItems[i].value].i = i; + } + + $("[name^='zentaoField']").each(function() + { + const $field = $(this).zui('picker'); + const field = $(this).val(); + let currentFieldItems = JSON.parse(JSON.stringify(allItems)); + if(field) currentFieldItems[fieldItems[field].i].disabled = false; + $field.render({items: currentFieldItems}); + }); +} diff --git a/module/convert/ui/importjiranotice.html.php b/module/convert/ui/importjiranotice.html.php new file mode 100644 index 0000000000..8cec652c71 --- /dev/null +++ b/module/convert/ui/importjiranotice.html.php @@ -0,0 +1,120 @@ + + * @package convert + * @link https://www.zentao.net + */ +namespace zin; + +$labelWidth = $method == 'db' ? '80px' : '100px'; + +formPanel +( + set::title($method == 'db' ? $lang->convert->jira->importFromDB : $lang->convert->jira->importFromFile), + set::headingClass('justify-start'), + set::bodyClass('px-0'), + set::submitBtnText($lang->convert->jira->next), + set::backUrl(inlink('index')), + to::heading + ( + span + ( + setClass('flex items-center text-gray'), + icon('exclamation text-warning mr-1'), + span($lang->convert->jira->importNotice) + ) + ), + formGroup + ( + setStyle(array('align-items' => 'center')), + set::label('1.'), + set::labelWidth($labelWidth), + $lang->convert->jira->importSteps[$method][1] + ), + formGroup + ( + setStyle(array('align-items' => 'center')), + set::label('2.'), + set::labelWidth($labelWidth), + $lang->convert->jira->importSteps[$method][2] + ), + formGroup + ( + setStyle(array('align-items' => 'center')), + set::label('3.'), + set::labelWidth($labelWidth), + $method == 'db' ? $lang->convert->jira->importSteps[$method][3] : html(sprintf($lang->convert->jira->importSteps[$method][3], $app->getTmpRoot() . 'jirafile')) + ), + formGroup + ( + setStyle(array('align-items' => 'center')), + set::label('4.'), + set::labelWidth($labelWidth), + html(sprintf($lang->convert->jira->importSteps[$method][4], $app->getTmpRoot())) + ), + formGroup + ( + setStyle(array('align-items' => 'center')), + set::label('5.'), + set::labelWidth($labelWidth), + $lang->convert->jira->importSteps[$method][5] + ), + $method == 'file' ? formGroup + ( + setStyle(array('align-items' => 'center')), + set::label('6.'), + set::labelWidth($labelWidth), + $lang->convert->jira->importSteps[$method][6] + ) : null, + $method == 'db' ? formGroup + ( + set::label($lang->convert->jira->database), + set::labelWidth($labelWidth), + set::required(true), + input + ( + setClass('w-72'), + set::name('dbName'), + set::placeholder($lang->convert->jira->dbNameNotice) + ) + ) : null, + $method == 'file' ? formGroup + ( + set::label($lang->convert->jira->domain), + set::labelWidth($labelWidth), + input + ( + setClass('w-72'), + set::name('jiraDomain'), + set::value(zget($jiraApi, 'domain', '')) + ) + ) : null, + $method == 'file' ? formGroup + ( + set::label($lang->convert->jira->admin), + set::labelWidth($labelWidth), + input + ( + setClass('w-72'), + set::name('jiraAdmin'), + set::value(zget($jiraApi, 'admin', '')) + ) + ) : null, + $method == 'file' ? formGroup + ( + set::label($lang->convert->jira->token), + set::labelWidth($labelWidth), + input + ( + setClass('w-72'), + set::name('jiraToken'), + set::value(zget($jiraApi, 'token', '')) + ) + ) : null, +); + +render(); diff --git a/module/convert/ui/index.html.php b/module/convert/ui/index.html.php new file mode 100644 index 0000000000..742ba41dee --- /dev/null +++ b/module/convert/ui/index.html.php @@ -0,0 +1,53 @@ + + * @package convert + * @link https://www.zentao.net + */ +namespace zin; + +panel +( + setID('importJira'), + set::title($lang->convert->jira->importJira), + div + ( + setClass('flex justify-between panel-form text-center mx-auto size-sm p-8'), + div + ( + setClass('border border-hover rounded-md cursor-pointer open-url p-4 w-72 h-28'), + set(array('data-url' => createLink('convert', 'importJiraNotice', 'type=db'))), + div + ( + setClass('text-xl font-bold mb-4'), + $lang->convert->jira->importFromDB + ), + div + ( + setClass('text-gray mb-4'), + $lang->convert->jira->dbDesc + ) + ), + div + ( + setClass('border border-hover rounded-md cursor-pointer open-url p-4 w-72 h-28'), + set(array('data-url' => createLink('convert', 'importJiraNotice', 'type=file'))), + div + ( + setClass('text-xl font-bold mb-4'), + $lang->convert->jira->importFromFile + ), + div + ( + setClass('text-gray mb-4'), + $lang->convert->jira->fileDesc + ) + ) + ) +); + +render(); diff --git a/module/convert/ui/jiraside.html.php b/module/convert/ui/jiraside.html.php new file mode 100644 index 0000000000..f101ef6f81 --- /dev/null +++ b/module/convert/ui/jiraside.html.php @@ -0,0 +1,41 @@ + + * @package convert + * @link https://www.zentao.net + */ +namespace zin; + +$items = array(); +foreach($stepList as $currentStep => $stepLabel) +{ + $icon = ''; + $url = 'javascript:;'; + if($step == $currentStep) + { + $icon = icon('ellipsis-v', setClass('secondary rounded-full rotate-90 p-1 mx-2')); + } + else if(!empty($stepStatus[$currentStep]) && $stepStatus[$currentStep] == 'done') + { + $icon = icon('check', setClass('success rounded-full p-1 mx-2')); + $url = inlink('mapJira2Zentao', "method={$method}&dbName={$dbName}&step={$currentStep}"); + if($currentStep == 'user') $url = inlink('initJiraUser', "method={$method}&dbName={$dbName}"); + } + else + { + $icon = icon('ellipsis-v', setClass('gray-200 rounded-full rotate-90 text-white p-1 mx-2')); + } + + $items[] = a(set::href($url), div(setClass('h-10 border content-center mb-4'), $icon, span(setClass('text-black'), $stepLabel))); +} + +featureBar(); +toolbar +( + $backUrl ? item(set(array('text' => $lang->convert->jira->back, 'class' => 'default', 'url' => $backUrl))) : null, + $step != 'confirme' ? item(set(array('text' => $lang->convert->jira->next, 'class' => 'primary', 'data-on' => 'click', 'data-call' => 'next', 'data-params' => 'event'))) : null +); From 3f8d502bcdad97a8749c42c766ef7996e712090a Mon Sep 17 00:00:00 2001 From: wangyuting Date: Fri, 7 Mar 2025 15:38:37 +0800 Subject: [PATCH 19/53] * [bug#60337,done,0.1h,0h] Fix import jira user error. --- module/convert/tao.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/module/convert/tao.php b/module/convert/tao.php index f9ee313554..3566fb3443 100644 --- a/module/convert/tao.php +++ b/module/convert/tao.php @@ -602,12 +602,11 @@ class convertTao extends convertModel if(!dao::isError() && !empty($userConfig['group'])) { - $data = new stdclass(); - $data->account = $user->account; - $data->group = $userConfig['group']; - $data->project = ''; - - $this->dao->dbh($this->dbh)->replace(TABLE_USERGROUP)->data($data)->exec(); + $group = new stdclass(); + $group->account = $user->account; + $group->group = $userConfig['group']; + $group->project = ''; + $this->dao->dbh($this->dbh)->replace(TABLE_USERGROUP)->data($group)->exec(); } $this->createTmpRelation('juser', $data->ID, 'zuser', $user->account); From 46e96d522f6f59cc469851cd9133d0cd9c5f312c Mon Sep 17 00:00:00 2001 From: wangyuting Date: Fri, 7 Mar 2025 15:43:44 +0800 Subject: [PATCH 20/53] Revert "* [refac] get cache configuration from environment." --- config/cache.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/config/cache.php b/config/cache.php index 77d532ee32..26c2911df2 100644 --- a/config/cache.php +++ b/config/cache.php @@ -1,11 +1,11 @@ cache = new stdclass(); -$config->cache->enable = getenv('ZT_CACHE_ENABLE') ?: false; // 是否开启缓存。Enable cache or not. -$config->cache->driver = getenv('ZT_CACHE_DRIVER') ?: 'apcu'; // 缓存驱动。 The driver of cache. Can be file|yac|apcu|redis. -$config->cache->scope = getenv('ZT_CACHE_SCOPE') ?: ''; // 缓存服务范围。The scope of cache. Can be private|shared. -$config->cache->namespace = getenv('ZT_CACHE_NAMESPACE') ?: ''; // 缓存命名空间。The namespace of cache. -$config->cache->lifetime = getenv('ZT_CACHE_LIFETIME') ?: 0; // 缓存生存时间,默认永不过期。The lifetime of cache. Default is no expiration. +$config->cache->enable = false; // 是否开启缓存。Enable cache or not. +$config->cache->driver = 'apcu'; // 缓存驱动。 The driver of cache. Can be file|yac|apcu|redis. +$config->cache->scope = ''; // 缓存服务范围。The scope of cache. Can be private|shared. +$config->cache->namespace = ''; // 缓存命名空间。The namespace of cache. +$config->cache->lifetime = 0; // 缓存生存时间,默认永不过期。The lifetime of cache. Default is no expiration. $config->cache->dao = new stdClass(); $config->cache->dao->enable = true; // 是否开启 DAO 缓存。Enable DAO cache or not. From 233f9b5ceef0fcb7440317c624301c3868a1e239 Mon Sep 17 00:00:00 2001 From: wangyidong Date: Fri, 7 Mar 2025 16:29:36 +0800 Subject: [PATCH 21/53] * [perf story #72106] Add importing notice. --- module/convert/js/importjira.ui.js | 19 +++++++++++++++++++ module/convert/ui/importjira.html.php | 2 ++ 2 files changed, 21 insertions(+) diff --git a/module/convert/js/importjira.ui.js b/module/convert/js/importjira.ui.js index 32375ab101..520262488c 100644 --- a/module/convert/js/importjira.ui.js +++ b/module/convert/js/importjira.ui.js @@ -1,8 +1,21 @@ +let dynamicImporting = langImporting; +const intervalId = setInterval(function() +{ + const $dynamicImporting = $('#importResult .dynamicImporting'); + if($dynamicImporting.length == 0) return; + + let ellipsis = $dynamicImporting.html().replace(langImporting, ''); + ellipsis += '•'; + if(ellipsis.length > 12) ellipsis = ''; + $('#importResult .dynamicImporting').html(langImporting + ' ' + ellipsis); +}, 1000); + function importJira(event, url, hide) { if(hide === true) $(event.target).hide(); $('#importResult .importing').removeClass('hidden'); + if($('#importResult .dynamicImporting').length == 0) $('#importResult').append("
  • " + dynamicImporting + "
  • "); $.get(url, function(data) { @@ -11,6 +24,8 @@ function importJira(event, url, hide) let response = JSON.parse(data); if(response.result == 'finished') { + clearInterval(intervalId); + $('#importResult .dynamicImporting').remove(); $('#importResult').append("
  • " + response.message + '
  • '); $('#importResult .importing').addClass('hidden'); return false; @@ -21,6 +36,8 @@ function importJira(event, url, hide) const $typeCount = $('#importResult .' + className) if($typeCount.length == 0) { + dynamicImporting = $('#importResult .dynamicImporting').html(); + $('#importResult .dynamicImporting').remove(); $('#importResult').append("
  • " + response.message + '
  • '); } else @@ -34,6 +51,8 @@ function importJira(event, url, hide) } catch(e) { + clearInterval(intervalId); + $('#importResult .dynamicImporting').remove(); $('#importResult').append("
  • " + data + '
  • '); } }); diff --git a/module/convert/ui/importjira.html.php b/module/convert/ui/importjira.html.php index e729f73f34..9bd89aacd8 100644 --- a/module/convert/ui/importjira.html.php +++ b/module/convert/ui/importjira.html.php @@ -14,6 +14,8 @@ include('jiraside.html.php'); $importUrl = inlink('importJira', "method={$method}&mode=import&type=user&lastID=0&createTable=true"); +jsVar('langImporting', $lang->convert->jira->importingAB); + div ( setClass('flex'), From b4a6d119803b56fa6dca68c2cdfbe7d74660aa49 Mon Sep 17 00:00:00 2001 From: wangyidong Date: Mon, 10 Mar 2025 09:17:32 +0800 Subject: [PATCH 22/53] * [refac] Adjust for createWorkflow when import jira. --- module/convert/tao.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/module/convert/tao.php b/module/convert/tao.php index 3566fb3443..890825fb7f 100644 --- a/module/convert/tao.php +++ b/module/convert/tao.php @@ -1914,10 +1914,12 @@ class convertTao extends convertModel $this->createDefaultLayout($fields, $flow); /* 将工作流发布到指定位置。 */ + $_POST = array(); $_POST['navigator'] = 'secondary'; $_POST['app'] = 'scrum'; - $_POST['positionModule'] = 'settings'; - $_POST['position'] = 'position'; + $_POST['positionModule'] = 'other'; + $_POST['dropMenu'] = 'issue'; + $_POST['position'] = 'before'; $_POST['syncRelease'] = 'all'; $_POST['module'] = $flow->module; $this->workflow->release($flowID); From 55899d3d21130dc2a90da24d9c890ff7ba5ca8d2 Mon Sep 17 00:00:00 2001 From: sunhao Date: Wed, 5 Mar 2025 15:25:47 +0800 Subject: [PATCH 23/53] * [misc] support for importing confluence content to editor. --- lib/zin/wg/docapp/js/v1.js | 28 ++++++++++++++++++++++------ module/doc/lang/de.php | 1 + module/doc/lang/en.php | 1 + module/doc/lang/fr.php | 1 + module/doc/lang/zh-cn.php | 1 + 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/lib/zin/wg/docapp/js/v1.js b/lib/zin/wg/docapp/js/v1.js index 61555eab93..55e35a8f54 100644 --- a/lib/zin/wg/docapp/js/v1.js +++ b/lib/zin/wg/docapp/js/v1.js @@ -673,10 +673,24 @@ function renameDocFile(file, doc) }); } -/* Import markdown or json file to editor. */ -function importFileToEditor(type) +async function importContentToEditor(type) { - const accept = type === 'markdown' ? '.md' : '.json'; + const docApp = getDocApp(); + if(!docApp) return; + const editor = docApp.editor.$; + if(!editor) return; + + let content = ''; + if(navigator.clipboard) content = await navigator.clipboard.readText(); + content = await zui.Modal.prompt({size: 'lg', message: `Import ${type}`, defaultValue: content, placeholder: 'Paste content here...', multiline: true}); + return getDocApp().importDoc(content, type); +} + +/* Import markdown or json file to editor. */ +function importFileToEditor(type, options) +{ + if(options && options.fromText) return importContentToEditor(type); + const accept = type === 'markdown' ? '.md' : (type === 'confluence' ? '.html,.txt' : '.json'); return zui.selectFile(accept).then(file => { if(!file) return; zui.readFile(file).then(content => { @@ -981,6 +995,7 @@ const actionsMap = const moreItems = []; if((doc.contentType === 'doc' || doc.contentType === 'markdown') && !isApiDoc(doc)) { + if(config.debug > 5) moreItems.push({text: lang.importConfluence, command: 'importFile/confluence'}); moreItems.push( {text: lang.importMarkdown, command: 'importFile/markdown'}, {text: lang.importJSON, command: 'importFile/json'}, @@ -1365,14 +1380,15 @@ const commands = const url = $.createLink('api', 'export', `libID=${libID}&version=0&release=${release}&moduleID=${moduleID}`); zui.Modal.open({url: url, size: 'sm'}); }, - importFile: function(_, args) + importFile: function(info, args) { const type = args[0] || 'markdown'; const docApp = getDocApp(); const editor = docApp.editor.$; - if(editor.isEmpty) return importFileToEditor(type); + const altKey = info.event.altKey; + if(editor.isEmpty) return importFileToEditor(type, {fromText: altKey}); zui.Modal.confirm({message: getLang('importConfirm')}) - .then((result) => result && importFileToEditor(type)); + .then((result) => result && importFileToEditor(type, {fromText: altKey})); }, showDocSettingModal: function(_, args) { diff --git a/module/doc/lang/de.php b/module/doc/lang/de.php index 8952e5aca5..ed3d6bf2b3 100644 --- a/module/doc/lang/de.php +++ b/module/doc/lang/de.php @@ -510,6 +510,7 @@ $lang->doc->docLang->exportHtml = "Export HTML"; $lang->doc->docLang->exportMarkdown = "Export Markdown"; $lang->doc->docLang->exportJSON = "Export Backup(.json)"; $lang->doc->docLang->importMarkdown = "Import Markdown"; +$lang->doc->docLang->importConfluence = "Import Confluence Storage"; $lang->doc->docLang->importJSON = "Import Backup(.json)"; $lang->doc->docLang->importConfirm = "Import will overwrite the current document content. Are you sure you want to import?"; $lang->doc->docLang->settings = $lang->settings; diff --git a/module/doc/lang/en.php b/module/doc/lang/en.php index 2da5987d88..2867895fce 100644 --- a/module/doc/lang/en.php +++ b/module/doc/lang/en.php @@ -510,6 +510,7 @@ $lang->doc->docLang->exportHtml = "Export HTML"; $lang->doc->docLang->exportMarkdown = "Export Markdown"; $lang->doc->docLang->exportJSON = "Export Backup(.json)"; $lang->doc->docLang->importMarkdown = "Import Markdown"; +$lang->doc->docLang->importConfluence = "Import Confluence Storage"; $lang->doc->docLang->importJSON = "Import Backup(.json)"; $lang->doc->docLang->importConfirm = "Import will overwrite the current document content. Are you sure you want to import?"; $lang->doc->docLang->settings = $lang->settings; diff --git a/module/doc/lang/fr.php b/module/doc/lang/fr.php index 6d04b0fa29..d7ac87c98e 100644 --- a/module/doc/lang/fr.php +++ b/module/doc/lang/fr.php @@ -510,6 +510,7 @@ $lang->doc->docLang->exportHtml = "Export HTML"; $lang->doc->docLang->exportMarkdown = "Export Markdown"; $lang->doc->docLang->exportJSON = "Export Backup(.json)"; $lang->doc->docLang->importMarkdown = "Import Markdown"; +$lang->doc->docLang->importConfluence = "Import Confluence Storage"; $lang->doc->docLang->importJSON = "Import Backup(.json)"; $lang->doc->docLang->importConfirm = "Import will overwrite the current document content. Are you sure you want to import?"; $lang->doc->docLang->settings = $lang->settings; diff --git a/module/doc/lang/zh-cn.php b/module/doc/lang/zh-cn.php index dde0ffc950..c933046703 100644 --- a/module/doc/lang/zh-cn.php +++ b/module/doc/lang/zh-cn.php @@ -511,6 +511,7 @@ $lang->doc->docLang->exportMarkdown = "导出 Markdown"; $lang->doc->docLang->exportJSON = "导出备份(.json)"; $lang->doc->docLang->importMarkdown = "导入 Markdown"; $lang->doc->docLang->importJSON = "导入备份(.json)"; +$lang->doc->docLang->importConfluence = "导入 Confluence 存储格式"; $lang->doc->docLang->importConfirm = "导入将覆盖当前文档内容,确定导入吗?"; $lang->doc->docLang->settings = $lang->settings; $lang->doc->docLang->save = $lang->save; From 88fd0ef9cbb3b6d84bc40c7e6e1d1d7f66e822a8 Mon Sep 17 00:00:00 2001 From: qixinzhi Date: Mon, 3 Mar 2025 01:03:34 +0000 Subject: [PATCH 24/53] * [misc] add hocuspocus config. --- config/config.php | 6 ++++++ lib/zin/wg/docapp/v1.php | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/config/config.php b/config/config.php index 71b38f2268..b03d947b84 100644 --- a/config/config.php +++ b/config/config.php @@ -159,6 +159,12 @@ $config->file->dangers = 'php,php3,php4,phtml,php5,jsp,py,rb,asp,aspx,ashx,a $config->file->allowed = 'txt,doc,docx,dot,wps,wri,pdf,ppt,pptx,xls,xlsx,ett,xlt,xlsm,csv,jpg,jpeg,png,psd,gif,ico,bmp,swf,avi,rmvb,rm,mp3,mp4,3gp,flv,mov,movie,rar,zip,bz,bz2,tar,gz,mpp,rp,pdm,vsdx,vsd,sql,xmind,mm'; $config->file->storageType = 'fs'; // fs or s3 +/* 文档多人协同配置。 Document Hocus Pocus. */ +$config->docHocuspocus = new stdclass(); +$config->docHocuspocus->enable = false; +$config->docHocuspocus->http = 'http://127.0.0.1:1234'; +$config->docHocuspocus->websocket = 'ws://127.0.0.1:1234'; + /* Upload settings. */ $config->allowedTags = '