1580 lines
57 KiB
PHP
1580 lines
57 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
/**
|
|
* The model file of convert module of ZenTaoPMS.
|
|
*
|
|
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
|
|
* @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
|
|
* @author Chunsheng Wang <chunsheng@cnezsoft.com>
|
|
* @package convert
|
|
* @version $Id: model.php 4129 2013-01-18 01:58:14Z wwccss $
|
|
* @link https://www.zentao.net
|
|
*/
|
|
?>
|
|
<?php
|
|
class convertModel extends model
|
|
{
|
|
/**
|
|
* 数据库连接句柄。
|
|
* Database link handle.
|
|
*
|
|
* @var object
|
|
* @access public
|
|
*/
|
|
public $sourceDBH;
|
|
|
|
/**
|
|
* 连接数据库。
|
|
* Connect to db.
|
|
*
|
|
* @param string $dbName
|
|
* @access public
|
|
* @return object|string
|
|
*/
|
|
public function connectDB(string $dbName = ''): object|string
|
|
{
|
|
try
|
|
{
|
|
$params = clone $this->config->db;
|
|
$params->name = $dbName;
|
|
|
|
$dbh = new dbh($params);
|
|
$dbh->exec("SET NAMES {$params->encoding}");
|
|
$dbh->setAttribute(PDO::ATTR_CASE , PDO::CASE_LOWER);
|
|
$dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
|
|
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
|
|
$this->sourceDBH = $dbh;
|
|
|
|
return $dbh;
|
|
}
|
|
catch (PDOException $exception)
|
|
{
|
|
return $exception->getMessage();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检查数据库是否存在。
|
|
* Check database exits or not.
|
|
*
|
|
* @param string $dbName
|
|
* @access public
|
|
* @return object|false
|
|
*/
|
|
public function dbExists(string $dbName = ''): object|false
|
|
{
|
|
if(!$this->checkDBName($dbName)) return false;
|
|
return $this->dbh->dbExists($dbName);
|
|
}
|
|
|
|
/**
|
|
* 检查数据表是否存在。
|
|
* Check table exits or not.
|
|
*
|
|
* @param string $table
|
|
* @access public
|
|
* @return object|false
|
|
*/
|
|
public function tableExists(string $table): object|false
|
|
{
|
|
$sql = "SHOW tables like '$table'";
|
|
return $this->dbh->query($sql)->fetch();
|
|
}
|
|
|
|
/**
|
|
* 检查jira数据库表是否存在。
|
|
* Check table of jira databases exits or not.
|
|
*
|
|
* @param string $dbName
|
|
* @param string $table
|
|
* @access public
|
|
* @return object|false
|
|
*/
|
|
public function tableExistsOfJira(string $dbName, string $table): object|false
|
|
{
|
|
$this->connectDB($dbName);
|
|
$sql = "SHOW tables like '$table'";
|
|
return $this->dao->dbh($this->sourceDBH)->query($sql)->fetch();
|
|
}
|
|
|
|
/**
|
|
* 保存每个表的最大ID。
|
|
* Save the max id of every table. Thus when we convert again, when can delete id larger then the saved max id.
|
|
*
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function saveState(): void
|
|
{
|
|
/* Get user defined tables. */
|
|
$constants = get_defined_constants(true);
|
|
$userConstants = $constants['user'];
|
|
|
|
/* These tables needn't save. */
|
|
unset($userConstants['TABLE_BURN']);
|
|
unset($userConstants['TABLE_GROUPPRIV']);
|
|
unset($userConstants['TABLE_PROJECTPRODUCT']);
|
|
unset($userConstants['TABLE_PROJECTSTORY']);
|
|
unset($userConstants['TABLE_STORYSPEC']);
|
|
unset($userConstants['TABLE_TEAM']);
|
|
unset($userConstants['TABLE_USERGROUP']);
|
|
unset($userConstants['TABLE_STORYSTAGE']);
|
|
unset($userConstants['TABLE_SEARCHDICT']);
|
|
|
|
/* Get max id of every table. */
|
|
foreach($userConstants as $key => $value)
|
|
{
|
|
if(strpos($key, 'TABLE') === false) continue;
|
|
if($key == 'TABLE_COMPANY') continue;
|
|
$state[$value] = (int)$this->dao->select('MAX(id) AS id')->from($value)->fetch('id');
|
|
}
|
|
$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;
|
|
}
|
|
elseif($method == 'file')
|
|
{
|
|
return $this->getJiraDataFromFile($module, $lastID, $limit);
|
|
}
|
|
else
|
|
{
|
|
return $this->getJiraDataFromAPI($module, $lastID, $limit);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 从接口获取jira数据。
|
|
* Get jira data from jiraAPI.
|
|
*
|
|
* @param string $module
|
|
* @param int $lastID
|
|
* @param int $limit
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getJiraDataFromAPI(string $module = '', int $lastID = 0, int $limit = 0): array
|
|
{
|
|
if(empty($_SESSION['jiraApi'])) return array();
|
|
$jiraApi = json_decode($this->session->jiraApi, true);
|
|
if(empty($jiraApi['domain'])) return array();
|
|
|
|
$this->app->loadClass('jira', true);
|
|
|
|
$jiraApi = new jira($jiraApi['domain'], $jiraApi['admin'], $jiraApi['token']);
|
|
|
|
$functionMap = array(
|
|
'issue' => 'getIssues',
|
|
'issuelinktype' => 'getIssueLinkTypes',
|
|
'issuetype' => 'getIssueTypes',
|
|
'user' => 'getUsers',
|
|
'project' => 'getProjects',
|
|
'resolution' => 'getResolutions',
|
|
'priority' => 'getPriority',
|
|
'status' => 'getStatus',
|
|
'customfield' => 'getCustomFields',
|
|
'customfieldoption' => 'getCustomFieldOption',
|
|
'workflow' => 'getWorkflowActions'
|
|
);
|
|
|
|
if(isset($functionMap[$module]))
|
|
{
|
|
$function = $functionMap[$module];
|
|
$dataList = $jiraApi->$function($lastID, $limit);
|
|
}
|
|
else
|
|
{
|
|
return array();
|
|
}
|
|
|
|
if(in_array($module, array_keys($this->config->convert->objectTables)) && $module != 'workflow')
|
|
{
|
|
if($module == 'customfield')
|
|
{
|
|
foreach($dataList as $issueType => $datas)
|
|
{
|
|
foreach($datas as $key => $data)
|
|
{
|
|
$buildfunction = 'build' . ucfirst($module) . 'data';
|
|
$dataList[$issueType][$key] = $this->$buildfunction($data);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach($dataList as $key => $data)
|
|
{
|
|
$buildfunction = 'build' . ucfirst($module) . 'data';
|
|
$dataList[$key] = $this->$buildfunction($data);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $dataList;
|
|
}
|
|
|
|
/**
|
|
* 从数据库获取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($module == 'fixversion' || $module == 'affectsversion')
|
|
{
|
|
$dataList = array();
|
|
}
|
|
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.
|
|
*
|
|
* @param string $module
|
|
* @param int $lastID
|
|
* @param int $limit
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getJiraDataFromFile(string $module, int $lastID = 0, int $limit = 0): array
|
|
{
|
|
$fileName = $module;
|
|
if($module == 'build') $fileName = 'version';
|
|
if($module == 'file') $fileName = 'fileattachment';
|
|
|
|
$filePath = $this->app->getTmpRoot() . 'jirafile/' . $fileName . '.xml';
|
|
if(!file_exists($filePath)) return array();
|
|
|
|
$xmlContent = file_get_contents($filePath);
|
|
$xmlContent = preg_replace ('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $xmlContent);
|
|
|
|
$reader = new XMLReader();
|
|
$dom = new DOMDocument();
|
|
|
|
$reader->XML($xmlContent);
|
|
$reader->read();
|
|
$domNode = $reader->expand($dom);
|
|
$reader->close();
|
|
|
|
$parsedXML = simplexml_import_dom($domNode);
|
|
$parsedXML = $this->object2Array($parsedXML);
|
|
|
|
$dataList = array();
|
|
foreach($parsedXML as $key => $xmlArray)
|
|
{
|
|
if(strtolower($key) != strtolower($fileName)) continue;
|
|
foreach($xmlArray as $key => $attributes)
|
|
{
|
|
if(is_array($attributes) && isset($attributes['status']) && $attributes['status'] == 'deleted') break;
|
|
|
|
if(is_numeric($key))
|
|
{
|
|
$desc = isset($attributes['description']) ? $attributes['description'] : '';
|
|
$summary = isset($attributes['summary']) ? $attributes['summary'] : '';
|
|
$body = isset($attributes['body']) ? $attributes['body'] : '';
|
|
|
|
$data = array();
|
|
$dataID = 0;
|
|
foreach($attributes as $k => $value)
|
|
{
|
|
if(empty($value)) continue;
|
|
if(is_array($value))
|
|
{
|
|
if(isset($value['status']) && $value['status'] == 'deleted') break;
|
|
|
|
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 + 1);
|
|
$data = array_merge($data, $value);
|
|
}
|
|
else
|
|
{
|
|
$data = array_merge($data, array($k => $value));
|
|
}
|
|
}
|
|
if(!empty($dataID)) $dataList[$dataID] = $data;
|
|
}
|
|
else
|
|
{
|
|
if(is_array($attributes))
|
|
{
|
|
if(isset($attributes['status']) && $attributes['status'] == 'deleted') continue;
|
|
|
|
$dataID = !empty($attributes['id']) ? $attributes['id'] : $key;
|
|
$dataList[$dataID] = $attributes;
|
|
}
|
|
else
|
|
{
|
|
if(!empty($dataID)) $dataList[$dataID][$key] = $attributes;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if($limit)
|
|
{
|
|
$dataList = array_slice($dataList, $lastID, $limit, true);
|
|
if(empty($dataList)) return array();
|
|
}
|
|
|
|
if(in_array($module, array_keys($this->config->convert->objectTables)))
|
|
{
|
|
foreach($dataList as $key => $data)
|
|
{
|
|
$buildFunction = 'build' . ucfirst($module) . 'Data';
|
|
$dataList[$key] = $this->$buildFunction($data);
|
|
}
|
|
}
|
|
|
|
return $dataList;
|
|
}
|
|
|
|
/**
|
|
* 分割jira文件。
|
|
* Split jira file.
|
|
*
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function splitFile(): void
|
|
{
|
|
$filePath = $this->app->getTmpRoot() . 'jirafile/';
|
|
$fileName = 'entities.xml';
|
|
$file = $filePath . $fileName;
|
|
$handle = fopen($file, "r");
|
|
|
|
$tagList = array('<Action' => '</Action>', '<Project' => '</Project>', '<Status' => '</Status>', '<Resolution' => '</Resolution>', '<User' => '</User>', '<Issue' => '</Issue>', '<ChangeGroup' => '</ChangeGroup>', '<ChangeItem' => '</ChangeItem>', '<IssueLink' => '</IssueLink>', '<IssueLinkType' => '</IssueLinkType>', '<FileAttachment' => '</FileattAchment>', '<Version' => '</Version>', '<IssueType' => '</IssueType>', '<NodeAssociation' => '</NodeAssociation>', '<ApplicationUser' => '</ApplicationUser>', '<FieldScreenLayoutItem' => '<FieldScreenLayoutItem>', '<Workflow' => '</Workflow>', '<WorkflowScheme' => '</WorkflowScheme>', '<FieldConfigSchemeIssueType' => '</FieldConfigSchemeIssueType>', '<FieldConfigScheme' => '</FieldConfigScheme>', '<CustomField' => '</CustomField>', '<CustomFieldOption' => '</CustomFieldOption>', '<CustomFieldValue' => '</CustomFieldValue>', '<OSPropertyEntry' => '</OSPropertyEntry>', '<Worklog' => '</Worklog>', '<AuditLog' => '</AuditLog>', '<Group' => '</Group>', '<Membership' => '</Membership>', '<ProjectRoleActor' => '</ProjectRoleActor>', '<Priority' => '</Priority>', '<ConfigurationContext' => '</ConfigurationContext>', '<OptionConfiguration' => '</OptionConfiguration>', '<FixVersion' => '</FixVersion>', '<AffectsVersion' => '</AffectsVersion>');
|
|
|
|
while(!feof($handle))
|
|
{
|
|
$itemStr = fgets($handle);
|
|
if(empty($itemStr)) continue;
|
|
foreach($tagList as $startName => $endName)
|
|
{
|
|
$startName .= ' ';
|
|
if(strpos($itemStr, $startName) === false) continue;
|
|
|
|
if(strpos($itemStr, '/>') === false)
|
|
{
|
|
while(true)
|
|
{
|
|
$followItemStr = fgets($handle);
|
|
$itemStr .= $followItemStr;
|
|
if(strpos($itemStr, $endName) !== false) break;
|
|
}
|
|
}
|
|
|
|
$object = str_replace('<', '', $startName);
|
|
$object = trim(strtolower($object));
|
|
$data = preg_replace ('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $itemStr);
|
|
if(!file_exists($filePath . $object . '.xml')) $data = "<?xml version='1.0' encoding='UTF-8'?>\n <entity-engine-xml>\n" . $data;
|
|
file_put_contents($filePath . $object . '.xml', $data, FILE_APPEND);
|
|
}
|
|
}
|
|
|
|
foreach($tagList as $startName => $endName)
|
|
{
|
|
$object = str_replace('<', '', $startName);
|
|
$object = strtolower($object);
|
|
$filename = $filePath . $object . '.xml';
|
|
if(file_exists($filename)) file_put_contents($filename, '</entity-engine-xml>', FILE_APPEND);
|
|
}
|
|
|
|
fclose($handle);
|
|
}
|
|
|
|
/**
|
|
* 创建jira数据表。
|
|
* Create tmp table for import jira.
|
|
*
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function createTmpTable4Jira(): void
|
|
{
|
|
$sql = <<<EOT
|
|
CREATE TABLE `jiratmprelation`(
|
|
`id` int(8) NOT NULL AUTO_INCREMENT,
|
|
`AType` char(30) NOT NULL,
|
|
`AID` char(100) NOT NULL,
|
|
`BType` char(30) NOT NULL,
|
|
`BID` char(100) NOT NULL,
|
|
`extra` char(100) NULL,
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `relation` (`AType`,`BType`,`AID`,`BID`)
|
|
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
|
EOT;
|
|
|
|
try
|
|
{
|
|
$this->dbh->exec($sql);
|
|
}
|
|
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 == 'changeitem') $this->convertTao->importJiraChangeItem($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);
|
|
}
|
|
}
|
|
|
|
if($this->session->jiraMethod == 'file') $this->deleteJiraFile();
|
|
|
|
/* 更新各项目的统计数据。 */
|
|
$projectList = $this->dao->dbh($this->dbh)->select('BID')->from(JIRA_TMPRELATION)->where('BType')->in('zproject,zexecution')->fetchPairs();
|
|
$this->loadModel('program')->updateStats($projectList);
|
|
|
|
unset($_SESSION['jiraDB']);
|
|
unset($_SESSION['jiraMethod']);
|
|
unset($_SESSION['jiraRelation']);
|
|
unset($_SESSION['stepStatus']);
|
|
unset($_SESSION['jiraUser']);
|
|
return array('finished' => true);
|
|
}
|
|
|
|
/**
|
|
* 删除jira文件。
|
|
* Delete jira backip file.
|
|
*
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function deleteJiraFile(): void
|
|
{
|
|
$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', 'fixversion', 'affectsversion');
|
|
foreach($fileList as $fileName)
|
|
{
|
|
$filePath = $this->app->getTmpRoot() . 'jirafile/' . $fileName . '.xml';
|
|
if(file_exists($filePath)) @unlink($filePath);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检查数据库名称。
|
|
* Check dbName is valide.
|
|
*
|
|
* @param string $dbName
|
|
* @access public
|
|
* @return bool
|
|
*/
|
|
public function checkDBName(string $dbName): bool
|
|
{
|
|
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 = $this->lang->convert->jira->zentaoObjectList;
|
|
if(!$this->config->enableER) unset($objectList['epic']);
|
|
if(!$this->config->URAndSR) unset($objectList['requirement']);
|
|
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;
|
|
}
|
|
}
|
|
|
|
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
|
|
{
|
|
return $this->getJiraData($this->session->jiraMethod, 'issuetype');
|
|
}
|
|
|
|
/**
|
|
* 获取Jira状态列表。
|
|
* Get jira status list.
|
|
*
|
|
* @param string $step
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getJiraStatusList($step = ''): array
|
|
{
|
|
if($this->session->jiraMethod == 'api')
|
|
{
|
|
$jql = 'created<=' . date('Y-m-d', strtotime('+1 day'));
|
|
if($step) $jql .= " AND issuetype = {$step}";
|
|
$issues = $this->callJiraAPI('/rest/api/3/search/jql?jql=' . urlencode($jql) . '&fields=status,issuetype&maxResults=5000');
|
|
|
|
foreach($issues as $issue)
|
|
{
|
|
if(!empty($issue->fields))
|
|
{
|
|
foreach($issue->fields as $field => $value) $issue->$field = $value;
|
|
}
|
|
if(!empty($issue->status->id)) $issue->issuestatus = $issue->status->id;
|
|
if(!empty($issue->issuetype->id)) $issue->issuetype = $issue->issuetype->id;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$issues = $this->getJiraData($this->session->jiraMethod, 'issue');
|
|
}
|
|
$statusList = $this->getJiraData($this->session->jiraMethod, 'status');
|
|
|
|
$jiraStatusList = array();
|
|
foreach($issues as $issue)
|
|
{
|
|
if(empty($issue->issuestatus)) continue;
|
|
if(empty($statusList[$issue->issuestatus])) continue;
|
|
|
|
$status = $statusList[$issue->issuestatus];
|
|
$jiraStatusList[$issue->issuetype][$issue->issuestatus] = $status->pname;
|
|
}
|
|
return $step ? zget($jiraStatusList, $step, array()) : $jiraStatusList;
|
|
}
|
|
|
|
/**
|
|
* 获取Jira自定义字段列表。
|
|
* Get jira custom fields.
|
|
*
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getJiraCustomField(): array
|
|
{
|
|
if($this->config->edition == 'open') return array();
|
|
|
|
$jiraFields = array();
|
|
$fields = $this->getJiraData($this->session->jiraMethod, 'customfield');
|
|
if($this->session->jiraMethod == 'api')
|
|
{
|
|
foreach($fields as $issueType => $fieldList)
|
|
{
|
|
foreach($fieldList as $fieldID => $field) $fields[$issueType][$fieldID] = $field->cfname;
|
|
}
|
|
return $fields;
|
|
}
|
|
|
|
$issues = $this->getJiraData($this->session->jiraMethod, 'issue');
|
|
$fieldValue = $this->getJiraData($this->session->jiraMethod, 'customfieldvalue');
|
|
|
|
foreach($fieldValue as $value)
|
|
{
|
|
if(empty($issues[$value->issue]) || empty($fields[$value->customfield])) continue;
|
|
|
|
$issue = $issues[$value->issue];
|
|
$field = $fields[$value->customfield];
|
|
|
|
if(in_array($field->customfieldtypekey, array('com.pyxis.greenhopper.jira:gh-sprint', 'com.pyxis.greenhopper.jira:gh-epic-label', 'com.pyxis.greenhopper.jira:gh-epic-status', 'com.pyxis.greenhopper.jira:gh-epic-color'))) continue;
|
|
$jiraFields[$issue->issuetype][$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');
|
|
if($this->session->jiraMethod == 'api')
|
|
{
|
|
$projectList = $this->callJiraAPI('/rest/api/3/issue/createmeta?expand=projects.issuetypes.fields&maxResults=1000');
|
|
$jiraFields = array();
|
|
foreach($projectList as $project)
|
|
{
|
|
if(empty($project->issuetypes)) continue;
|
|
foreach($project->issuetypes as $issueType)
|
|
{
|
|
if(empty($issueType->fields)) continue;
|
|
|
|
$zentaoObject = $relations['zentaoObject'][$issueType->id];
|
|
foreach($issueType->fields as $key => $field)
|
|
{
|
|
if(strpos($key, 'customfield_') === false) continue;
|
|
|
|
$fieldKey = str_replace('customfield_', '', $key);
|
|
if(empty($fieldList[$fieldKey])) continue;
|
|
|
|
$zentaoField = $fieldList[$fieldKey][$zentaoObject];
|
|
$jiraFields[$project->id][$zentaoObject][$zentaoField->field] = $zentaoField;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $jiraFields;
|
|
}
|
|
|
|
$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();
|
|
|
|
if($this->session->jiraMethod == 'api')
|
|
{
|
|
$schemeList = $this->callJiraAPI('/rest/api/3/issuetypescheme?expand=projects,issuetypes&maxResults=1000');
|
|
$workflowList = $this->callJiraAPI('/rest/api/3/workflow/search?expand=transitions,projects&maxResults=1000');
|
|
$projectGroup = array();
|
|
foreach($schemeList as $scheme)
|
|
{
|
|
if(empty($scheme->issueTypes->values) || empty($scheme->projects->values)) continue;
|
|
foreach($scheme->issueTypes->values as $issueType)
|
|
{
|
|
foreach($scheme->projects->values as $project) $projectGroup[$issueType->id][$project->id] = $project->id;
|
|
}
|
|
}
|
|
|
|
$workflowActions = array();
|
|
foreach($workflowList as $workflow)
|
|
{
|
|
if(empty($workflow->transitions) || empty($workflow->projects)) continue;
|
|
foreach($workflow->projects as $project)
|
|
{
|
|
foreach($projectGroup as $issueType => $projects)
|
|
{
|
|
if(!in_array($project->id, $projects)) continue;
|
|
foreach($workflow->transitions as $action) $workflowActions[$issueType]['actions'][$action->id] = (array)$action;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $workflowActions;
|
|
}
|
|
|
|
$workflows = $this->getJiraData($this->session->jiraMethod, 'workflow');
|
|
|
|
$workflowActions = array();
|
|
$actionNameList = 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));
|
|
if(empty($actionNameList[$actionInfo['name']]) && strpos($actionInfo['name'], 'Issue') === false) $workflowActions['actions'][] = $actionInfo;
|
|
$actionNameList[$actionInfo['name']] = $actionInfo['name'];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$actionInfo = array_merge($actionInfo, $key == '@attributes' ? $actionList : array($key => $actionList));
|
|
if(empty($actionNameList[$actionInfo['name']]) && strpos($actionInfo['name'], 'Issue') === false) $workflowActions['actions'][] = $actionInfo;
|
|
$actionNameList[$actionInfo['name']] = $actionInfo['name'];
|
|
}
|
|
}
|
|
}
|
|
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 api接口。
|
|
* Call Jira API.
|
|
*
|
|
* @param string $url
|
|
* @param int $start
|
|
* @param bool $rawData
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function callJiraAPI($url, $start = 0, $rawData = false)
|
|
{
|
|
if(empty($_SESSION['jiraApi'])) return array();
|
|
$jiraApi = json_decode($this->session->jiraApi, true);
|
|
if(empty($jiraApi['domain'])) return array();
|
|
|
|
if(strpos($url, $jiraApi['domain']) === 0) $url = str_replace($jiraApi['domain'], '', $url);
|
|
|
|
$token = base64_encode("{$jiraApi['admin']}:{$jiraApi['token']}");
|
|
$httpURL = $jiraApi['domain'] . $url . "&startAt=$start";
|
|
$result = json_decode(commonModel::http($httpURL, array(), array(), array("Authorization: Basic $token"), 'json', 'GET', 10));
|
|
|
|
if(!empty($result->values) || !empty($result->issues))
|
|
{
|
|
$dataList = !empty($result->values) ? $result->values : $result->issues;
|
|
if(empty($result->isLast))
|
|
{
|
|
if(!empty($result->nextPageToken))
|
|
{
|
|
$dataList = array_merge($dataList, $this->callJiraAPI($url . "&nextPageToken={$result->nextPageToken}"));
|
|
}
|
|
else
|
|
{
|
|
$dataList = array_merge($dataList, $this->callJiraAPI($url, $start + $result->maxResults));
|
|
}
|
|
}
|
|
|
|
return $dataList;
|
|
}
|
|
|
|
if(!empty($result->projects)) return $result->projects;
|
|
|
|
return $rawData ? $result : array();
|
|
}
|
|
|
|
/**
|
|
* 获取jira用户。
|
|
* Get jira account.
|
|
*
|
|
* @param string $userKey
|
|
* @access public
|
|
* @return string
|
|
*/
|
|
public function getJiraAccount(string $userKey): string
|
|
{
|
|
if(empty($userKey)) return '';
|
|
|
|
$users = $this->getJiraUser();
|
|
return zget($users, $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 preg_replace("/[^a-zA-Z0-9]/", "", $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;
|
|
|
|
$data = new stdclass();
|
|
$data->versionid = $value['sinkNodeId'];
|
|
$data->issueid = $value['sourceNodeId'];
|
|
$data->relation = $value['associationType'];
|
|
$dataList[$value['sinkNodeId']][] = $data;
|
|
}
|
|
}
|
|
}
|
|
|
|
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) && empty($value))
|
|
{
|
|
$parsedXML[$key] = (string)$value;
|
|
continue;
|
|
}
|
|
if(!is_object($value) && !is_array($value)) continue;
|
|
$parsedXML[$key] = $this->object2Array($value);
|
|
}
|
|
}
|
|
|
|
return $parsedXML;
|
|
}
|
|
|
|
/**
|
|
* 转换需求阶段。
|
|
* Convert stage.
|
|
*
|
|
* @param string $jiraStatus
|
|
* @param string $issueType
|
|
* @param array $relations
|
|
* @access public
|
|
* @return string
|
|
*/
|
|
public function convertStage(string $jiraStatus, string $issueType, array $relations = array()): string
|
|
{
|
|
if(empty($relations))
|
|
{
|
|
$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
|
|
* @param array $relations
|
|
* @access public
|
|
* @return string
|
|
*/
|
|
public function convertStatus(string $objectType, string $jiraStatus, string $issueType, array $relations = array()): string
|
|
{
|
|
if(empty($relations))
|
|
{
|
|
$jiraRelation = $this->session->jiraRelation;
|
|
$relations = $jiraRelation ? json_decode($jiraRelation, true) : array();
|
|
}
|
|
|
|
if(!empty($relations["zentaoStatus{$issueType}"][$jiraStatus])) return (string)$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' || $this->session->jiraMethod == 'api')
|
|
{
|
|
foreach($projectList as $projectID)
|
|
{
|
|
$boardList = $this->callJiraAPI("/rest/agile/1.0/board?projectKeyOrId={$projectID}&maxResults=1000");
|
|
foreach($boardList as $board)
|
|
{
|
|
$sprintList = $this->callJiraAPI("/rest/agile/1.0/board/$board->id/sprint?maxResults=1000");
|
|
foreach($sprintList 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 != 'db')
|
|
{
|
|
foreach($sprintRelation as $sprintID => $executionID)
|
|
{
|
|
$issueList = $this->callJiraAPI("/rest/agile/1.0/sprint/{$sprintID}/issue?maxResults=1000");
|
|
foreach($issueList as $issue)
|
|
{
|
|
$issueGroup[$issue->id] = $executionID;
|
|
if(!empty($issue->fields->subtasks))
|
|
{
|
|
foreach($issue->fields->subtasks as $subtask) $issueGroup[$subtask->id] = $executionID;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $issueGroup;
|
|
}
|
|
|
|
/**
|
|
* 获取Jira已归档的项目。
|
|
* Get jira archived project.
|
|
*
|
|
* @param array $dataList
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getJiraArchivedProject($dataList): array
|
|
{
|
|
if($this->session->jiraMethod == 'api') return 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) && empty($project->archived)) $projectList[$project->id] = $project->id; // 没有被归档的项目。
|
|
}
|
|
}
|
|
|
|
/* 过滤掉没被归档的项目,剩下的都是被归档的项目。 */
|
|
foreach($dataList as $project)
|
|
{
|
|
if(empty($projectList[$project->id])) $archivedProject[$project->id] = $project->id;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$sql = "SHOW tables like 'ao_c77861_audit_entity';";
|
|
if($this->dao->dbh($this->sourceDBH)->query($sql)->fetch())
|
|
{
|
|
$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.
|
|
*
|
|
* @param array $projectList
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getJiraProjectRoleActor(array $projectList): array
|
|
{
|
|
if($this->session->jiraMethod == 'api')
|
|
{
|
|
$projectMember = array();
|
|
foreach($projectList as $project)
|
|
{
|
|
$projectRoles = $this->callJiraAPI("/rest/api/2/project/{$project->id}/role?maxResults=1000", 0, true);
|
|
foreach($projectRoles as $role)
|
|
{
|
|
if(!is_string($role)) continue;
|
|
|
|
$users = $this->callJiraAPI($role . '?maxResults=1000', 0 , true);
|
|
if(empty($users->actors)) continue;
|
|
|
|
foreach($users->actors as $user) $projectMember[$project->id][$user->actorUser->accountId] = $user->actorUser->accountId;
|
|
}
|
|
}
|
|
return $projectMember;
|
|
}
|
|
$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
|
|
{
|
|
if($this->session->jiraMethod == 'api')
|
|
{
|
|
$projectList = $this->callJiraAPI('/rest/api/3/issue/createmeta?expand=projects.issuetypes&maxResults=1000');
|
|
$projectGroup = array();
|
|
foreach($projectList as $project)
|
|
{
|
|
if(empty($project->issuetypes)) continue;
|
|
foreach($project->issuetypes as $issueType)
|
|
{
|
|
$projectGroup[$project->id][] = $relations['zentaoObject'][$issueType->id];
|
|
}
|
|
}
|
|
|
|
return $projectGroup;
|
|
}
|
|
|
|
$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;
|
|
}
|
|
|
|
/**
|
|
* 快速导入jira数据。
|
|
* Quick import Jira data.
|
|
*
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function quickImportJiraData()
|
|
{
|
|
$jiraApi = array();
|
|
$jiraApi['domain'] = $this->post->domain;
|
|
$jiraApi['admin'] = $this->post->account;
|
|
$jiraApi['token'] = $this->post->token;
|
|
|
|
$this->session->set('jiraApi', json_encode($jiraApi));
|
|
$this->session->set('jiraDB', '');
|
|
$this->session->set('jiraMethod', 'api');
|
|
|
|
$this->checkJiraApi();
|
|
if(dao::isError()) return false;
|
|
|
|
$issueTypeList = $this->getJiraTypeList();
|
|
$defaultValue = $this->getObjectDefaultValue('object');
|
|
$jiraFields = $this->getJiraCustomField();
|
|
$jiraStatus = $this->getJiraStatusList();
|
|
$jiraActions = $this->getJiraWorkflowActions();
|
|
$jiraResolution = $this->getJiraData('api', 'resolution');
|
|
$jiraLinkTypes = $this->getJiraData('api', 'issuelinktype');
|
|
|
|
$jiraRelation = array();
|
|
foreach($issueTypeList as $issueID => $issueType)
|
|
{
|
|
$jiraRelation['jiraObject'][] = $issueID;
|
|
$jiraRelation['zentaoObject'][$issueID] = !empty($defaultValue['zentaoObject'][$issueType->pname]) ? $defaultValue['zentaoObject'][$issueType->pname] : 'add_custom';
|
|
|
|
if($jiraRelation['zentaoObject'][$issueID] == 'add_custom') continue;
|
|
|
|
if(!empty($jiraFields[$issueID]))
|
|
{
|
|
foreach($jiraFields[$issueID] as $fieldID => $field)
|
|
{
|
|
$jiraRelation["jiraField{$issueID}"][] = $fieldID;
|
|
$jiraRelation["zentaoField{$issueID}"][$fieldID] = 'add_field';
|
|
}
|
|
}
|
|
|
|
if(!empty($jiraStatus[$issueID]))
|
|
{
|
|
foreach($jiraStatus[$issueID] as $statusID => $status)
|
|
{
|
|
$jiraRelation["jiraStatus{$issueID}"][] = $statusID;
|
|
$jiraRelation["zentaoStatus{$issueID}"][$statusID] = !empty($this->config->convert->importDeafaultValue[$jiraRelation['zentaoObject'][$issueID]]['status'][$status]) ? $this->config->convert->importDeafaultValue[$jiraRelation['zentaoObject'][$issueID]]['status'][$status] : '';
|
|
if(in_array($jiraRelation['zentaoObject'][$issueID], array('requirement', 'story', 'epic'))) $jiraRelation["zentaoStage{$issueID}"][$statusID] = '';
|
|
}
|
|
}
|
|
|
|
if(!empty($jiraActions[$issueID]))
|
|
{
|
|
foreach($jiraActions[$issueID]['actions'] as $actionID => $action)
|
|
{
|
|
$jiraRelation["jiraAction{$issueID}"][] = $actionID;
|
|
$jiraRelation["zentaoAction{$issueID}"][$actionID] = !empty($this->config->convert->importDeafaultValue[$jiraRelation['zentaoObject'][$issueID]]['action'][$action['name']]) ? $this->config->convert->importDeafaultValue[$jiraRelation['zentaoObject'][$issueID]]['action'][$action['name']] : 'add_action';
|
|
}
|
|
}
|
|
|
|
if(!empty($jiraResolution))
|
|
{
|
|
foreach($jiraResolution as $resolutionID => $resolution)
|
|
{
|
|
$jiraRelation["jiraResolution{$issueID}"][] = $resolutionID;
|
|
if($jiraRelation['zentaoObject'][$issueID] == 'bug')
|
|
{
|
|
$jiraRelation["zentaoResolution{$issueID}"][$resolutionID] = !empty($this->config->convert->importDeafaultValue[$jiraRelation['zentaoObject'][$issueID]]['resolution'][$resolution->pname]) ? $this->config->convert->importDeafaultValue[$jiraRelation['zentaoObject'][$issueID]]['resolution'][$resolution->pname] : '';
|
|
}
|
|
else
|
|
{
|
|
$jiraRelation["zentaoReason{$issueID}"][$resolutionID] = !empty($this->config->convert->importDeafaultValue[$jiraRelation['zentaoObject'][$issueID]]['reason'][$resolution->pname]) ? $this->config->convert->importDeafaultValue[$jiraRelation['zentaoObject'][$issueID]]['reason'][$resolution->pname] : '';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!empty($jiraLinkTypes))
|
|
{
|
|
foreach($jiraLinkTypes as $linkID => $linkType)
|
|
{
|
|
$jiraRelation['jiraLinkType'][] = $linkID;
|
|
$jiraRelation['zentaoLinkType'][$linkID] = 'add_relation';
|
|
}
|
|
}
|
|
|
|
$jiraUser['password'] = md5('123456');
|
|
$jiraUser['group'] = '';
|
|
$jiraUser['mode'] = 'email';
|
|
$this->session->set('jiraUser', $jiraUser);
|
|
$this->session->set('jiraRelation', json_encode($jiraRelation));
|
|
|
|
$this->batchImportJiraData();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 分批次的导入Jira数据。
|
|
* Batch import Jira data.
|
|
*
|
|
* @param string $type
|
|
* @param int $lastID
|
|
* @param bool $createTable
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function batchImportJiraData(string $type = '', int $lastID = 0, bool $createTable = true): bool
|
|
{
|
|
$result = $this->importJiraData($type, $lastID, $createTable);
|
|
if(!empty($result['finished'])) return true;
|
|
|
|
return $this->batchImportJiraData($result['type'], $result['lastID'], false);
|
|
}
|
|
|
|
/**
|
|
* 将日期转成对应时区的日期。
|
|
* Format date.
|
|
*
|
|
* @param string $date
|
|
* @access public
|
|
* @return string
|
|
*/
|
|
public function formatDate(string $date): string
|
|
{
|
|
if($this->session->jiraMethod == 'api') return $date;
|
|
|
|
$dateUTC = new DateTime($date, new DateTimeZone('UTC'));
|
|
$dateUTC->setTimezone(new DateTimeZone($this->config->timezone));
|
|
return $dateUTC->format('Y-m-d');
|
|
}
|
|
|
|
/**
|
|
* 将日期转成对应时区的日期。
|
|
* Format date.
|
|
*
|
|
* @param string $date
|
|
* @access public
|
|
* @return string
|
|
*/
|
|
public function formatDatetime(string $datetime): string
|
|
{
|
|
if($this->session->jiraMethod == 'api') return $datetime;
|
|
|
|
$dateUTC = new DateTime($datetime, new DateTimeZone('UTC'));
|
|
$dateUTC->setTimezone(new DateTimeZone($this->config->timezone));
|
|
return $dateUTC->format('Y-m-d H:i:s');
|
|
}
|
|
}
|