1522 lines
56 KiB
PHP
1522 lines
56 KiB
PHP
<?php
|
|
/**
|
|
* The model file of mr module of ZenTaoPMS.
|
|
*
|
|
* @copyright Copyright 2009-2021 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
|
|
* @license ZPL (http://zpl.pub/page/zplv12.html)
|
|
* @author dingguodong <dingguodong@easycorp.ltd>
|
|
* @package mr
|
|
* @version $Id$
|
|
* @link http://www.zentao.net
|
|
*/
|
|
class mrModel extends model
|
|
{
|
|
/**
|
|
* The construct method, to do some auto things.
|
|
*
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
$this->loadModel('gitlab');
|
|
}
|
|
|
|
/**
|
|
* Get a MR by id.
|
|
*
|
|
* @param int $id
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function getByID($id)
|
|
{
|
|
return $this->dao->findByID($id)->from(TABLE_MR)->fetch();
|
|
}
|
|
|
|
/**
|
|
* Get MR list of gitlab project.
|
|
*
|
|
* @param string $mode
|
|
* @param string $param
|
|
* @param string $orderBy
|
|
* @param object $pager
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getList($mode = 'all', $param = 'all', $orderBy = 'id_desc', $pager = null)
|
|
{
|
|
$MRList = $this->dao->select('*')
|
|
->from(TABLE_MR)
|
|
->where('deleted')->eq('0')
|
|
->beginIF($mode == 'status' and $param != 'all')->andWhere('status')->eq($param)->fi()
|
|
->beginIF($mode == 'assignee' and $param != 'all')->andWhere('assignee')->eq($param)->fi()
|
|
->beginIF($mode == 'creator' and $param != 'all')->andWhere('createdBy')->eq($param)->fi()
|
|
->orderBy($orderBy)
|
|
->page($pager)
|
|
->fetchAll('id');
|
|
|
|
return $MRList;
|
|
}
|
|
|
|
/**
|
|
* Get gitlab pairs.
|
|
*
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getPairs($repoID)
|
|
{
|
|
$MR = $this->dao->select('id,title')
|
|
->from(TABLE_MR)
|
|
->where('deleted')->eq('0')
|
|
->andWhere('repoID')->eq($repoID)
|
|
->orderBy('id')->fetchPairs('id', 'title');
|
|
return array('' => '') + $MR;
|
|
}
|
|
|
|
/**
|
|
* Create MR function.
|
|
*
|
|
* @access public
|
|
* @return int|bool|object
|
|
*/
|
|
public function create()
|
|
{
|
|
$MR = fixer::input('post')
|
|
->setDefault('jobID', 0)
|
|
->setDefault('repoID', 0)
|
|
->setDefault('removeSourceBranch','0')
|
|
->setDefault('needCI', 0)
|
|
->add('createdBy', $this->app->user->account)
|
|
->add('createdDate', helper::now())
|
|
->get();
|
|
|
|
$result = $this->checkSameOpened($MR->gitlabID, $MR->sourceProject, $MR->sourceBranch, $MR->targetProject, $MR->targetBranch);
|
|
if($result['result'] == 'fail') return $result;
|
|
|
|
/* Exec Job */
|
|
if(isset($MR->jobID) && $MR->jobID)
|
|
{
|
|
$pipeline = $this->loadModel('job')->exec($MR->jobID);
|
|
if(!empty($pipeline->queue))
|
|
{
|
|
$compile = $this->loadModel('compile')->getByQueue($pipeline->queue);
|
|
$MR->compileID = $compile->id;
|
|
$MR->compileStatus = $compile->status;
|
|
}
|
|
}
|
|
|
|
$this->dao->insert(TABLE_MR)->data($MR, $this->config->mr->create->skippedFields)
|
|
->batchCheck($this->config->mr->create->requiredFields, 'notempty')
|
|
->checkIF($MR->needCI, 'jobID', 'notempty')
|
|
->autoCheck()
|
|
->exec();
|
|
if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError());
|
|
|
|
$MRID = $this->dao->lastInsertId();
|
|
$this->loadModel('action')->create('mr', $MRID, 'opened');
|
|
|
|
$MRObject = new stdclass;
|
|
$MRObject->target_project_id = $MR->targetProject;
|
|
$MRObject->source_branch = $MR->sourceBranch;
|
|
$MRObject->target_branch = $MR->targetBranch;
|
|
$MRObject->title = $MR->title;
|
|
$MRObject->description = $MR->description;
|
|
$MRObject->remove_source_branch = $MR->removeSourceBranch == '1' ? true : false;
|
|
if($MR->assignee)
|
|
{
|
|
$gitlabAssignee = $this->gitlab->getUserIDByZentaoAccount($this->post->gitlabID, $MR->assignee);
|
|
if($gitlabAssignee) $MRObject->assignee_ids = $gitlabAssignee;
|
|
}
|
|
|
|
$rawMR = $this->apiCreateMR($this->post->gitlabID, $this->post->sourceProject, $MRObject);
|
|
|
|
/**
|
|
* Another open merge request already exists for this source branch.
|
|
* The type of variable `$rawMR->message` is array.
|
|
*/
|
|
if(isset($rawMR->message) and !isset($rawMR->iid))
|
|
{
|
|
$this->dao->delete()->from(TABLE_MR)->where('id')->eq($MRID)->exec();
|
|
|
|
$errorMessage = $this->convertApiError($rawMR->message);
|
|
return array('result' => 'fail', 'message' => sprintf($this->lang->mr->apiError->createMR, $errorMessage));
|
|
}
|
|
|
|
/* Create MR failed. */
|
|
if(!isset($rawMR->iid))
|
|
{
|
|
$this->dao->delete()->from(TABLE_MR)->where('id')->eq($MRID)->exec();
|
|
return array('result' => 'fail', 'message' => $this->lang->mr->createFailedFromAPI);
|
|
}
|
|
|
|
/* Create a todo item for this MR. */
|
|
if(empty($MR->jobID)) $this->apiCreateMRTodo($this->post->gitlabID, $this->post->targetProject, $rawMR->iid);
|
|
|
|
$newMR = new stdclass;
|
|
$newMR->mriid = $rawMR->iid;
|
|
$newMR->status = $rawMR->state;
|
|
$newMR->mergeStatus = $rawMR->merge_status;
|
|
|
|
/* Update MR in Zentao database. */
|
|
$this->dao->update(TABLE_MR)->data($newMR)->where('id')->eq($MRID)->autoCheck()->exec();
|
|
if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError());
|
|
return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => helper::createLink('mr', 'browse'));
|
|
}
|
|
|
|
/**
|
|
* Create MR function by api.
|
|
*
|
|
* @access public
|
|
* @return int|bool|object
|
|
*/
|
|
public function apiCreate()
|
|
{
|
|
$postData = fixer::input('post')->get();
|
|
$postData = (object)$postData->data;
|
|
|
|
$repo = $this->loadModel('repo')->getRepoByUrl($postData->RepoUrl);
|
|
if(empty($repo['data']))
|
|
{
|
|
dao::$errors[] = $repo['message'];
|
|
return false;
|
|
}
|
|
$repo = $repo['data'];
|
|
|
|
/* Process and insert mr data. */
|
|
$MR = new stdClass();
|
|
$MR->gitlabID = $repo->client;
|
|
$MR->sourceProject = $repo->path;
|
|
$MR->sourceBranch = $postData->RepoSrcBranch;
|
|
$MR->targetProject = $repo->path;
|
|
$MR->targetBranch = $postData->RepoDistBranch;
|
|
$MR->diffs = $this->post->data['DiffMsg'];
|
|
$MR->title = $this->lang->mr->common . ' ' . $postData->RepoSrcBranch . $this->lang->mr->to . $postData->RepoDistBranch ;
|
|
$MR->repoID = $repo->id;
|
|
$MR->jobID = isset($repo->job->id) ? $repo->job->id : 0;
|
|
$MR->status = 'opened';
|
|
$MR->synced = '0';
|
|
$MR->needCI = '1';
|
|
$MR->hasNoConflict = $postData->MergeStatus ? '0' : '1';
|
|
$MR->mergeStatus = $postData->MergeStatus ? 'can_be_merged' : 'cannot_be_merged';
|
|
$MR->createdBy = $this->app->user->account;
|
|
$MR->createdDate = date('Y-m-d H:i:s');
|
|
|
|
$result = $this->checkSameOpened($MR->gitlabID, $MR->sourceProject, $MR->sourceBranch, $MR->targetProject, $MR->targetBranch);
|
|
if($result['result'] == 'fail')
|
|
{
|
|
dao::$errors[] = $result['message'];
|
|
return false;
|
|
}
|
|
|
|
$this->dao->insert(TABLE_MR)->data($MR, $this->config->mr->create->skippedFields)
|
|
->batchCheck($this->config->mr->apicreate->requiredFields, 'notempty')
|
|
->autoCheck()
|
|
->exec();
|
|
if(dao::isError()) return false;
|
|
|
|
$MRID = $this->dao->lastInsertId();
|
|
$this->loadModel('action')->create('mr', $MRID, 'opened');
|
|
|
|
/* Exec Job */
|
|
if($MR->hasNoConflict == '0' && $MR->mergeStatus == 'can_be_merged' && $MR->jobID)
|
|
{
|
|
$extraParam = array();
|
|
if(!empty($repo->fileServerUrl)) $extraParam = array('ZENTAO_REPOPATH' => $repo->fileServerUrl);
|
|
|
|
$pipeline = $this->loadModel('job')->exec($MR->jobID, $extraParam);
|
|
$newMR = new stdClass();
|
|
if(!empty($pipeline->queue))
|
|
{
|
|
$compile = $this->loadModel('compile')->getByQueue($pipeline->queue);
|
|
$newMR->compileID = $compile->id;
|
|
$newMR->compileStatus = $compile->status;
|
|
if($newMR->compileStatus == 'failure') $newMR->status = 'closed';
|
|
}
|
|
else
|
|
{
|
|
$newMR->compileStatus = $pipeline->status;
|
|
if($newMR->compileStatus == 'create_fail') $newMR->status = 'closed';
|
|
}
|
|
|
|
/* Update MR in Zentao database. */
|
|
$this->dao->update(TABLE_MR)->data($newMR)->where('id')->eq($MRID)->autoCheck()->exec();
|
|
if(dao::isError()) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Edit MR function.
|
|
*
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function update($MRID)
|
|
{
|
|
$MR = fixer::input('post')
|
|
->setDefault('jobID', 0)
|
|
->setDefault('repoID', 0)
|
|
->setDefault('removeSourceBranch','0')
|
|
->setDefault('needCI', 0)
|
|
->setDefault('editedBy', $this->app->user->account)
|
|
->setDefault('editedDate', helper::now())
|
|
->setIF($this->post->needCI == 0, 'jobID', 0)
|
|
->get();
|
|
$oldMR = $this->getByID($MRID);
|
|
|
|
$this->dao->update(TABLE_MR)->data($MR)->checkIF($MR->needCI, 'jobID', 'notempty');
|
|
if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError());
|
|
|
|
/* Exec Job */
|
|
if(isset($MR->jobID) && $MR->jobID)
|
|
{
|
|
$pipeline = $this->loadModel('job')->exec($MR->jobID);
|
|
|
|
if(!empty($pipeline->queue) && $MR->jobID != $oldMR->jobID)
|
|
{
|
|
$compile = $this->loadModel('compile')->getByQueue($pipeline->queue);
|
|
$MR->compileID = $compile->id;
|
|
$MR->compileStatus = $compile->status;
|
|
}
|
|
}
|
|
|
|
/* Update MR in GitLab. */
|
|
$newMR = new stdclass;
|
|
$newMR->title = $MR->title;
|
|
$newMR->description = $MR->description;
|
|
$newMR->target_branch = $MR->targetBranch;
|
|
$newMR->remove_source_branch = $MR->removeSourceBranch == '1' ? true : false;
|
|
if($MR->assignee)
|
|
{
|
|
$gitlabAssignee = $this->gitlab->getUserIDByZentaoAccount($oldMR->gitlabID, $MR->assignee);
|
|
if($gitlabAssignee) $newMR->assignee_ids = $gitlabAssignee;
|
|
}
|
|
|
|
/* Known issue: `reviewer_ids` takes no effect. */
|
|
$rawMR = $this->apiUpdateMR($oldMR->gitlabID, $oldMR->targetProject, $oldMR->mriid, $newMR);
|
|
if(!isset($rawMR->id) and isset($rawMR->message))
|
|
{
|
|
$errorMessage = $this->convertApiError($rawMR->message);
|
|
return array('result' => 'fail', 'message' => $errorMessage);
|
|
}
|
|
|
|
/* Update MR in Zentao database. */
|
|
$this->dao->update(TABLE_MR)->data($MR, $this->config->mr->edit->skippedFields)
|
|
->where('id')->eq($MRID)
|
|
->batchCheck($this->config->mr->edit->requiredFields, 'notempty')
|
|
->autoCheck()
|
|
->exec();
|
|
$MR = $this->getByID($MRID);
|
|
|
|
if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError());
|
|
return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => helper::createLink('mr', 'browse'));
|
|
}
|
|
|
|
/**
|
|
* sync MR from GitLab API to Zentao database.
|
|
*
|
|
* @param object $MR
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function apiSyncMR($MR)
|
|
{
|
|
$rawMR = $this->apiGetSingleMR($MR->gitlabID, $MR->targetProject, $MR->mriid);
|
|
/* Sync MR in ZenTao database whatever status of MR in GitLab. */
|
|
if(isset($rawMR->iid))
|
|
{
|
|
$map = $this->config->mr->maps->sync;
|
|
$gitlabUsers = $this->gitlab->getUserIdAccountPairs($MR->gitlabID);
|
|
|
|
$newMR = new stdclass;
|
|
foreach($map as $syncField => $config)
|
|
{
|
|
$value = '';
|
|
list($field, $optionType, $options) = explode('|', $config);
|
|
|
|
if($optionType == 'field') $value = $rawMR->$field;
|
|
if($optionType == 'userPairs')
|
|
{
|
|
$gitlabUserID = '';
|
|
if(isset($rawMR->$field))
|
|
{
|
|
$values = $rawMR->$field;
|
|
if(isset($values[0])) $gitlabUserID = $values[0]->$options;
|
|
}
|
|
$value = zget($gitlabUsers, $gitlabUserID, '');
|
|
}
|
|
|
|
if($value) $newMR->$syncField = $value;
|
|
}
|
|
|
|
/* Update MR in Zentao database. */
|
|
$this->dao->update(TABLE_MR)->data($newMR)
|
|
->where('id')->eq($MR->id)
|
|
->exec();
|
|
}
|
|
return $this->dao->findByID($MR->id)->from(TABLE_MR)->fetch();
|
|
}
|
|
|
|
/**
|
|
* Batch Sync GitLab MR Database.
|
|
*
|
|
* @param object $MRList
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function batchSyncMR($MRList)
|
|
{
|
|
if(!empty($MRList)) foreach($MRList as $key => $MR)
|
|
{
|
|
if($MR->status != 'opened') continue;
|
|
$rawMR = $this->apiGetSingleMR($MR->gitlabID, $MR->targetProject, $MR->mriid);
|
|
|
|
if(isset($rawMR->iid))
|
|
{
|
|
/* create gitlab mr todo to zentao todo */
|
|
$this->batchSyncTodo($MR->gitlabID, $MR->targetProject);
|
|
|
|
$map = $this->config->mr->maps->sync;
|
|
$gitlabUsers = $this->gitlab->getUserIdAccountPairs($MR->gitlabID);
|
|
|
|
$newMR = new stdclass;
|
|
|
|
foreach($map as $syncField => $config)
|
|
{
|
|
$value = '';
|
|
list($field, $optionType, $options) = explode('|', $config);
|
|
|
|
if($optionType == 'field') $value = $rawMR->$field;
|
|
if($optionType == 'userPairs')
|
|
{
|
|
$gitlabUserID = '';
|
|
if(isset($rawMR->$field))
|
|
{
|
|
$values = $rawMR->$field;
|
|
if(isset($values[0])) $gitlabUserID = $values[0]->$options;
|
|
}
|
|
$value = zget($gitlabUsers, $gitlabUserID, '');
|
|
}
|
|
|
|
if($value) $newMR->$syncField = $value;
|
|
}
|
|
|
|
/* For compatibility with PHP 5.4 . */
|
|
$condition = (array)$newMR;
|
|
if(empty($condition)) continue;
|
|
|
|
/* Update compile status of current MR object */
|
|
if(isset($MR->needCI) and $MR->needCI == '1')
|
|
{
|
|
$newMR->compileStatus = empty($MR->compileID) ? 'failed' : $this->loadModel('compile')->getByID($MR->compileID)->status;
|
|
}
|
|
|
|
/* Update MR in Zentao database. */
|
|
$this->dao->update(TABLE_MR)->data($newMR)
|
|
->where('id')->eq($MR->id)
|
|
->exec();
|
|
|
|
/* Refetch MR in Zentao database. */
|
|
$MR = $this->dao->findByID($MR->id)->from(TABLE_MR)->fetch();
|
|
$MRList[$key] = $MR;
|
|
}
|
|
}
|
|
|
|
return $MRList;
|
|
}
|
|
|
|
/**
|
|
* Sync GitLab Todo to ZenTao Todo.
|
|
*
|
|
* @param int $gitlabID
|
|
* @param int $projectID
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function batchSyncTodo($gitlabID, $projectID)
|
|
{
|
|
/* It can only get todo from GitLab API by its assignee. So here should use sudo as the assignee to get the todo list. */
|
|
/* In this case, ignore sync todo for reviewer due to an issue in GitLab API. */
|
|
$accountList = $this->dao->select('assignee')->from(TABLE_MR)
|
|
->where('deleted')->eq('0')
|
|
->andWhere('status')->eq('opened')
|
|
->andWhere('gitlabID')->eq($gitlabID)
|
|
->andWhere('targetProject')->eq($projectID)
|
|
->fetchPairs();
|
|
|
|
foreach($accountList as $account)
|
|
{
|
|
$accountPair = $this->getSudoAccountPair($gitlabID, $projectID, $account);
|
|
if(!empty($accountPair) and isset($accountPair[$account]))
|
|
{
|
|
$sudo = $accountPair[$account];
|
|
$todoList = $this->gitlab->apiGetTodoList($gitlabID, $projectID, $sudo);
|
|
|
|
foreach($todoList as $rawTodo)
|
|
{
|
|
$todoDesc = $this->dao->select('*')
|
|
->from(TABLE_TODO)
|
|
->where('idvalue')->eq($rawTodo->id)
|
|
->fetch();
|
|
if(empty($todoDesc))
|
|
{
|
|
$todo = new stdClass;
|
|
$todo->account = $this->app->user->account;
|
|
$todo->assignedTo = $account;
|
|
$todo->assignedBy = $this->app->user->account;
|
|
$todo->date = date("Y-m-d", strtotime($rawTodo->target->created_at));
|
|
$todo->assignedDate = $rawTodo->target->created_at;
|
|
$todo->begin = '2400'; /* 2400 means begin is 'undefined'. */
|
|
$todo->end = '2400'; /* 2400 means end is 'undefined'. */
|
|
$todo->type = 'custom';
|
|
$todo->idvalue = $rawTodo->id;
|
|
$todo->pri = 3;
|
|
$todo->name = $this->lang->mr->common . ": " . $rawTodo->target->title;
|
|
$todo->desc = $rawTodo->target->assignee->name . ' ' . $this->lang->mr->at . ' ' . '<a href="' . $this->gitlab->apiGetSingleProject($gitlabID, $projectID)->web_url . '" target="_blank">' . $rawTodo->project->path .'</a>' . ' ' . $this->lang->mr->todomessage . '<a href="' . $rawTodo->target->web_url . '" target="_blank">' . ' ' . $this->lang->mr->common .'</a>' . '。';
|
|
$todo->status = 'wait';
|
|
$todo->finishedBy = '';
|
|
|
|
$this->dao->insert(TABLE_TODO)->data($todo)->exec();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a list of todo items.
|
|
*
|
|
* @param int $gitlabID
|
|
* @param int $projectID
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function todoDescriptionLink($gitlabID, $projectID)
|
|
{
|
|
$gitlab = $this->gitlab->getByID($gitlabID);
|
|
if(!$gitlab) return '';
|
|
return rtrim($gitlab->url, '/')."/dashboard/todos?project_id=$projectID&type=MergeRequest";
|
|
}
|
|
|
|
/**
|
|
* Create MR by API.
|
|
*
|
|
* @link https://docs.gitlab.com/ee/api/merge_requests.html#create-mr
|
|
* @param int $gitlabID
|
|
* @param int $projectID
|
|
* @param object $MR
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function apiCreateMR($gitlabID, $projectID, $MR)
|
|
{
|
|
$url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests");
|
|
return json_decode(commonModel::http($url, $MR));
|
|
}
|
|
|
|
/**
|
|
* Get MR list by API.
|
|
*
|
|
* @link https://docs.gitlab.com/ee/api/merge_requests.html#list-project-merge-requests
|
|
* @param int $gitlabID
|
|
* @param int $projectID
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function apiGetMRList($gitlabID, $projectID)
|
|
{
|
|
$url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests");
|
|
return json_decode(commonModel::http($url));
|
|
}
|
|
|
|
/**
|
|
* Get same opened mr by api.
|
|
*
|
|
* @param int $gitlabID
|
|
* @param int $sourceProject
|
|
* @param string $sourceBranch
|
|
* @param int $targetProject
|
|
* @param string $targetBranch
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function apiGetSameOpened($gitlabID, $sourceProject, $sourceBranch, $targetProject, $targetBranch)
|
|
{
|
|
if(empty($gitlabID) or empty($sourceProject) or empty($sourceBranch) or empty($targetProject) or empty($targetBranch)) return null;
|
|
|
|
$url = sprintf($this->loadModel('gitlab')->getApiRoot((int)$gitlabID), "/projects/{$sourceProject}/merge_requests") . "&state=opened&source_branch={$sourceBranch}&target_branch={$targetBranch}";
|
|
$response = json_decode(commonModel::http($url));
|
|
|
|
if($response)
|
|
{
|
|
foreach($response as $mr)
|
|
{
|
|
if(empty($mr->source_project_id) or empty($mr->target_project_id)) return null;
|
|
if($mr->source_project_id == $sourceProject and $mr->target_project_id == $targetProject) return $mr;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get single MR by API.
|
|
*
|
|
* @link https://docs.gitlab.com/ee/api/merge_requests.html#get-single-mr
|
|
* @param int $gitlabID
|
|
* @param int $projectID targetProject
|
|
* @param int $MRID
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function apiGetSingleMR($gitlabID, $projectID, $MRID)
|
|
{
|
|
$url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests/$MRID");
|
|
return json_decode(commonModel::http($url));
|
|
}
|
|
|
|
/**
|
|
* Update MR by API.
|
|
*
|
|
* @link https://docs.gitlab.com/ee/api/merge_requests.html#update-mr
|
|
* @param int $gitlabID
|
|
* @param int $projectID
|
|
* @param int $MRID
|
|
* @param object $MR
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function apiUpdateMR($gitlabID, $projectID, $MRID, $MR)
|
|
{
|
|
$url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests/$MRID");
|
|
return json_decode(commonModel::http($url, $MR, $options = array(CURLOPT_CUSTOMREQUEST => 'PUT')));
|
|
}
|
|
|
|
/**
|
|
* Delete MR by API.
|
|
*
|
|
* @link https://docs.gitlab.com/ee/api/merge_requests.html#delete-a-merge-request
|
|
* @param int $gitlabID
|
|
* @param int $projectID
|
|
* @param int $MRID
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function apiDeleteMR($gitlabID, $projectID, $MRID)
|
|
{
|
|
$url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests/$MRID");
|
|
return json_decode(commonModel::http($url, null, array(CURLOPT_CUSTOMREQUEST => 'DELETE')));
|
|
}
|
|
|
|
/**
|
|
* Close MR by API.
|
|
*
|
|
* @link https://docs.gitlab.com/ee/api/merge_requests.html#update-mr
|
|
* @param int $gitlabID
|
|
* @param int $projectID
|
|
* @param int $MRID
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function apiCloseMR($gitlabID, $projectID, $MRID)
|
|
{
|
|
$url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests/$MRID") . '&state_event=close';
|
|
return json_decode(commonModel::http($url, null, array(CURLOPT_CUSTOMREQUEST => 'PUT')));
|
|
}
|
|
|
|
/**
|
|
* Reopen MR by API.
|
|
*
|
|
* @link https://docs.gitlab.com/ee/api/merge_requests.html#update-mr
|
|
* @param int $gitlabID
|
|
* @param int $projectID
|
|
* @param int $MRID
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function apiReopenMR($gitlabID, $projectID, $MRID)
|
|
{
|
|
$url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests/$MRID") . '&state_event=reopen';
|
|
return json_decode(commonModel::http($url, null, array(CURLOPT_CUSTOMREQUEST => 'PUT')));
|
|
}
|
|
|
|
/**
|
|
* Accept MR by API.
|
|
*
|
|
* @link https://docs.gitlab.com/ee/api/merge_requests.html#accept-mr
|
|
* @param int $gitlabID
|
|
* @param int $projectID
|
|
* @param int $MRID
|
|
* @param string $sudo
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function apiAcceptMR($gitlabID, $projectID, $MRID, $sudo = "")
|
|
{
|
|
$url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests/$MRID/merge");
|
|
if($sudo != "") return json_decode(commonModel::http($url, $data = null, $options = array(CURLOPT_CUSTOMREQUEST => 'PUT'), $headers = array("sudo: {$sudo}")));
|
|
return json_decode(commonModel::http($url, $data = null, $options = array(CURLOPT_CUSTOMREQUEST => 'PUT')));
|
|
}
|
|
|
|
/**
|
|
* Get MR diff versions by API.
|
|
*
|
|
* @link https://docs.gitlab.com/ee/api/merge_requests.html#get-mr-diff-versions
|
|
* @param object $MR
|
|
* @param string $encoding
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function getDiffs($MR, $encoding = '')
|
|
{
|
|
$diffVersions = array();
|
|
if($MR->synced) $diffVersions = $this->apiGetDiffVersions($MR->gitlabID, $MR->targetProject, $MR->mriid);
|
|
|
|
$gitlab = $this->gitlab->getByID($MR->gitlabID);
|
|
|
|
$this->loadModel('repo');
|
|
$repo = new stdclass;
|
|
$repo->SCM = 'GitLab';
|
|
$repo->gitlab = $gitlab->id;
|
|
$repo->project = $MR->targetProject;
|
|
$repo->path = sprintf($this->config->repo->gitlab->apiPath, $gitlab->url, $MR->targetProject);
|
|
$repo->client = $gitlab->url;
|
|
$repo->password = $gitlab->token;
|
|
$repo->account = '';
|
|
$repo->encoding = $encoding;
|
|
|
|
$lines = array();
|
|
$commitList = array();
|
|
foreach($diffVersions as $diffVersion)
|
|
{
|
|
$singleDiff = $this->apiGetSingleDiffVersion($MR->gitlabID, $MR->targetProject, $MR->mriid, $diffVersion->id);
|
|
if($singleDiff->state == 'empty') continue;
|
|
|
|
$commits = $singleDiff->commits;
|
|
$diffs = $singleDiff->diffs;
|
|
foreach($diffs as $index => $diff)
|
|
{
|
|
if(empty($commits[$index])) continue;
|
|
/* Make sure every file with same commitID is unique in $lines. */
|
|
$shortID = $commits[$index]->short_id;
|
|
if(in_array($shortID, $commitList)) continue;
|
|
$commitList[] = $shortID;
|
|
|
|
$lines[] = sprintf("diff --git a/%s b/%s", $diff->old_path, $diff->new_path);
|
|
$lines[] = sprintf("index %s ... %s %s ", $singleDiff->head_commit_sha, $singleDiff->base_commit_sha, $diff->b_mode);
|
|
$lines[] = sprintf("--a/%s", $diff->old_path);
|
|
$lines[] = sprintf("--b/%s", $diff->new_path);
|
|
$diffLines = explode("\n", $diff->diff);
|
|
foreach($diffLines as $diffLine) $lines[] = $diffLine;
|
|
}
|
|
}
|
|
|
|
if(empty($MR->synced))
|
|
{
|
|
$diffs = preg_replace('/^\s*$\n?\r?/m', '', $MR->diffs);
|
|
$lines = explode("\n", $diffs);
|
|
}
|
|
|
|
$scm = $this->app->loadClass('scm');
|
|
$scm->setEngine($repo);
|
|
$diff = $scm->engine->parseDiff($lines);
|
|
return $diff;
|
|
}
|
|
|
|
/**
|
|
* Get sudo account pair, such as "zentao account" => "gitlab account|id".
|
|
*
|
|
* @param int $gitlabID
|
|
* @param int $projectID
|
|
* @param int $account
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getSudoAccountPair($gitlabID, $projectID, $account)
|
|
{
|
|
$bindedUsers = $this->gitlab->getUserAccountIdPairs($gitlabID);
|
|
$accountPair = array();
|
|
if(isset($bindedUsers[$account])) $accountPair[$account] = $bindedUsers[$account];
|
|
return $accountPair;
|
|
}
|
|
|
|
/**
|
|
* Get sudo user ID in both GitLab and Project.
|
|
* Note: sudo parameter in GitLab API can be user ID or username.
|
|
* @param int $gitlabID
|
|
* @param int $projectID
|
|
* @access public
|
|
* @return int|string
|
|
*/
|
|
public function getSudoUsername($gitlabID, $projectID)
|
|
{
|
|
$zentaoUser = $this->app->user->account;
|
|
|
|
/* Fetch user list both in Zentao and current GitLab project. */
|
|
$bindedUsers = $this->gitlab->getUserAccountIdPairs($gitlabID);
|
|
$rawProjectUsers = $this->gitlab->apiGetProjectUsers($gitlabID, $projectID);
|
|
$users = array();
|
|
foreach($rawProjectUsers as $rawProjectUser)
|
|
{
|
|
if(!empty($bindedUsers[$rawProjectUser->username])) $users[$rawProjectUser->username] = $bindedUsers[$rawProjectUser->username];
|
|
}
|
|
if(!empty($users[$zentaoUser])) return $users[$zentaoUser];
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Create a todo item for merge request.
|
|
*
|
|
* @param int $gitlabID
|
|
* @param int $projectID
|
|
* @param int $MRID
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function apiCreateMRTodo($gitlabID, $projectID, $MRID)
|
|
{
|
|
$url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests/$MRID/todo");
|
|
return json_decode(commonModel::http($url, $data = null, $options = array(CURLOPT_CUSTOMREQUEST => 'POST')));
|
|
}
|
|
|
|
/**
|
|
* Get diff versions of MR from GitLab API.
|
|
*
|
|
* @param int $gitlabID
|
|
* @param int $projectID
|
|
* @param int $MRID
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function apiGetDiffVersions($gitlabID, $projectID, $MRID)
|
|
{
|
|
$url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests/$MRID/versions");
|
|
return json_decode(commonModel::http($url));
|
|
}
|
|
|
|
/**
|
|
* Get a single diff version of MR from GitLab API.
|
|
*
|
|
* @param int $gitlabID
|
|
* @param int $projectID
|
|
* @param int $MRID
|
|
* @param int $versionID
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function apiGetSingleDiffVersion($gitlabID, $projectID, $MRID, $versionID)
|
|
{
|
|
$url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests/$MRID/versions/$versionID");
|
|
return json_decode(commonModel::http($url));
|
|
}
|
|
|
|
/**
|
|
* Get diff commits of MR from GitLab API.
|
|
*
|
|
* @param int $gitlabID
|
|
* @param int $projectID
|
|
* @param int $MRID
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function apiGetDiffCommits($gitlabID, $projectID, $MRID)
|
|
{
|
|
$url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests/$MRID/commits");
|
|
return json_decode(commonModel::http($url));
|
|
}
|
|
|
|
/**
|
|
* Reject or Approve this MR.
|
|
*
|
|
* @param object $MR
|
|
* @param string $action
|
|
* @param string $comment
|
|
* @return array
|
|
*/
|
|
public function approve($MR, $action = 'approve', $comment = '')
|
|
{
|
|
$this->loadModel('action');
|
|
$actionID = $this->action->create('mr', $MR->id, $action);
|
|
|
|
$oldMR = $MR;
|
|
if(isset($MR->status) and $MR->status == 'opened')
|
|
{
|
|
$rawApprovalStatus = '';
|
|
if(isset($MR->approvalStatus)) $rawApprovalStatus = $MR->approvalStatus;
|
|
$MR->approver = $this->app->user->account;
|
|
if ($action == 'reject' and $rawApprovalStatus != 'rejected') $MR->approvalStatus = 'rejected';
|
|
if ($action == 'approve' and $rawApprovalStatus != 'approved') $MR->approvalStatus = 'approved';
|
|
if (isset($MR->approvalStatus) and $rawApprovalStatus != $MR->approvalStatus)
|
|
{
|
|
$changes = common::createChanges($oldMR, $MR);
|
|
$this->action->logHistory($actionID, $changes);
|
|
$this->dao->update(TABLE_MR)->data($MR)
|
|
->where('id')->eq($MR->id)
|
|
->exec();
|
|
if (dao::isError()) return array('result' => 'fail', 'message' => dao::getError());
|
|
|
|
/* Save approval history into db. */
|
|
$approval = new stdClass;
|
|
$approval->date = helper::now();
|
|
$approval->mrID = $MR->id;
|
|
$approval->account = $MR->approver;
|
|
$approval->action = $action;
|
|
$approval->comment = $comment;
|
|
$this->dao->insert(TABLE_MRAPPROVAL)->data($approval, $this->config->mrapproval->create->skippedFields)
|
|
->batchCheck($this->config->mrapproval->create->requiredFields, 'notempty')
|
|
->autoCheck()
|
|
->exec();
|
|
if (dao::isError()) return array('result' => 'fail', 'message' => dao::getError());
|
|
|
|
return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'callback' => 'parent.refresh()');
|
|
}
|
|
}
|
|
return array('result' => 'fail', 'message' => $this->lang->mr->repeatedOperation, 'locate' => helper::createLink('mr', 'view', "mr={$MR->id}"));
|
|
}
|
|
|
|
/**
|
|
* Close this MR.
|
|
*
|
|
* @param mixed $MR
|
|
* @return void
|
|
*/
|
|
public function close($MR)
|
|
{
|
|
$this->loadModel('action');
|
|
$actionID = $this->action->create('mr', $MR->id, 'closed');
|
|
$rawMR = $this->apiCloseMR($MR->gitlabID, $MR->targetProject, $MR->mriid);
|
|
$changes = common::createChanges($MR, $rawMR);
|
|
$this->action->logHistory($actionID, $changes);
|
|
if(isset($rawMR->state) and $rawMR->state == 'closed') return array('result' => 'success', 'message' => $this->lang->mr->closeSuccess, 'locate' => helper::createLink('mr', 'view', "mr={$MR->id}"));
|
|
return array('result' => 'fail', 'message' => $this->lang->fail, 'locate' => helper::createLink('mr', 'view', "mr={$MR->id}"));
|
|
}
|
|
|
|
/**
|
|
* Reopen this MR.
|
|
*
|
|
* @param mixed $MR
|
|
* @return void
|
|
*/
|
|
public function reopen($MR)
|
|
{
|
|
$this->loadModel('action');
|
|
$actionID = $this->action->create('mr', $MR->id, 'reopen');
|
|
$rawMR = $this->apiReopenMR($MR->gitlabID, $MR->targetProject, $MR->mriid);
|
|
$changes = common::createChanges($MR, $rawMR);
|
|
$this->action->logHistory($actionID, $changes);
|
|
if(isset($rawMR->state) and $rawMR->state == 'opened') return array('result' => 'success', 'message' => $this->lang->mr->reopenSuccess, 'locate' => helper::createLink('mr', 'view', "mr={$MR->id}"));
|
|
return array('result' => 'fail', 'message' => $this->lang->fail, 'locate' => helper::createLink('mr', 'view', "mr={$MR->id}"));
|
|
}
|
|
|
|
|
|
/**
|
|
* Get review.
|
|
*
|
|
* @param int $repoID
|
|
* @param int $MRID
|
|
* @param string $revision
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getReview($repoID, $MRID, $revision = '')
|
|
{
|
|
if(empty($repoID) or empty($MRID)) return array();
|
|
|
|
$reviews = array();
|
|
$bugs = $this->dao->select('t1.*, t2.realname')->from(TABLE_BUG)->alias('t1')
|
|
->leftJoin(TABLE_USER)->alias('t2')->on('t1.openedBy = t2.account')
|
|
->where('t1.repo')->eq((int)$repoID)
|
|
->andWhere('t1.mr')->eq((int)$MRID)
|
|
->beginIF($revision)->andWhere('t1.v2')->eq($revision)->fi()
|
|
->andWhere('t1.deleted')->eq(0)
|
|
->fetchAll('id');
|
|
foreach($bugs as $bug)
|
|
{
|
|
if(common::hasPriv('bug', 'edit')) $bug->edit = true;
|
|
if(common::hasPriv('bug', 'delete')) $bug->delete = true;
|
|
if(common::hasPriv('bug', 'view')) $bug->view = true;
|
|
$lines = explode(',', trim($bug->lines, ','));
|
|
$line = $lines[0];
|
|
$reviews[$line]['bug'][$bug->id] = $bug;
|
|
}
|
|
|
|
$tasks = $this->dao->select('t1.*, t2.realname')->from(TABLE_TASK)->alias('t1')
|
|
->leftJoin(TABLE_USER)->alias('t2')->on('t1.openedBy = t2.account')
|
|
->where('t1.repo')->eq((int)$repoID)
|
|
->andWhere('t1.mr')->eq((int)$MRID)
|
|
->beginIF($revision)->andWhere('t1.v2')->eq($revision)->fi()
|
|
->andWhere('t1.deleted')->eq(0)
|
|
->fetchAll('id');
|
|
foreach($tasks as $task)
|
|
{
|
|
if(common::hasPriv('task', 'edit')) $task->edit = true;
|
|
if(common::hasPriv('task', 'delete')) $task->delete = true;
|
|
if(common::hasPriv('task', 'view')) $task->view = true;
|
|
$lines = explode(',', trim($task->lines, ','));
|
|
$line = $lines[0];
|
|
$reviews[$line]['task'][$task->id] = $task;
|
|
}
|
|
|
|
return $reviews;
|
|
}
|
|
|
|
/**
|
|
* Get bugs by repo.
|
|
*
|
|
* @param int $repoID
|
|
* @param string $browseType
|
|
* @param string $orderBy
|
|
* @param object $pager
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getBugsByRepo($repoID, $browseType, $orderBy, $pager)
|
|
{
|
|
/* Get execution that user can access. */
|
|
$executions = $this->loadModel('execution')->getPairs($this->session->project, 'all', 'empty|withdelete');
|
|
|
|
$bugs = $this->dao->select('*')->from(TABLE_BUG)
|
|
->where('repo')->eq($repoID)
|
|
->andWhere('deleted')->eq('0')
|
|
->beginIF(!$this->app->user->admin)->andWhere('product')->in($this->app->user->view->products)->fi()
|
|
->beginIF(!$this->app->user->admin)->andWhere('execution')->in(array_keys($executions))->fi()
|
|
->beginIF($browseType == 'assigntome')->andWhere('assignedTo')->eq($this->app->user->account)->fi()
|
|
->beginIF($browseType == 'openedbyme')->andWhere('openedBy')->eq($this->app->user->account)->fi()
|
|
->beginIF($browseType == 'resolvedbyme')->andWhere('resolvedBy')->eq($this->app->user->account)->fi()
|
|
->beginIF($browseType == 'assigntonull')->andWhere('assignedTo')->eq('')->fi()
|
|
->beginIF($browseType == 'unresolved')->andWhere('resolvedBy')->eq('')->fi()
|
|
->beginIF($browseType == 'unclosed')->andWhere('status')->ne('closed')->fi()
|
|
->orderBy($orderBy)
|
|
->page($pager)
|
|
->fetchAll();
|
|
return $bugs;
|
|
}
|
|
|
|
/**
|
|
* Get execution pairs.
|
|
*
|
|
* @param int $product
|
|
* @param int $branch
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getExecutionPairs($product, $branch = 0)
|
|
{
|
|
$pairs = array();
|
|
$executions = $this->loadModel('execution')->getList(0, 'all', 'undone', 0, $product, $branch);
|
|
foreach($executions as $execution) $pairs[$execution->id] = $execution->name;
|
|
return $pairs;
|
|
}
|
|
|
|
/**
|
|
* Save bug.
|
|
*
|
|
* @param int $repoID
|
|
* @param int $mr
|
|
* @param int $v1
|
|
* @param int $v2
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function saveBug($repoID, $mr, $v1, $v2)
|
|
{
|
|
$now = helper::now();
|
|
$data = fixer::input('post')
|
|
->stripTags('commentText', $this->config->allowedTags)
|
|
->add('pri', 2)
|
|
->add('severity', 2)
|
|
->add('openedBy', $this->app->user->account)
|
|
->add('openedDate', $now)
|
|
->add('openedBuild', 'trunk')
|
|
->add('type', 'codeerror')
|
|
->add('repo', $repoID)
|
|
->add('mr', $mr)
|
|
->add('lines', $this->post->begin . ',' . $this->post->end)
|
|
->add('v1', $v1)
|
|
->add('v2', $v2)
|
|
->cleanInt('module,execution,mr,repo')
|
|
->remove('begin,end,uid,reviewType,taskExecution,taskModule,taskAssignedTo')
|
|
->get();
|
|
|
|
$data->steps = $this->loadModel('file')->pasteImage($data->commentText, $this->post->uid);
|
|
if($data->execution) $data->project = (int)$this->dao->select('project')->from(TABLE_PROJECT)->where('id')->eq($data->execution)->fetch('project');
|
|
if($data->assignedTo) $data->assignedDate = $now;
|
|
unset($data->commentText);
|
|
|
|
$this->loadModel('bug');
|
|
foreach(explode(',', $this->config->bug->create->requiredFields . ',repo,mr') as $requiredField)
|
|
{
|
|
$requiredField = trim($requiredField);
|
|
if(empty($requiredField)) continue;
|
|
if(!isset($data->$requiredField)) continue;
|
|
if(empty($data->$requiredField))
|
|
{
|
|
$fieldName = $requiredField;
|
|
if(isset($this->lang->bug->$requiredField)) $fieldName = $this->lang->bug->$requiredField;
|
|
dao::$errors[$requiredField][] = sprintf($this->lang->error->notempty, $fieldName);
|
|
}
|
|
}
|
|
if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError());
|
|
|
|
$this->dao->insert(TABLE_BUG)->data($data)->autocheck()->exec();
|
|
|
|
if(!dao::isError())
|
|
{
|
|
$bugID = $this->dao->lastInsertID();
|
|
$this->loadModel('file')->updateObjectID($this->post->uid, $bugID, 'bug');
|
|
setcookie("repoPairs[$repoID]", $data->product);
|
|
|
|
$bugInfo = array();
|
|
$bugInfo['result'] = 'success';
|
|
$bugInfo['id'] = $bugID;
|
|
$bugInfo['realname'] = $this->app->user->realname;
|
|
$bugInfo['openedDate'] = substr($now, 5, 11);
|
|
$bugInfo['edit'] = common::hasPriv('bug', 'edit');
|
|
$bugInfo['view'] = common::hasPriv('bug', 'view');
|
|
$bugInfo['delete'] = common::hasPriv('bug', 'delete');
|
|
$bugInfo['lines'] = $data->lines;
|
|
$bugInfo['line'] = $this->post->begin;
|
|
$bugInfo['content'] = $data->steps;
|
|
$bugInfo['title'] = $data->title;
|
|
$bugInfo['objectType'] = 'bug';
|
|
$bugInfo['entry'] = $data->entry;
|
|
return $bugInfo;
|
|
}
|
|
|
|
return array('result' => 'fail', 'message' => dao::getError());
|
|
}
|
|
|
|
/**
|
|
* Save task.
|
|
*
|
|
* @param int $repoID
|
|
* @param int $mr
|
|
* @param int $v1
|
|
* @param int $v2
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function saveTask($repoID, $mr, $v1, $v2)
|
|
{
|
|
$now = helper::now();
|
|
$data = fixer::input('post')->stripTags('commentText', $this->config->allowedTags)->get();
|
|
|
|
$task = new stdclass();
|
|
$task->execution = (int)$data->taskExecution;
|
|
$task->project = (int)$this->dao->select('project')->from(TABLE_PROJECT)->where('id')->eq($task->execution)->fetch('project');
|
|
$task->module = (int)$data->taskModule;
|
|
$task->name = $data->title;
|
|
$task->type = 'devel';
|
|
$task->pri = '2';
|
|
$task->status = 'wait';
|
|
$task->version = '1';
|
|
$task->openedBy = $this->app->user->account;
|
|
$task->assignedTo = $data->taskAssignedTo;
|
|
$task->repo = (int)$repoID;
|
|
$task->mr = (int)$mr;
|
|
$task->lines = $this->post->begin . ',' . $this->post->end;
|
|
$task->entry = $data->entry;
|
|
$task->v1 = $v1;
|
|
$task->v2 = $v2;
|
|
$task->desc = $this->loadModel('file')->pasteImage($data->commentText, $this->post->uid);
|
|
if($task->assignedTo) $task->assignedDate = $now;
|
|
|
|
$this->loadModel('task');
|
|
foreach(explode(',', $this->config->task->create->requiredFields . ',repo,mr') as $requiredField)
|
|
{
|
|
$requiredField = trim($requiredField);
|
|
if(empty($requiredField)) continue;
|
|
if(!isset($task->$requiredField)) continue;
|
|
if(empty($task->$requiredField))
|
|
{
|
|
$fieldName = $requiredField;
|
|
if(isset($this->lang->task->$requiredField)) $fieldName = $this->lang->task->$requiredField;
|
|
dao::$errors[$requiredField][] = sprintf($this->lang->error->notempty, $fieldName);
|
|
}
|
|
}
|
|
if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError());
|
|
|
|
$this->dao->insert(TABLE_TASK)->data($task)->autocheck()->exec();
|
|
|
|
if(!dao::isError())
|
|
{
|
|
$taskID = $this->dao->lastInsertID();
|
|
$this->file->updateObjectID($this->post->uid, $taskID, 'task');
|
|
|
|
$taskInfo = array();
|
|
$taskInfo['result'] = 'success';
|
|
$taskInfo['id'] = $taskID;
|
|
$taskInfo['realname'] = $this->app->user->realname;
|
|
$taskInfo['openedDate'] = substr($now, 5, 11);
|
|
$taskInfo['edit'] = common::hasPriv('task', 'edit');
|
|
$taskInfo['view'] = common::hasPriv('task', 'view');
|
|
$taskInfo['delete'] = common::hasPriv('task', 'delete');
|
|
$taskInfo['lines'] = $task->lines;
|
|
$taskInfo['line'] = $this->post->begin;
|
|
$taskInfo['content'] = $task->desc;
|
|
$taskInfo['title'] = $data->title;
|
|
$taskInfo['objectType'] = 'task';
|
|
$taskInfo['entry'] = $task->entry;
|
|
return $taskInfo;
|
|
}
|
|
|
|
return array('result' => 'fail', 'message' => dao::getError());
|
|
}
|
|
|
|
/**
|
|
* Update bug.
|
|
*
|
|
* @param int $bugID
|
|
* @param string $title
|
|
* @access public
|
|
* @return string
|
|
*/
|
|
public function updateBug($bugID, $title)
|
|
{
|
|
$this->dao->update(TABLE_BUG)->set('title')->eq($title)->where('id')->eq($bugID)->exec();
|
|
return $title;
|
|
}
|
|
|
|
/**
|
|
* Update comment.
|
|
*
|
|
* @param int $commentID
|
|
* @param string $comment
|
|
* @access public
|
|
* @return string
|
|
*/
|
|
public function updateComment($commentID, $comment)
|
|
{
|
|
$this->dao->update(TABLE_ACTION)->set('comment')->eq($comment)->where('id')->eq($commentID)->exec();
|
|
return $comment;
|
|
}
|
|
|
|
/**
|
|
* Delete comment.
|
|
*
|
|
* @param int $commentID
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function deleteComment($commentID)
|
|
{
|
|
return $this->dao->delete()->from(TABLE_ACTION)->where('id')->eq($commentID)->exec();
|
|
}
|
|
|
|
/**
|
|
* Get last review info.
|
|
*
|
|
* @param int $repoID
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function getLastReviewInfo($repoID)
|
|
{
|
|
if(empty($repoID)) return null;
|
|
|
|
$lastReview = new stdclass();
|
|
$lastReview->bug = $this->dao->select('*')->from(TABLE_BUG)->where('repo')->eq((int)$repoID)->orderby('id_desc')->fetch();
|
|
$lastReview->task = $this->dao->select('*')->from(TABLE_TASK)->where('repo')->eq((int)$repoID)->orderby('id_desc')->fetch();
|
|
return $lastReview;
|
|
}
|
|
|
|
/**
|
|
* Get mr link list.
|
|
*
|
|
* @param int $MRID
|
|
* @param int $productID
|
|
* @param string $type
|
|
* @param string $orderBy
|
|
* @param object $pager
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getLinkList($MRID, $productID, $type, $orderBy = 'id_desc', $pager = null)
|
|
{
|
|
$linkIDs = $this->dao->select('BID')->from(TABLE_RELATION)
|
|
->where('product')->eq($productID)
|
|
->andWhere('relation')->eq('interrated')
|
|
->andWhere('AType')->eq('mr')
|
|
->andWhere('AID')->eq($MRID)
|
|
->andWhere('BType')->eq($type)
|
|
->fetchPairs('BID');
|
|
|
|
$links = array();
|
|
if($type == 'story' and !empty($linkIDs))
|
|
{
|
|
$orderBy = str_replace('name_', 'title_', $orderBy);
|
|
$links = $this->dao->select('t1.*, t2.spec, t2.verify, t3.name as productTitle')
|
|
->from(TABLE_STORY)->alias('t1')
|
|
->leftJoin(TABLE_STORYSPEC)->alias('t2')->on('t1.id=t2.story')
|
|
->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product=t3.id')
|
|
->where('t1.deleted')->eq(0)
|
|
->andWhere('t1.version=t2.version')
|
|
->andWhere('t1.id')->in($linkIDs)
|
|
->orderBy($orderBy)
|
|
->page($pager)
|
|
->fetchAll('id');
|
|
}
|
|
if($type == 'bug' and !empty($linkIDs))
|
|
{
|
|
$orderBy = str_replace('name_', 'title_', $orderBy);
|
|
$links = $this->dao->select('*')->from(TABLE_BUG)
|
|
->where('deleted')->eq(0)
|
|
->andWhere('id')->in($linkIDs)
|
|
->orderBy($orderBy)
|
|
->page($pager)
|
|
->fetchAll('id');
|
|
}
|
|
if($type == 'task' and !empty($linkIDs))
|
|
{
|
|
$orderBy = str_replace('title_', 'name_', $orderBy);
|
|
$links = $this->dao->select('*')->from(TABLE_TASK)
|
|
->where('deleted')->eq(0)
|
|
->andWhere('id')->in($linkIDs)
|
|
->orderBy($orderBy)
|
|
->page($pager)
|
|
->fetchAll('id');
|
|
}
|
|
|
|
return $links;
|
|
}
|
|
|
|
/**
|
|
* Get linked MR pairs.
|
|
*
|
|
* @param int $objectID
|
|
* @param string $objectType
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getLinkedMRPairs($objectID, $objectType = 'story')
|
|
{
|
|
return $this->dao->select("t2.id,t2.title")->from(TABLE_RELATION)->alias('t1')
|
|
->leftJoin(TABLE_MR)->alias('t2')->on('t1.AID = t2.id')
|
|
->where('t1.AType')->eq('mr')
|
|
->andWhere('t1.BType')->eq($objectType)
|
|
->andWhere('t1.BID')->eq($objectID)
|
|
->fetchPairs();
|
|
}
|
|
|
|
/**
|
|
* Create an mr link.
|
|
*
|
|
* @param int $MRID
|
|
* @param int $productID
|
|
* @param string $type
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function link($MRID, $productID, $type)
|
|
{
|
|
$this->loadModel('action');
|
|
if($type == 'story') $links = $this->post->stories;
|
|
if($type == 'bug') $links = $this->post->bugs;
|
|
if($type == 'task') $links = $this->post->tasks;
|
|
|
|
/* Get link action text. */
|
|
$MR = $this->getByID($MRID);
|
|
$users = $this->loadModel('user')->getPairs('noletter');
|
|
$MRCreateAction = sprintf($this->lang->mr->createAction, $MR->createdDate, zget($users, $MR->createdBy), helper::createLink('mr', 'view', "mr={$MR->id}"));
|
|
|
|
foreach($links as $linkID)
|
|
{
|
|
$relation = new stdclass;
|
|
$relation->product = $productID;
|
|
$relation->AType = 'mr';
|
|
$relation->AID = $MRID;
|
|
$relation->relation = 'interrated';
|
|
$relation->BType = $type;
|
|
$relation->BID = $linkID;
|
|
|
|
$this->dao->replace(TABLE_RELATION)->data($relation)->exec();
|
|
|
|
if($type == 'story') $this->action->create('story', $linkID, 'createmr', '', $MRCreateAction);
|
|
if($type == 'bug') $this->action->create('bug', $linkID, 'createmr', '', $MRCreateAction);
|
|
if($type == 'task') $this->action->create('task', $linkID, 'createmr', '', $MRCreateAction);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* unLink an mr link.
|
|
*
|
|
* @param int $MRID
|
|
* @param int $productID
|
|
* @param string $type
|
|
* @param int $linkID
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function unlink($MRID, $productID, $type, $linkID)
|
|
{
|
|
$this->dao->delete()->from(TABLE_RELATION)->where('product')->eq($productID)->andWhere('AType')->eq('mr')->andWhere('AID')->eq($MRID)->andWhere('BType')->eq($type)->andWhere('BID')->eq($linkID)->exec();
|
|
|
|
$this->loadModel('action')->create($type, $linkID, 'deletemr');
|
|
}
|
|
|
|
/**
|
|
* Get links by mr commites.
|
|
*
|
|
* @param int $gitlabID
|
|
* @param int $projectID
|
|
* @param int $MRID
|
|
* @param string $type
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getCommitedLink($gitlabID, $projectID, $MRID, $type)
|
|
{
|
|
$DiffCommits = $this->apiGetDiffCommits($gitlabID, $projectID, $MRID);
|
|
|
|
$commits = array();
|
|
foreach($DiffCommits as $DiffCommit)
|
|
{
|
|
if(isset($DiffCommit->id)) $commits[] = substr($DiffCommit->id, 0, 10);
|
|
}
|
|
|
|
return $this->dao->select('objectID')->from(TABLE_ACTION)->where('objectType')->eq($type)->andWhere('extra')->in($commits)->fetchPairs('objectID');
|
|
}
|
|
|
|
/**
|
|
* Get mr product.
|
|
*
|
|
* @param object $MR
|
|
* @access public
|
|
* @return mix
|
|
*/
|
|
public function getMRProduct($MR)
|
|
{
|
|
$product = array();
|
|
|
|
if($MR->repoID)
|
|
{
|
|
$productID = $this->dao->select('product')->from(TABLE_REPO)->where('id')->eq($MR->repoID)->fetch('product');
|
|
}
|
|
else
|
|
{
|
|
$products = $this->loadModel('gitlab')->getProductsByProjects(array($MR->targetProject, $MR->sourceProject));
|
|
$productID = array_shift($products);
|
|
}
|
|
|
|
if($productID) $product = $this->loadModel('product')->getById($productID);
|
|
return $product;
|
|
}
|
|
|
|
/**
|
|
* Get toList and ccList.
|
|
*
|
|
* @param object $mr
|
|
* @access public
|
|
* @return bool|array
|
|
*/
|
|
public function getToAndCcList($mr)
|
|
{
|
|
return array($mr->createdBy, $mr->assignee);
|
|
}
|
|
|
|
/**
|
|
* Log merged action to links.
|
|
*
|
|
* @param object $MR
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function logMergedAction($MR)
|
|
{
|
|
$this->loadModel('action');
|
|
$product = $this->getMRProduct($MR);
|
|
|
|
$stories = $this->getLinkList($MR->id, $product->id, 'story');
|
|
foreach($stories as $story)
|
|
{
|
|
$this->action->create('story', $story->id, 'mergedmr', '', helper::createLink('mr', 'view', "mr={$MR->id}"));
|
|
}
|
|
|
|
$bugs = $this->getLinkList($MR->id, $product->id, 'bug');
|
|
foreach($bugs as $bug)
|
|
{
|
|
$this->action->create('bug', $bug->id, 'mergedmr', '', helper::createLink('mr', 'view', "mr={$MR->id}"));
|
|
}
|
|
|
|
$tasks = $this->getLinkList($MR->id, $product->id, 'task');
|
|
foreach($tasks as $task)
|
|
{
|
|
$this->action->create('task', $task->id, 'mergedmr', '', helper::createLink('mr', 'view', "mr={$MR->id}"));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check same opened mr for source branch.
|
|
*
|
|
* @param int $gitlabID
|
|
* @param int $sourceProject
|
|
* @param string $sourceBranch
|
|
* @param int $targetProject
|
|
* @param string $targetBranch
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function checkSameOpened($gitlabID, $sourceProject, $sourceBranch, $targetProject, $targetBranch)
|
|
{
|
|
if(empty($sourceProject) or empty($sourceBranch) or empty($targetProject) or empty($targetBranch)) return array('result' => 'success');
|
|
|
|
$dbOpenedCount = $this->dao->select('count(*) as count')->from(TABLE_MR)
|
|
->where('gitlabID')->eq($gitlabID)
|
|
->andWhere('sourceProject')->eq($sourceProject)
|
|
->andWhere('sourceBranch')->eq($sourceBranch)
|
|
->andWhere('targetProject')->eq($targetProject)
|
|
->andWhere('targetBranch')->eq($targetBranch)
|
|
->andWhere('status')->eq('opened')
|
|
->andWhere('deleted')->eq('0')
|
|
->fetch('count');
|
|
if($dbOpenedCount > 0) return array('result' => 'fail', 'message' => $this->lang->mr->hasSameOpenedMR);
|
|
|
|
if($this->apiGetSameOpened($gitlabID, $sourceProject, $sourceBranch, $targetProject, $targetBranch)) return array('result' => 'fail', 'message' => $this->lang->mr->hasSameOpenedMR);
|
|
return array('result' => 'success');
|
|
}
|
|
|
|
/**
|
|
* Convert API error.
|
|
*
|
|
* @param array $message
|
|
* @access public
|
|
* @return string
|
|
*/
|
|
public function convertApiError($message)
|
|
{
|
|
if(is_array($message)) $message = $message[0];
|
|
if(!is_string($message)) return $message;
|
|
|
|
foreach($this->lang->mr->apiErrorMap as $key => $errorMsg)
|
|
{
|
|
if(strpos($errorMsg, '/') === 0)
|
|
{
|
|
$result = preg_match($errorMsg, $message, $matches);
|
|
if($result) $errorMessage = sprintf(zget($this->lang->mr->errorLang, $key), $matches[1]);
|
|
}
|
|
elseif($message == $errorMsg)
|
|
{
|
|
$errorMessage = zget($this->lang->mr->errorLang, $key, $message);
|
|
}
|
|
if(isset($errorMessage)) break;
|
|
}
|
|
return isset($errorMessage) ? $errorMessage : $message;
|
|
}
|
|
|
|
/**
|
|
* Adjust the action clickable.
|
|
*
|
|
* @param object $MR
|
|
* @param string $action
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public static function isClickable($MR, $action)
|
|
{
|
|
if($action == 'edit' and !$MR->synced) return false;
|
|
return true;
|
|
}
|
|
}
|