diff --git a/.gitignore b/.gitignore index b0628fb53a..ad25ba4508 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ tmp/extension/* .gitignore .DS_Store vendor/ +composer.lock + diff --git a/api/v1/entries/error.php b/api/v1/entries/error.php new file mode 100644 index 0000000000..c2b5eece56 --- /dev/null +++ b/api/v1/entries/error.php @@ -0,0 +1,15 @@ +send(404, array('error' => 'not found')); + } +} diff --git a/api/v1/entries/product.php b/api/v1/entries/product.php new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/v1/entries/productline.php b/api/v1/entries/productline.php new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/v1/entries/productlines.php b/api/v1/entries/productlines.php new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/v1/entries/products.php b/api/v1/entries/products.php new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/v1/entries/program.php b/api/v1/entries/program.php new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/v1/entries/programs.php b/api/v1/entries/programs.php new file mode 100644 index 0000000000..0c94910361 --- /dev/null +++ b/api/v1/entries/programs.php @@ -0,0 +1,9 @@ +loadController('program', 'browse'); + $program->browse(); + } +} diff --git a/api/v1/entries/project.php b/api/v1/entries/project.php new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/v1/entries/projects.php b/api/v1/entries/projects.php new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/v1/entries/task.php b/api/v1/entries/task.php new file mode 100644 index 0000000000..00496ea8ef --- /dev/null +++ b/api/v1/entries/task.php @@ -0,0 +1,44 @@ +loadController('task', 'view'); + $control->view($taskID); + + $data = $this->getData(); + $task = $data->data->task; + $this->send(200, $task); + } + + public function put($taskID) + { + $oldTask = $this->loadModel('task')->getByID($taskID); + + /* Set $_POST variables. */ + $fields = 'name,type,assignedTo,estimate,left,consumed,story,parent,execution,module,closedReason,status'; + $this->batchSetPost($fields, $oldTask); + + $control = $this->loadController('task', 'edit'); + $control->edit($taskID); + + $this->getData(); + $this->sendSuccess(200, 'success'); + } + + public function delete($taskID) + { + $control = $this->loadController('task', 'delete'); + $control->delete(0, $taskID, 'true'); + + $this->getData(); + $this->sendSuccess(200, 'success'); + } +} diff --git a/api/v1/entries/taskassignto.php b/api/v1/entries/taskassignto.php new file mode 100644 index 0000000000..77feefa35c --- /dev/null +++ b/api/v1/entries/taskassignto.php @@ -0,0 +1,30 @@ +loadModel('task')->getByID($taskID); + + $fields = 'assignedTo,comment,left'; + $this->batchSetPost($fields); + + $control = $this->loadController('task', 'assignTo'); + $this->requireFields('assignedTo'); + + $control->assignTo($task->execution, $taskID); + + $data = $this->getData(); + if($data->result == 'fail') return $this->sendError(400, $data->message); + + $task = $this->loadModel('task')->getByID($dataID); + + $this->send(200, $task); + } +} diff --git a/api/v1/entries/taskfinish.php b/api/v1/entries/taskfinish.php new file mode 100644 index 0000000000..5f23f8e70a --- /dev/null +++ b/api/v1/entries/taskfinish.php @@ -0,0 +1,35 @@ +loadModel('task')->getByID($taskID); + + $fields = 'assignedTo,realStarted'; + $this->batchSetPost($fields, $task); + + $fields = 'finishedDate,comment'; + $this->batchSetPost($fields); + + $this->setPost('currentConsumed', $this->request('currentConsumed', 0)); + $this->setPost('consumed', $this->request('currentConsumed', 0) + $task->consumed); + + $control = $this->loadController('task', 'finish'); + $this->requireFields('assignedTo,currentConsumed,realStarted,finishedDate'); + $control->finish($taskID); + + $data = $this->getData(); + if($data->result == 'fail') return $this->sendError(400, $data->message); + + $task = $this->loadModel('task')->getByID($taskID); + + $this->send(200, $task); + } +} diff --git a/api/v1/entries/tasks.php b/api/v1/entries/tasks.php new file mode 100644 index 0000000000..a1df8a596f --- /dev/null +++ b/api/v1/entries/tasks.php @@ -0,0 +1,32 @@ +batchSetPost($fields); + + $control = $this->loadController('task', 'create'); + $this->requireFields('name,assignedTo,type'); + + $control->create($executionID, $this->request('storyID', 0), $this->request('moduleID', 0), $this->request('copyTaskID', 0), $this->request('copyTodoID', 0)); + + $data = $this->getData(); + if(!isset($data->id)) return $this->sendError(400, $data->message); + + $task = $this->loadModel('task')->getByID($data->id); + + $this->send(200, $task); + } +} diff --git a/api/v1/entries/taskstart.php b/api/v1/entries/taskstart.php new file mode 100644 index 0000000000..d81e69874e --- /dev/null +++ b/api/v1/entries/taskstart.php @@ -0,0 +1,29 @@ +loadModel('task')->getByID($taskID); + + $fields = 'assignedTo,realStarted,comment,left'; + $this->batchSetPost($fields); + + $control = $this->loadController('task', 'start'); + $this->requireFields('left'); + $control->start($taskID); + + $data = $this->getData(); + if($data->result == 'fail') return $this->sendError(400, $data->message); + + $task = $this->loadModel('task')->getByID($dataID); + + $this->send(200, $task); + } +} diff --git a/api/v1/entries/tokens.php b/api/v1/entries/tokens.php new file mode 100644 index 0000000000..b4d7732643 --- /dev/null +++ b/api/v1/entries/tokens.php @@ -0,0 +1,26 @@ +request('account'); + $password = $this->request('password'); + + $user = $this->loadModel('user')->identify($account, $password); + + if($user) + { + $this->user->login($user); + $this->send(200, array('token' => session_id())); + } + + $this->sendError(400, $this->app->lang->user->loginFailed); + } +} diff --git a/config/config.php b/config/config.php index 186ed887a2..dca995a082 100644 --- a/config/config.php +++ b/config/config.php @@ -170,6 +170,10 @@ if(file_exists($myConfig)) include $myConfig; $zentaopmsConfig = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'zentaopms.php'; if(file_exists($zentaopmsConfig)) include $zentaopmsConfig; +/* API路由配置。API route settings. */ +$routesConfig = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'routes.php'; +if(file_exists($routesConfig)) include $routesConfig; + /* Include extension config files. */ $extConfigFiles = glob(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'ext/*.php'); if($extConfigFiles) foreach($extConfigFiles as $extConfigFile) include $extConfigFile; diff --git a/config/filter.php b/config/filter.php index f69a51cd97..42bd625242 100644 --- a/config/filter.php +++ b/config/filter.php @@ -56,6 +56,7 @@ $filter->webhook = new stdclass(); $filter->git = new stdclass(); $filter->svn = new stdclass(); $filter->search = new stdclass(); +$filter->gitlab = new stdclass(); $filter->block->default = new stdclass(); $filter->block->main = new stdclass(); @@ -130,6 +131,8 @@ $filter->webhook->bind = new stdclass(); $filter->user->ajaxgetmore = new stdclass(); $filter->repo->ajaxsynccommit = new stdclass(); $filter->search->index = new stdclass(); +$filter->gitlab->webhook = new stdclass(); +$filter->gitlab->importissue = new stdclass(); $filter->bug->batchcreate->cookie['preBranch'] = 'int'; $filter->bug->browse->cookie['bugModule'] = 'int'; @@ -308,3 +311,15 @@ $filter->webhook->bind->cookie['selectedDepts'] = 'reg::checked'; $filter->search->index->get['words'] = 'reg::any'; $filter->search->index->get['type'] = 'code'; + +$filter->gitlab->webhook->get['gitlab'] = 'int'; +$filter->gitlab->webhook->get['product'] = 'int'; +$filter->gitlab->webhook->get['project'] = 'int'; +$filter->gitlab->webhook->get['token'] = 'reg::any'; + +$filter->gitlab->importissue->get['gitlab'] = 'int'; +$filter->gitlab->importissue->get['product'] = 'int'; +$filter->gitlab->importissue->get['product'] = 'string'; +$filter->gitlab->importissue->get['project'] = 'int'; +$filter->gitlab->importissue->get['repo'] = 'int'; + diff --git a/config/routes.php b/config/routes.php new file mode 100644 index 0000000000..9d76ff8beb --- /dev/null +++ b/config/routes.php @@ -0,0 +1,26 @@ +routes = $routes; diff --git a/config/zentaopms.php b/config/zentaopms.php index 870b7560c1..0b7db239c2 100644 --- a/config/zentaopms.php +++ b/config/zentaopms.php @@ -115,6 +115,29 @@ $config->charsets['fr']['GBK'] = 'GBK'; $config->charsets['vi']['utf-8'] = 'UTF-8'; $config->charsets['vi']['GBK'] = 'GBK'; +$config->openMethods = array(); +$config->openMethods[] = 'gitlab.webhook'; +$config->openMethods[] = 'upgrade.ajaxupdatefile'; +$config->openMethods[] = 'user.login'; +$config->openMethods[] = 'user.logout'; +$config->openMethods[] = 'user.deny'; +$config->openMethods[] = 'user.reset'; +$config->openMethods[] = 'user.refreshrandom'; +$config->openMethods[] = 'api.getsessionid'; +$config->openMethods[] = 'misc.checktable'; +$config->openMethods[] = 'misc.qrcode'; +$config->openMethods[] = 'misc.about'; +$config->openMethods[] = 'misc.checkupdate'; +$config->openMethods[] = 'misc.ping'; +$config->openMethods[] = 'misc.captcha'; +$config->openMethods[] = 'sso.login'; +$config->openMethods[] = 'sso.logout'; +$config->openMethods[] = 'sso.bind'; +$config->openMethods[] = 'sso.gettodolist'; +$config->openMethods[] = 'file.read'; +$config->openMethods[] = 'index.changelog'; +$config->openMethods[] = 'my.preference'; + /* Define the tables. */ define('TABLE_COMPANY', '`' . $config->db->prefix . 'company`'); define('TABLE_DEPT', '`' . $config->db->prefix . 'dept`'); @@ -185,7 +208,7 @@ define('TABLE_LOG', '`' . $config->db->prefix . 'log`'); define('TABLE_SCORE', '`' . $config->db->prefix . 'score`'); define('TABLE_NOTIFY', '`' . $config->db->prefix . 'notify`'); define('TABLE_OAUTH', '`' . $config->db->prefix . 'oauth`'); -define('TABLE_JENKINS', '`' . $config->db->prefix . 'jenkins`'); +define('TABLE_PIPELINE', '`' . $config->db->prefix . 'pipeline`'); define('TABLE_JOB', '`' . $config->db->prefix . 'job`'); define('TABLE_COMPILE', '`' . $config->db->prefix . 'compile`'); @@ -231,5 +254,5 @@ $config->objectTables['team'] = TABLE_TEAM; /* Program privs.*/ $config->programPriv = new stdclass(); -$config->programPriv->scrum = array('product', 'story', 'productplan', 'release', 'project', 'task', 'build', 'qa', 'bug', 'testcase', 'testsuite', 'testreport', 'caselib', 'doc', 'report', 'repo', 'svn', 'git', 'search', 'tree', 'file', 'jenkins', 'job', 'ci', 'branch'); +$config->programPriv->scrum = array('product', 'story', 'productplan', 'release', 'project', 'task', 'build', 'qa', 'bug', 'testcase', 'testsuite', 'testreport', 'caselib', 'doc', 'report', 'repo', 'svn', 'git', 'search', 'tree', 'file', 'jenkins', 'gitlab', 'job', 'ci', 'branch'); $config->programPriv->waterfall = $config->programPriv->scrum + array('workestimation', 'durationestimation', 'budget', 'programplan', 'review', 'reviewissue', 'weekly', 'milestone', 'design', 'issue', 'risk', 'auditplan', 'nc', 'cm', 'pssp'); diff --git a/db/standard/zentao15.0.rc3.sql b/db/standard/zentao15.0.rc3.sql index d59660d05a..68b0271093 100644 --- a/db/standard/zentao15.0.rc3.sql +++ b/db/standard/zentao15.0.rc3.sql @@ -447,7 +447,7 @@ CREATE TABLE `zt_history` ( PRIMARY KEY (`id`), KEY `action` (`action`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; -CREATE TABLE `zt_jenkins` ( +CREATE TABLE `zt_pipeline` ( `id` smallint(8) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `url` varchar(255) DEFAULT NULL, diff --git a/db/zentao.sql b/db/zentao.sql index 328e3bcce9..3e1fc29691 100644 --- a/db/zentao.sql +++ b/db/zentao.sql @@ -471,14 +471,16 @@ CREATE TABLE IF NOT EXISTS `zt_history` ( PRIMARY KEY (`id`), KEY `action` (`action`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; --- DROP TABLE IF EXISTS `zt_jenkins`; -CREATE TABLE IF NOT EXISTS `zt_jenkins` ( +-- DROP TABLE IF EXISTS `zt_pipeline`; +CREATE TABLE IF NOT EXISTS `zt_pipeline` ( `id` smallint(8) unsigned NOT NULL AUTO_INCREMENT, + `type` char(30) NOT NULL, `name` varchar(50) NOT NULL, `url` varchar(255) DEFAULT NULL, `account` varchar(30) DEFAULT NULL, `password` varchar(255) NOT NULL, `token` varchar(255) DEFAULT NULL, + `private` char(32) DEFAULT NULL, `createdBy` varchar(30) NOT NULL, `createdDate` datetime NOT NULL, `editedBy` varchar(30) NOT NULL, @@ -749,7 +751,7 @@ CREATE TABLE IF NOT EXISTS `zt_relation` ( `BVersion` char(30) NOT NULL, `extra` char(30) NOT NULL, PRIMARY KEY (`id`), - UNIQUE KEY `relation` (`relation`,`AType`,`BType`, `AID`, `BID`) + UNIQUE KEY `relation` (`product`,`relation`,`AType`,`BType`, `AID`, `BID`) ) ENGINE='MyISAM' DEFAULT CHARSET=utf8; -- DROP TABLE IF EXISTS `zt_release`; CREATE TABLE IF NOT EXISTS `zt_release` ( diff --git a/framework/api/entry.class.php b/framework/api/entry.class.php new file mode 100644 index 0000000000..03aac91b8f --- /dev/null +++ b/framework/api/entry.class.php @@ -0,0 +1,369 @@ +app->user)) $this->sendError(401, 'Unauthorized'); + } +} + +/** + * 禅道API的baseEntry类。 + * The baseEntry class file of ZenTao API. + * + */ +class baseEntry +{ + /** + * 全局对象 $app。 + * The global $app object. + * + * @var object + * @access public + */ + public $app; + + /** + * 提交的POST数据 + * The decoded request body. + * + * @var object + * @access public + */ + public $requestBody; + + /** + * 构造方法。 + * The construct function. + * + * @access public + * @return void + */ + public function __construct() + { + global $app; + $this->app = $app; + + $this->parseRequestBody(); + } + + /** + * 获取请求数据(POST PUT) + * Get request data(POST or PUT). + * + * @param string $key + * @param mixed $defaultValue + * @access public + * @return mixed + */ + public function request($key, $defaultValue = '') + { + if(isset($this->requestBody->$key)) return $this->requestBody->$key; + return $defaultValue; + } + + /** + * 解析请求数据 + * Parse body of request data. + * + * @access public + * @return void + */ + private function parseRequestBody() + { + $this->requestBody = new stdClass(); + + if($this->app->action == 'post' or $this->app->action == 'put') + { + $requestBody = file_get_contents("php://input"); + if($requestBody) $this->requestBody = json_decode($requestBody); + } + } + + /** + * HTTP状态码 + * HTTP status code + * + * @access public + */ + public $statusCode = array( + 100 => "100 Continue", + 101 => "101 Switching Protocols", + 102 => "102 Processing", + + 200 => "200 OK", + 201 => "201 Created", + 202 => "202 Accepted", + 203 => "203 Non-Authoritative Information", + 204 => "204 No Content", + 205 => "205 Reset Content", + 206 => "206 Partial Content", + 207 => "207 Multi-Status", + + 300 => "300 Multiple Choices", + 301 => "301 Moved Permanently", + 302 => "302 Found", + 303 => "303 See Other", + 304 => "304 Not Modified", + 305 => "305 Use Proxy", + 307 => "307 Temporary Redirect", + + 400 => "400 Bad Request", + 401 => "401 Authorization Required", + 402 => "402 Payment Required", + 403 => "403 Forbidden", + 404 => "404 Not Found", + 405 => "405 Method Not Allowed", + 406 => "406 Not Acceptable", + 407 => "407 Proxy Authentication Required", + 408 => "408 Request Time-out", + 409 => "409 Conflict", + 410 => "410 Gone", + 411 => "411 Length Required", + 412 => "412 Precondition Failed", + 413 => "413 Request Entity Too Large", + 414 => "414 Request-URI Too Large", + 415 => "415 Unsupported Media Type", + 416 => "416 Requested Range Not Satisfiable", + 417 => "417 Expectation Failed", + 422 => "422 Unprocessable Entity", + 423 => "423 Locked", + 424 => "424 Failed Dependency", + 426 => "426 Upgrade Required", + ); + + /** + * 发送请求的响应数据 + * Send response data + * + * @access public + * @return void + */ + public function send($code, $data) + { + header("Content-type: application/json"); + header("HTTP/1.1 {$this->statusCode[$code]}"); + echo json_encode($data); + exit; + } + + /** + * 发送错误信息 + * Send error response + * + * @param int $code + * @param string $msg + * @access public + * @return void + */ + public function sendError($code, $msg) + { + $response = new stdclass(); + $response->error = $msg; + + $this->send($code, $response); + } + + /** + * 发送成功提示 + * Send success response + * + * @param int $code + * @param string $msg + * @access public + * @return void + */ + public function sendSuccess($code, $msg) + { + $response = new stdclass(); + $response->message = $msg; + + $this->send($code, $response); + } + + /** + * 加载禅道的控制器类 + * Load controller of zentaopms + * + * @param string $moduleName + * @param string $methodName + * @access public + * @return object + */ + public function loadController($moduleName, $methodName) + { + global $app; + $app->setModuleName($moduleName); + $app->setMethodName($methodName); + $app->setControlFile(); + + /* + * 引入该模块的control文件。 + * Include the control file of the module. + **/ + $file2Included = $app->setActionExtFile() ? $app->extActionFile : $app->controlFile; + chdir(dirname($file2Included)); + helper::import($file2Included); + + /* + * 设置control的类名。 + * Set the class name of the control. + **/ + $className = class_exists("my$moduleName") ? "my$moduleName" : $moduleName; + if(!class_exists($className)) $app->triggerError("the control $className not found", __FILE__, __LINE__, $exit = true); + + $controller = new $className(); + $controller->viewType = 'json'; + return $controller; + } + + /** + * 加载指定模块的model文件。 + * Load the model file of one module. + * + * @param string $moduleName 模块名,如果为空,使用当前模块。The module name, if empty, use current module's name. + * @param string $appName The app name, if empty, use current app's name. + * @access public + * @return object|bool 如果没有model文件,返回false,否则返回model对象。If no model file, return false, else return the model object. + */ + public function loadModel($moduleName = '', $appName = '') + { + if(empty($moduleName)) $moduleName = $this->moduleName; + if(empty($appName)) $appName = $this->app->appName; + + global $loadedModels; + if(isset($loadedModels[$appName][$moduleName])) + { + $this->$moduleName = $loadedModels[$appName][$moduleName]; + $this->dao = $this->$moduleName->dao; + return $this->$moduleName; + } + + $modelFile = $this->app->setModelFile($moduleName, $appName); + + /** + * 如果没有model文件,尝试加载config配置信息。 + * If no model file, try load config. + */ + if(!helper::import($modelFile)) + { + $this->app->loadModuleConfig($moduleName, $appName); + $this->app->loadLang($moduleName, $appName); + $this->dao = new dao(); + return false; + } + + /** + * 如果没有扩展文件,model类名是$moduleName + 'model',如果有扩展,还需要增加ext前缀。 + * If no extension file, model class name is $moduleName + 'model', else with 'ext' as the prefix. + */ + $modelClass = class_exists('ext' . $appName . $moduleName. 'model') ? 'ext' . $appName . $moduleName . 'model' : $appName . $moduleName . 'model'; + if(!class_exists($modelClass)) + { + $modelClass = class_exists('ext' . $moduleName. 'model') ? 'ext' . $moduleName . 'model' : $moduleName . 'model'; + if(!class_exists($modelClass)) $this->app->triggerError(" The model $modelClass not found", __FILE__, __LINE__, $exit = true); + } + + /** + * 初始化model对象,在control对象中可以通过$this->$moduleName来引用。同时将dao对象赋为control对象的成员变量,方便引用。 + * Init the model object thus you can try $this->$moduleName to access it. Also assign the $dao object as a member of control object. + */ + $loadedModels[$appName][$moduleName] = new $modelClass($appName); + $this->$moduleName = $loadedModels[$appName][$moduleName]; + $this->dao = $this->$moduleName->dao; + + return $this->$moduleName; + } + + /** + * 获取控制器执行返回的数据,在output缓存中. + * Get controller data from output. + * + * @access public + * @return object. + */ + public function getData() + { + $output = ob_get_clean(); + $output = json_decode($output); + + if(isset($output->data)) $output->data = json_decode($output->data); + + return $output; + } + + /** + * 添加$_POST全局变量. + * Add data to $_POST. + * + * @param string $key + * @param mixed $value + * @access public + * @return void + */ + public function setPost($key, $value) + { + $_POST[$key] = $value; + } + + /** + * 批量添加$_POST全局变量. + * Batch set data to $_POST. + * + * @param string $fields + * @param mixed $object + * @access public + * @return void + */ + public function batchSetPost($fields, $object = '') + { + $fields = explode(',', $fields); + foreach($fields as $field) + { + /* + * If the field exists in request body, use it. + * Otherwise set default value from $object. + */ + if(isset($this->requestBody->$field)) + { + $value = $this->requestBody->$field; + } + else + { + if(!$object or !isset($object->$field)) continue; + $value = $object->$field; + } + + $this->setPost($field, $value); + } + } + + /** + * 确保字段不能为空. + * Make sure the fields is not empty. + * + * @param string $fields + * @param mixed $object + * @access public + * @return void + */ + public function requireFields($fields) + { + $fields = explode(',', $fields); + foreach($fields as $field) + { + if(!isset($_POST[$field])) + { + $module = $this->app->moduleName; + $name = isset($this->app->lang->$module->$field) ? $this->app->lang->$module->$field : $field; + $this->sendError(400, sprintf($this->app->lang->error->notempty, $name)); + } + } + } +} diff --git a/framework/api/router.class.php b/framework/api/router.class.php new file mode 100644 index 0000000000..9c6f96d773 --- /dev/null +++ b/framework/api/router.class.php @@ -0,0 +1,230 @@ +httpMethod = strtolower($_SERVER['REQUEST_METHOD']); + + if(!empty($_SERVER['PATH_INFO'])) + { + $this->path = rtrim($_SERVER['PATH_INFO'], '/'); + } + else + { + $this->path = rtrim((strpos($_SERVER['REQUEST_URI'], '?') > 0 ? strstr($_SERVER['REQUEST_URI'], '?', true) : $_SERVER['REQUEST_URI']), '/'); + } + + $dir = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/'); + if($dir != '') $this->path = substr($this->path, strlen($dir)); + + $subPos = strpos($this->path, '/', 1); + + $this->version = substr($this->path, 1, $subPos-1); + $this->path = substr($this->path, $subPos); + } + + /** + * 解析请求路径,找到处理方法 + * + * Parse request path, find entry and action. + * + * @param array $routes + * @access private + * @return void + */ + public function route($routes) + { + foreach($routes as $route => $target) + { + $patternAsRegex = preg_replace_callback( + '#:([\w]+)\+?#', + array($this, 'matchesCallback'), + str_replace(')', ')?', $route) + ); + if(substr($route, -1) === '/') $patternAsRegex .= '?'; + + /* Cache URL params' names and values if this route matches the current HTTP request. */ + if(!preg_match('#^' . $patternAsRegex . '$#', $this->path, $paramValues)) continue; + + /* Set module and action */ + $this->entry = $target; + $this->action = strtolower($_SERVER['REQUEST_METHOD']); + + /* Set params */ + foreach($this->paramNames as $name) + { + if(!isset($paramValues[$name])) continue; + + if(isset($this->paramNamesPath[$name])) + { + $this->params[$name] = explode('/', urldecode($paramValues[$name])); + } + else + { + $this->params[$name] = urldecode($paramValues[$name]); + } + } + return; + } + + $this->entry = 'error'; + $this->action = 'notFound'; + } + + /** + * 将路由路径参数转化为正则 + * + * Parse params of route to regular expression. + * + * @param string $param + * @access protected + * @return string + */ + protected function matchesCallback($m) + { + $this->paramNames[] = $m[1]; + return '(?P<' . $m[1] . '>[^/]+)'; + } + + /** + * 解析访问请求 + * + * Parse request. + * + * @access public + * @return void + */ + public function parseRequest() + { + /* If version of api don't exists, call parent method. */ + if(!$this->version) return parent::parseRequest(); + + $this->route($this->config->routes); + } + + /** + * 执行对应模块 + * + * Load the running module. + * + * @access public + * @return void + */ + public function loadModule() + { + /* If the version of api don't exists, call parent method. */ + if(!$this->version) return parent::loadModule(); + + $entry = strtolower($this->entry); + include($this->appRoot . "api/$this->version/entries/$entry.php"); + + $entryName = $this->entry . 'Entry'; + $entry = new $entryName(); + call_user_func_array(array($entry, $this->action), $this->params); + } + + /** + * 格式化旧版本API响应数据 + * + * Format old version data. + * + * @param string + * @access public + * @return string + */ + public function formatData($output) + { + /* If the version exists, return output directly. */ + if($this->version) return $output; + + $output = json_decode($output); + + $data = new stdClass(); + $data->status = isset($output->status) ? $output->status : $output->result; + if(isset($output->message)) $data->message = $output->message; + if(isset($output->data)) $data->data = json_decode($output->data); + $output = json_encode($data); + + unset($_SESSION['ENTRY_CODE']); + unset($_SESSION['VALID_ENTRY']); + + return $output; + } +} diff --git a/framework/base/control.class.php b/framework/base/control.class.php index fb9a960bc3..f0d4a82df5 100644 --- a/framework/base/control.class.php +++ b/framework/base/control.class.php @@ -1,4 +1,5 @@ -viewType == 'json') { print(json_encode($data)); - die(helper::removeUTF8Bom(ob_get_clean())); + $response = helper::removeUTF8Bom(ob_get_clean()); + + if(defined('RUN_MODE') and RUN_MODE == 'api') return print($response); + + die($response); } /** diff --git a/framework/base/router.class.php b/framework/base/router.class.php index ad4e48f97d..b79e0f036b 100644 --- a/framework/base/router.class.php +++ b/framework/base/router.class.php @@ -851,15 +851,15 @@ class baseRouter { if(!defined('SESSION_STARTED')) { + /* If request header has token, use it as session for authentication. */ + $this->sessionID = isset($_SERVER['HTTP_TOKEN']) ? session_id($_SERVER['HTTP_TOKEN']) : session_id(); + $sessionName = $this->config->sessionVar; session_name($sessionName); session_set_cookie_params(0, $this->config->webRoot, '', $this->config->cookieSecure, true); if($this->config->customSession) session_save_path($this->getTmpRoot() . 'session'); session_start(); - /* If request header has token, use it as session for authentication. */ - $this->sessionID = isset($_SERVER['HTTP_TOKEN']) ? session_id($_SERVER['HTTP_TOKEN']) : session_id(); - if(isset($_GET[$this->config->sessionVar])) helper::restartSession($_GET[$this->config->sessionVar]); define('SESSION_STARTED', true); diff --git a/lib/scm/gitlab.class.php b/lib/scm/gitlab.class.php index 1ea27285f7..341cfa2442 100644 --- a/lib/scm/gitlab.class.php +++ b/lib/scm/gitlab.class.php @@ -517,7 +517,7 @@ class gitlab if(!scm::checkRevision($version)) return array(); $api = "commits"; - if(empty($count)) $count = 100; + if(empty($count)) $count = 1; $params = array(); $params['ref_name'] = $branch; $params['per_page'] = $count; @@ -610,31 +610,32 @@ class gitlab public function getFilesByCommit($revision) { if(!scm::checkRevision($revision)) return array(); - $api = "commits/{$revision}/diff"; - $files = array(); + $api = "commits/{$revision}/diff"; + $params = new stdclass; + $params->page = 1; + $params->per_page = 100; - $params = array(); - $params['per_page'] = '100'; - for($page = 1; true; $page ++) + $allResults = array(); + while(true) { - $params['page'] = $page; - $allResults = $this->fetch($api, $params); - if(empty($allResults)) break; + $results = $this->fetch($api, $params); + $params->page ++; + $allResults = $allResults + $results; + if(count($results) < 100) break; + } - foreach($allResults as $row) - { - $file = new stdclass(); - $file->revision = $revision; - $file->path = '/' . $row->new_path; - $file->type = 'file'; + foreach($allResults as $row) + { + $file = new stdclass(); + $file->revision = $revision; + $file->path = '/' . $row->new_path; + $file->type = 'file'; - $file->action = 'M'; - if($row->new_file) $file->action = 'A'; - if($row->renamed_file) $file->action = 'R'; - if($row->deleted_file) $file->action = 'D'; - $files[] = $file; - } - if(count($allResults) < $params['per_page']) break; + $file->action = 'M'; + if($row->new_file) $file->action = 'A'; + if($row->renamed_file) $file->action = 'R'; + if($row->deleted_file) $file->action = 'D'; + $files[] = $file; } return $files; diff --git a/module/bug/config.php b/module/bug/config.php index ab1efb6707..bc508859ab 100644 --- a/module/bug/config.php +++ b/module/bug/config.php @@ -2,6 +2,7 @@ $config->bug = new stdClass(); $config->bug->batchCreate = 10; $config->bug->longlife = 7; +$config->bug->removeFields= 'objectTypeList,productList,executionList,gitlabID,gitlabProjectID'; $config->bug->create = new stdclass(); $config->bug->edit = new stdclass(); diff --git a/module/bug/control.php b/module/bug/control.php index 04d34e3611..1c3b20fd13 100644 --- a/module/bug/control.php +++ b/module/bug/control.php @@ -557,6 +557,13 @@ class bug extends control $this->view->customFields = $customFields; $this->view->showFields = $this->config->bug->custom->createFields; + /* Set gitlabProjects. */ + $allGitlabs = $this->loadModel('gitlab')->getPairs(); + $gitlabProjects = $this->loadModel('gitlab')->getProjectsByExecution($executionID); + foreach($allGitlabs as $id => $name) if($id and !isset($gitlabProjects[$id])) unset($allGitlabs[$id]); + $this->view->gitlabList = $allGitlabs; + $this->view->gitlabProjects = $gitlabProjects; + $this->view->title = $this->products[$productID] . $this->lang->colon . $this->lang->bug->create; $this->view->position[] = html::a($this->createLink('bug', 'browse', "productID=$productID"), $this->products[$productID]); $this->view->position[] = $this->lang->bug->create; @@ -1457,6 +1464,13 @@ class bug extends control $bugs = $this->bug->getByList($bugIDList); foreach($bugs as $bugID => $bug) { + $relation = $this->loadModel('gitlab')->getRelationByObject('bug', $bugID); + if(!empty($relation)) + { + $currentIssue = $this->loadModel('gitlab')->apiGetSingleIssue($relation->gitlabID, $relation->projectID, $relation->issueID); + if($currentIssue->state != 'closed') $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'bug', $bug); + } + if($bug->status != 'resolved') { if($bug->status != 'closed') $skipBugs[$bugID] = $bugID; @@ -1540,6 +1554,10 @@ class bug extends control } else { + /* Delete related issue in gitlab. */ + $relation = $this->loadModel('gitlab')->getRelationByObject('bug', $bugID); + if(!empty($relation)) $this->loadModel('gitlab')->deleteIssue('bug', $bugID, $relation->issueID); + $this->bug->delete(TABLE_BUG, $bugID); if($bug->toTask != 0) { diff --git a/module/bug/js/create.js b/module/bug/js/create.js index c9cdc7d5ec..831178a860 100644 --- a/module/bug/js/create.js +++ b/module/bug/js/create.js @@ -168,3 +168,23 @@ $(function() $(window).unload(function(){ if(blockID) window.parent.refreshBlock($('#block' + blockID)); }); + +$(document).ready(function() +{ + $('#gitlab').change(function() + { + host = $('#gitlab').val(); + if(host == '') return false; + projects = ''; + $.each(gitlabProjects[host], function(id, obj){projects = projects + ',' + obj.gitlabProject}); + url = createLink('repo', 'ajaxgetgitlabprojects', "host=" + host + "&projects=" + projects); + + $.get(url, function(response) + { + $('#gitlabProject').html('').append(response); + $('#gitlabProject').chosen().trigger("chosen:updated");; + }); + + }); + +}); diff --git a/module/bug/lang/zh-cn.php b/module/bug/lang/zh-cn.php index 65fc2086d9..a17aa251ad 100644 --- a/module/bug/lang/zh-cn.php +++ b/module/bug/lang/zh-cn.php @@ -24,6 +24,7 @@ $lang->bug->storyVersion = "{$lang->SRCommon}版本"; $lang->bug->color = '标题颜色'; $lang->bug->task = '相关任务'; $lang->bug->title = 'Bug标题'; +$lang->bug->sync2gitlab = '同步gitlab'; $lang->bug->severity = '严重程度'; $lang->bug->severityAB = '级别'; $lang->bug->pri = '优先级'; diff --git a/module/bug/model.php b/module/bug/model.php index a310b1cb7f..5c26f44494 100644 --- a/module/bug/model.php +++ b/module/bug/model.php @@ -84,10 +84,13 @@ class bugModel extends model /* Use classic mode to replace required project. */ if($this->config->systemMode == 'classic' and strpos($this->config->bug->create->requiredFields, 'project') !== false) $this->config->bug->create->requiredFields = str_replace('project', 'execution', $this->config->bug->create->requiredFields); - $this->dao->insert(TABLE_BUG)->data($bug)->autoCheck()->batchCheck($this->config->bug->create->requiredFields, 'notempty')->exec(); + $this->dao->insert(TABLE_BUG)->data($bug, $skip = 'gitlab,gitlabProject')->autoCheck()->batchCheck($this->config->bug->create->requiredFields, 'notempty')->exec(); if(!dao::isError()) { $bugID = $this->dao->lastInsertID(); + + $this->loadModel('gitlab')->apiCreateIssue($this->post->gitlab, $this->post->gitlabProject, 'bug', $bugID, $bug); + $this->file->updateObjectID($this->post->uid, $bugID, 'bug'); $this->file->saveUpload('bug', $bugID); empty($bug->case) ? $this->loadModel('score')->create('bug', 'create', $bugID) : $this->loadModel('score')->create('bug', 'createFormCase', $bug->case); @@ -273,6 +276,32 @@ class bugModel extends model return $actions; } + /** + * Create bug from gitlab issue. + * + * @param object $bug + * @param int $executionID + * @access public + * @return int + */ + public function createBugFromGitlabIssue($bug, $executionID) + { + $bug->openedBy = $this->app->user->account; + $bug->openedDate = helper::now(); // TODO(dingguodong) use from issue->created_at ? + $bug->assignedDate = isset($bug->assignedTo) ? helper::now() : 0; + $bug->openedBuild = 1; + $bug->story = 0; + $bug->task = 0; + $bug->pri = 3; + $bug->severity = 3; + $bug->project = $this->dao->select('parent')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->fetch('parent'); + + $this->dao->insert(TABLE_BUG)->data($bug, $skip = 'gitlab,gitlabProject')->autoCheck()->batchCheck($this->config->bug->create->requiredFields, 'notempty')->exec(); + if(!dao::isError()) return $this->dao->lastInsertID(); + + return false; + } + /** * Get bugs. * @@ -645,6 +674,12 @@ class bugModel extends model if(!empty($bug->resolvedBy)) $this->loadModel('score')->create('bug', 'resolve', $bugID); $this->file->updateObjectID($this->post->uid, $bugID, 'bug'); + + if(!empty($bug)) + { + $relation = $this->loadModel('gitlab')->getRelationByObject('bug', $bugID); + if($relation) $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'bug', $bug, $bugID); + } return common::createChanges($oldBug, $bug); } } @@ -766,6 +801,10 @@ class bugModel extends model $this->executeHooks($bugID); $allChanges[$bugID] = common::createChanges($oldBug, $bug); + + /* update bug to gitlab issue. */ + $relation = $this->loadModel('gitlab')->getRelationByObject('bug', $bugID); + if($relation) $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'bug', $bug, $bugID); } else { @@ -820,6 +859,12 @@ class bugModel extends model /* Update bugs. */ foreach($activateBugs as $bugID => $bug) { + if(!empty($bug)) + { + $relation = $this->loadModel('gitlab')->getRelationByObject('bug', $bugID); + if($relation) $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'bug', (Object)$bug, $bugID); + } + $oldBug = $bugs[$bugID]; $this->dao->update(TABLE_BUG)->data($bug, $skipFields = 'comment')->autoCheck()->where('id')->eq((int)$bugID)->exec(); if(dao::isError()) die(js::error('bug#' . $bugID . dao::getError(true))); @@ -854,6 +899,15 @@ class bugModel extends model ->autoCheck() ->where('id')->eq($bugID)->exec(); + $relation = $this->loadModel('gitlab')->getRelationByObject('bug', $bugID); + $bug = $this->getById($bugID); // get full bug object to update issue. + $bug->assignee_id = $this->loadModel('gitlab')->getGitlabUserID($relation->gitlabID, $bug->assignedTo); + if($bug->assignee_id != '') + { + // TODO(dingguodong) we should alert to operator when can not find the user, and the operator should reconfigure user binding. + $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'bug', $bug, $bugID); + } + if(!dao::isError()) return common::createChanges($oldBug, $bug); } @@ -908,6 +962,12 @@ class bugModel extends model $bug->lastEditedDate = $now; $bug->confirmed = 1; + if(!empty($bug)) + { + $relation = $this->loadModel('gitlab')->getRelationByObject('bug', $bugID); + if($relation) $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'bug', $bug, $bugID); + } + $this->dao->update(TABLE_BUG)->data($bug)->where('id')->eq($bugID)->exec(); $this->executeHooks($bugID); } @@ -991,6 +1051,7 @@ class bugModel extends model ->checkIF($bug->resolution == 'fixed', 'resolvedBuild','notempty') ->where('id')->eq((int)$bugID) ->exec(); + if(!dao::isError()) { $this->loadModel('score')->create('bug', 'resolve', $oldBug); @@ -998,6 +1059,9 @@ class bugModel extends model /* Link bug to build and release. */ $this->linkBugToBuild($bugID, $bug->resolvedBuild); + $relation = $this->loadModel('gitlab')->getRelationByObject('bug', $bugID); + if($relation) $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'bug', $bug, $bugID); + return common::createChanges($oldBug, $bug); } @@ -1127,6 +1191,10 @@ class bugModel extends model $this->dao->update(TABLE_BUG)->data($bug)->where('id')->eq($bugID)->exec(); $this->executeHooks($bugID); + /* batch resolve issure bugs.*/ + $relation = $this->loadModel('gitlab')->getRelationByObject('bug', $bugID); + if($relation) $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'bug', $bug, $bugID); + $changes[$bugID] = common::createChanges($oldBug, $bug); } @@ -1178,11 +1246,18 @@ class bugModel extends model foreach($openedBuilds as $openedBuild) { $build = $this->build->getByID($openedBuild); + if(empty($build)) continue; $build->bugs = trim(str_replace(",$bugID,", ',', ",$build->bugs,"), ','); $this->dao->update(TABLE_BUILD)->set('bugs')->eq($build->bugs)->where('id')->eq((int)$openedBuild)->exec(); } } + if(!empty($bug)) + { + $relation = $this->loadModel('gitlab')->getRelationByObject('bug', $bugID); + if($relation) $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'bug', $bug, $bugID); + } + $bug->activatedCount += 1; return common::createChanges($oldBug, $bug); } @@ -1212,6 +1287,12 @@ class bugModel extends model $this->dao->update(TABLE_BUG)->data($bug)->autoCheck()->where('id')->eq((int)$bugID)->exec(); + $relation = $this->loadModel('gitlab')->getRelationByObject('bug', $bugID); + if(!empty($relation)) + { + $currentIssue = $this->loadModel('gitlab')->apiGetSingleIssue($relation->gitlabID, $relation->projectID, $relation->issueID); + if($currentIssue->state != 'closed') $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'bug', $bug, $bugID); + } return common::createChanges($oldBug, $bug); } diff --git a/module/bug/view/create.html.php b/module/bug/view/create.html.php index a742898300..951d25b4fb 100644 --- a/module/bug/view/create.html.php +++ b/module/bug/view/create.html.php @@ -26,6 +26,7 @@ js::set('isStepsTemplate', $isStepsTemplate); js::set('oldProjectID', $projectID); js::set('blockID', $blockID); js::set('moduleID', $moduleID); +js::set('gitlabProjects', $gitlabProjects); ?>
@@ -312,6 +313,13 @@ js::set('moduleID', $moduleID); + + + task->sync2Gitlab;?> + + + + bug->status;?> diff --git a/module/ci/control.php b/module/ci/control.php index 9e79c040cd..dd9a637b0c 100644 --- a/module/ci/control.php +++ b/module/ci/control.php @@ -104,7 +104,7 @@ class ci extends control { $compile = $this->dao->select('t1.*, t2.jkJob,t2.product,t2.frame,t3.name as jenkinsName,t3.url,t3.account,t3.token,t3.password')->from(TABLE_COMPILE)->alias('t1') ->leftJoin(TABLE_JOB)->alias('t2')->on('t1.job=t2.id') - ->leftJoin(TABLE_JENKINS)->alias('t3')->on('t2.jkHost=t3.id') + ->leftJoin(TABLE_PIPELINE)->alias('t3')->on('t2.jkHost=t3.id') ->where('t1.id')->eq($compileID) ->fetch(); diff --git a/module/ci/model.php b/module/ci/model.php index a3ae0144c0..81c1fffece 100644 --- a/module/ci/model.php +++ b/module/ci/model.php @@ -31,7 +31,7 @@ class ciModel extends model $compiles = $this->dao->select('t1.*, t2.jkJob, t3.name as jenkinsName,t3.url,t3.account,t3.token,t3.password') ->from(TABLE_COMPILE)->alias('t1') ->leftJoin(TABLE_JOB)->alias('t2')->on('t1.job=t2.id') - ->leftJoin(TABLE_JENKINS)->alias('t3')->on('t2.jkHost=t3.id') + ->leftJoin(TABLE_PIPELINE)->alias('t3')->on('t2.jkHost=t3.id') ->where('t1.status')->ne('success') ->andWhere('t1.status')->ne('failure') ->andWhere('t1.status')->ne('create_fail') diff --git a/module/common/lang/common.php b/module/common/lang/common.php index 00f346a769..20e9eebdcf 100644 --- a/module/common/lang/common.php +++ b/module/common/lang/common.php @@ -38,6 +38,7 @@ $lang->user = new stdclass(); $lang->report = new stdclass(); $lang->repo = new stdclass(); $lang->jenkins = new stdclass(); +$lang->gitlab = new stdclass(); $lang->compile = new stdclass(); $lang->job = new stdclass(); $lang->svn = new stdclass(); @@ -150,4 +151,4 @@ $lang->icons['unlock'] = 'unlock-alt'; $lang->icons['confirmStoryChange'] = 'search'; $lang->icons['score'] = 'tint'; -$lang->noMenuModule = array('report', 'my', 'todo', 'effort', 'program', 'product', 'execution', 'task', 'build', 'productplan', 'project', 'projectrelease', 'projectstory', 'story', 'branch', 'release', 'attend', 'leave', 'makeup', 'overtime', 'lieu', 'custom', 'admin', 'mail', 'extension', 'dev', 'backup', 'action', 'cron', 'pssp', 'sms', 'message', 'webhook', 'search', 'score', 'stage', 'entry', 'jenkins'); +$lang->noMenuModule = array('report', 'my', 'todo', 'effort', 'program', 'product', 'execution', 'task', 'build', 'productplan', 'project', 'projectrelease', 'projectstory', 'story', 'branch', 'release', 'attend', 'leave', 'makeup', 'overtime', 'lieu', 'custom', 'admin', 'mail', 'extension', 'dev', 'backup', 'action', 'cron', 'pssp', 'sms', 'message', 'webhook', 'search', 'score', 'stage', 'entry', 'jenkins','gitlab'); diff --git a/module/common/lang/menu.php b/module/common/lang/menu.php index 1cdbc98c1e..61def452eb 100644 --- a/module/common/lang/menu.php +++ b/module/common/lang/menu.php @@ -319,12 +319,14 @@ $lang->devops->menu = new stdclass(); $lang->devops->menu->code = array('link' => "{$lang->repo->common}|repo|browse|repoID=%s", 'alias' => 'diff,view,revision,log,blame,showsynccomment'); $lang->devops->menu->compile = array('link' => "{$lang->devops->compile}|job|browse", 'subModule' => 'compile,job'); $lang->devops->menu->jenkins = array('link' => "Jenkins|jenkins|browse", 'alias' => 'create,edit'); +$lang->devops->menu->gitlab = array('link' => "Gitlab|gitlab|browse", 'alias' => 'create,edit'); $lang->devops->menu->maintain = array('link' => "{$lang->devops->repo}|repo|maintain", 'alias' => 'create,edit'); $lang->devops->menu->rules = array('link' => "{$lang->devops->rules}|repo|setrules"); $lang->devops->menuOrder[5] = 'code'; $lang->devops->menuOrder[10] = 'compile'; $lang->devops->menuOrder[15] = 'jenkins'; +$lang->devops->menuOrder[14] = 'gitlab'; $lang->devops->menuOrder[20] = 'maintain'; $lang->devops->menuOrder[25] = 'rules'; @@ -521,6 +523,7 @@ $lang->navGroup->devops = 'devops'; $lang->navGroup->repo = 'devops'; $lang->navGroup->job = 'devops'; $lang->navGroup->jenkins = 'devops'; +$lang->navGroup->gitlab = 'devops'; $lang->navGroup->compile = 'devops'; $lang->navGroup->ci = 'devops'; $lang->navGroup->svn = 'devops'; diff --git a/module/common/model.php b/module/common/model.php index 99671fb000..1a0168e12c 100644 --- a/module/common/model.php +++ b/module/common/model.php @@ -178,23 +178,9 @@ class commonModel extends model */ public function isOpenMethod($module, $method) { - if($module == 'upgrade' and $method == 'ajaxupdatefile') return true; - if($module == 'user' and strpos('login|logout|deny|reset|refreshrandom', $method) !== false) return true; - if($module == 'api' and $method == 'getsessionid') return true; - if($module == 'misc' and $method == 'checktable') return true; - if($module == 'misc' and $method == 'qrcode') return true; - if($module == 'misc' and $method == 'about') return true; - if($module == 'misc' and $method == 'checkupdate') return true; - if($module == 'misc' and $method == 'ping') return true; - if($module == 'misc' and $method == 'captcha') return true; - if($module == 'sso' and $method == 'login') return true; - if($module == 'sso' and $method == 'logout') return true; - if($module == 'sso' and $method == 'bind') return true; - if($module == 'sso' and $method == 'gettodolist') return true; + if(in_array("$module.$method", $this->config->openMethods)) return true; + if($module == 'block' and $method == 'main' and isset($_GET['hash'])) return true; - if($module == 'file' and $method == 'read') return true; - if($module == 'index' and $method == 'changelog') return true; - if($module == 'my' and $method == 'preference') return true; if($this->loadModel('user')->isLogon() or ($this->app->company->guest and $this->app->user->account == 'guest')) { @@ -2038,6 +2024,8 @@ EOD; */ public function checkEntry() { + if($this->isOpenMethod($_GET[$this->config->moduleVar], $_GET[$this->config->methodVar])) return true; + $this->loadModel('entry'); if($this->session->valid_entry) { @@ -2050,10 +2038,10 @@ EOD; if(!$this->get->token) $this->response('PARAM_TOKEN_MISSING'); $entry = $this->entry->getByCode($this->get->code); - if(!$entry) $this->response('EMPTY_ENTRY'); - if(!$entry->key) $this->response('EMPTY_KEY'); - if(!$this->checkIP($entry->ip)) $this->response('IP_DENIED'); - if(!$this->checkEntryToken($entry)) $this->response('INVALID_TOKEN'); + if(!$entry) $this->response('EMPTY_ENTRY'); + if(!$entry->key) $this->response('EMPTY_KEY'); + if(!$this->checkIP($entry->ip)) $this->response('IP_DENIED'); + if(!$this->checkEntryToken($entry)) $this->response('INVALID_TOKEN'); if($entry->freePasswd == 0 and empty($entry->account)) $this->response('ACCOUNT_UNBOUND'); $isFreepasswd = ($_GET['m'] == 'user' and strtolower($_GET['f']) == 'apilogin' and $_GET['account'] and $entry->freePasswd); @@ -2253,14 +2241,15 @@ EOD; if(!empty($data)) { + if(is_object($data)) $data = (array) $data; curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_POSTFIELDS, $data); } - + if($options) curl_setopt_array($curl, $options); - $response = curl_exec($curl); $errors = curl_error($curl); + curl_close($curl); $logFile = $app->getLogRoot() . 'saas.'. date('Ymd') . '.log.php'; diff --git a/module/compile/model.php b/module/compile/model.php index 7f82a344ce..f9f1646335 100644 --- a/module/compile/model.php +++ b/module/compile/model.php @@ -37,7 +37,7 @@ class compileModel extends model return $this->dao->select('t1.id, t1.name, t1.job, t1.status, t1.createdDate,t1.testtask, t2.jkJob,t2.triggerType,t2.comment,t2.atDay,t2.atTime, t3.name as repoName, t4.name as jenkinsName')->from(TABLE_COMPILE)->alias('t1') ->leftJoin(TABLE_JOB)->alias('t2')->on('t1.job=t2.id') ->leftJoin(TABLE_REPO)->alias('t3')->on('t2.repo=t3.id') - ->leftJoin(TABLE_JENKINS)->alias('t4')->on('t2.jkHost=t4.id') + ->leftJoin(TABLE_PIPELINE)->alias('t4')->on('t2.jkHost=t4.id') ->where('t1.deleted')->eq('0') ->andWhere('t1.job')->ne('0') ->beginIF(!empty($jobID))->andWhere('t1.job')->eq($jobID)->fi() @@ -123,7 +123,7 @@ class compileModel extends model { $job = $this->dao->select('t1.id,t1.name,t1.repo,t1.jkJob,t2.name as jenkinsName,t2.url,t2.account,t2.token,t2.password') ->from(TABLE_JOB)->alias('t1') - ->leftJoin(TABLE_JENKINS)->alias('t2')->on('t1.jkHost=t2.id') + ->leftJoin(TABLE_PIPELINE)->alias('t2')->on('t1.jkHost=t2.id') ->where('t1.id')->eq($compile->job) ->fetch(); diff --git a/module/execution/control.php b/module/execution/control.php index 9a118778d1..5f96a4150d 100644 --- a/module/execution/control.php +++ b/module/execution/control.php @@ -2510,6 +2510,10 @@ class execution extends control $_POST = array(); foreach($storyIdList as $storyID) { + /* Delete related issue in gitlab. */ + $relation = $this->loadModel('gitlab')->getRelationByObject('story', $storyID); + if(!empty($relation)) $this->loadModel('gitlab')->deleteIssue('story', $storyID, $relation->issueID); + $this->execution->unlinkStory($executionID, $storyID); } } diff --git a/module/execution/model.php b/module/execution/model.php index b1ea240b45..b43c8b03c3 100644 --- a/module/execution/model.php +++ b/module/execution/model.php @@ -1569,6 +1569,7 @@ class executionModel extends model * Get products of a execution. * * @param int $executionID + * @param bool $withBranch * @access public * @return array */ diff --git a/module/gitlab/config.php b/module/gitlab/config.php new file mode 100644 index 0000000000..84ee284a8d --- /dev/null +++ b/module/gitlab/config.php @@ -0,0 +1,104 @@ +gitlab->create = new stdclass; +$config->gitlab->create->requiredFields = 'name,url,token'; + +$config->gitlab->edit = new stdclass; +$config->gitlab->edit->requiredFields = 'name,url,token'; + +$config->gitlab->labelPattern = new stdclass; +$config->gitlab->labelPattern->task = '/^zentao_task\/\d+$/'; +$config->gitlab->labelPattern->bug = '/^zentao_bug\/\d+$/'; +$config->gitlab->labelPattern->story = '/^zentao_story\/\d+$/'; + +$config->gitlab->actions = array(); +$config->gitlab->actions['issue'] = array(); + +$config->gitlab->zentaoObjectLabel = new stdclass; +$config->gitlab->zentaoObjectLabel->name = "zentao_%s/%s"; +$config->gitlab->zentaoObjectLabel->description = "%s"; + +$config->gitlab->zentaoObjectLabel->color = new stdclass; +$config->gitlab->zentaoObjectLabel->color->task = '#0033CC'; +$config->gitlab->zentaoObjectLabel->color->story = '#69D100'; +$config->gitlab->zentaoObjectLabel->color->bug = '#D10069'; +$config->gitlab->zentaoObjectLabel->priority = "0"; + +$config->gitlab->webhookURL = "%s/api.php?m=gitlab&f=webhook&product=%s&gitlab=%s"; + +$config->gitlab->skippedFields = new stdclass; +$config->gitlab->skippedFields->issueCreate = array(); +$config->gitlab->skippedFields->issueCreate['story'] = array(); +$config->gitlab->skippedFields->issueCreate['task'] = array(); +$config->gitlab->skippedFields->issueCreate['bug'] = array(); + +$config->gitlab->maps = new stdclass; +$config->gitlab->maps->task = array(); +$config->gitlab->maps->task['name'] = 'title|field|'; +$config->gitlab->maps->task['desc'] = 'description|field|'; +$config->gitlab->maps->task['openedDate'] = 'created_at|field|datetime'; +$config->gitlab->maps->task['assignedTo'] = 'assignee_id|userPairs|'; +$config->gitlab->maps->task['lastEditedDate'] = 'updated_at|field|datetime'; +$config->gitlab->maps->task['deadline'] = 'due_date|field|date'; +$config->gitlab->maps->task['status'] = 'state|configItems|taskStateMap'; +$config->gitlab->maps->task['pri'] = 'weight|configItems|taskWeightMap'; +$config->gitlab->maps->task['lastEditedBy'] = 'updated_by_id|userPairs|'; + +$config->gitlab->maps->story = array(); +$config->gitlab->maps->story['title'] = 'title|field|'; +$config->gitlab->maps->story['spec'] = 'description|fields|verify'; +$config->gitlab->maps->story['openedDate'] = 'created_at|field|datetime'; +$config->gitlab->maps->story['assignedTo'] = 'assignee_id|userPairs|'; +$config->gitlab->maps->story['status'] = 'state|configItems|storyStateMap'; +$config->gitlab->maps->story['pri'] = 'weight|configItems|storyWeightMap'; + +$config->gitlab->maps->bug = array(); +$config->gitlab->maps->bug['title'] = 'title|field|'; +$config->gitlab->maps->bug['steps'] = 'description|field|'; +$config->gitlab->maps->bug['openedDate'] = 'created_at|field|datetime'; +$config->gitlab->maps->bug['deadline'] = 'due_date|field|date'; +$config->gitlab->maps->bug['assignedTo'] = 'assignee_id|userPairs|'; +$config->gitlab->maps->bug['status'] = 'state|configItems|bugStateMap'; +$config->gitlab->maps->bug['pri'] = 'weight|configItems|bugWeightMap'; + +$config->gitlab->taskWeightMap = array(); +$config->gitlab->taskWeightMap['1'] = '1'; +$config->gitlab->taskWeightMap['2'] = '2'; +$config->gitlab->taskWeightMap['3'] = '3'; + +$config->gitlab->taskStateMap = array(); +$config->gitlab->taskStateMap['doing'] = 'opened'; +$config->gitlab->taskStateMap['wait'] = 'opened'; +$config->gitlab->taskStateMap['closed'] = 'closed'; +$config->gitlab->taskStateMap['done'] = 'closed'; +$config->gitlab->taskStateMap['cancel'] = 'closed'; + +$config->gitlab->taskTypesToSync = 'design,devel,request,discuss,ui,affair,misc'; + +$config->gitlab->storyWeightMap = array(); +$config->gitlab->storyWeightMap['1'] = '1'; +$config->gitlab->storyWeightMap['2'] = '2'; +$config->gitlab->storyWeightMap['3'] = '3'; + +$config->gitlab->storyStateMap = array(); +$config->gitlab->storyStateMap['active'] = 'opened'; +$config->gitlab->storyStateMap['resolved'] = 'closed'; +$config->gitlab->storyStateMap['closed'] = 'closed'; + +$config->gitlab->bugWeightMap = array(); +$config->gitlab->bugWeightMap['1'] = '1'; +$config->gitlab->bugWeightMap['2'] = '2'; +$config->gitlab->bugWeightMap['3'] = '3'; +$config->gitlab->bugWeightMap['4'] = '4'; + +$config->gitlab->bugStateMap = array(); +$config->gitlab->bugStateMap['active'] = 'opened'; +$config->gitlab->bugStateMap['resolved'] = 'closed'; +$config->gitlab->bugStateMap['closed'] = 'closed'; + +$config->gitlab->objectTables = new stdclass; +$config->gitlab->objectTables->story = TABLE_STORY; +$config->gitlab->objectTables->task = TABLE_TASK; +$config->gitlab->objectTables->bug = TABLE_BUG; + +$config->gitlab->objectTypes = array('', 'task', 'bug', 'story'); + diff --git a/module/gitlab/control.php b/module/gitlab/control.php new file mode 100644 index 0000000000..b8b72c1201 --- /dev/null +++ b/module/gitlab/control.php @@ -0,0 +1,373 @@ + + * @package product + * @version $Id: ${FILE_NAME} 5144 2020/1/8 8:10 下午 chenqi@cnezsoft.com $ + * @link http://www.zentao.net + */ +class gitlab extends control +{ + /** + * Browse gitlab. + * + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function browse($orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + $this->app->loadClass('pager', $static = true); + $pager = new pager($recTotal, $recPerPage, $pageID); + + $this->view->position[] = $this->lang->gitlab->common; + $this->view->position[] = $this->lang->gitlab->browse; + + $this->view->title = $this->lang->gitlab->common . $this->lang->colon . $this->lang->gitlab->browse; + $this->view->gitlabList = $this->gitlab->getList($orderBy, $pager); + $this->view->orderBy = $orderBy; + $this->view->pager = $pager; + + $this->display(); + } + + /** + * Create a gitlab. + * + * @access public + * @return void + */ + public function create() + { + if($_POST) + { + $this->checkToken(); + $gitlabID = $this->gitlab->create(); + + if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError())); + $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse'))); + } + + $this->view->title = $this->lang->gitlab->common . $this->lang->colon . $this->lang->gitlab->create; + + $this->view->position[] = html::a(inlink('browse'), $this->lang->gitlab->common); + $this->view->position[] = $this->lang->gitlab->create; + + $this->display(); + } + + /** + * Edit a gitlab. + * + * @param int $id + * @access public + * @return void + */ + public function edit($id) + { + $gitlab = $this->gitlab->getByID($id); + if($_POST) + { + $this->checkToken(); + $this->gitlab->update($id); + if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError())); + $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse'))); + } + + $this->view->position[] = html::a(inlink('browse'), $this->lang->gitlab->common); + $this->view->position[] = $this->lang->gitlab->edit; + + $this->view->title = $this->lang->gitlab->common . $this->lang->colon . $this->lang->gitlab->edit; + $this->view->gitlab = $gitlab; + + $this->display(); + } + + /** + * Bind gitlab user to zentao users. + * + * @access public + * @return void + */ + public function bindUser($gitlabID) + { + $userPairs = $this->loadModel('user')->getPairs(); + + if($_POST) + { + $users = $this->post->zentaoUsers; + $accountList = array(); + $repeatUsers = array(); + foreach($users as $openID => $user) + { + if(empty($user)) continue; + if(isset($accountList[$user])) $repeatUsers[] = zget($userPairs, $user); + $accountList[$user] = $openID; + } + + if(count($repeatUsers)) $this->send(array('result' => 'fail', 'message' => sprintf($this->lang->gitlab->bindUserError, join(',', $repeatUsers)))); + + $user = new stdclass; + $user->providerID = $gitlabID; + $user->providerType = 'gitlab'; + + /* Delete binded users and save new relationship.*/ + $this->dao->delete()->from(TABLE_OAUTH)->where('providerType')->eq($user->providerType)->andWhere('providerID')->eq($user->providerID)->exec(); + foreach($users as $openID => $account) + { + if(!$account) continue; + $user->account = $account; + $user->openID = $openID; + + $this->dao->delete() + ->from(TABLE_OAUTH) + ->where('openID')->eq($user->openID) + ->andWhere('providerType')->eq($user->providerType) + ->andWhere('providerID')->eq($user->providerID) + ->andWhere('account')->eq($user->account) + ->exec(); + + $this->dao->insert(TABLE_OAUTH)->data($user)->exec(); + } + $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->server->http_referer)); + } + + $gitlab = $this->gitlab->getByID($gitlabID); + $zentaoUsers = $this->dao->select('account,email,realname')->from(TABLE_USER)->fetchAll('account'); + + $this->view->title = $this->lang->gitlab->bindUser; + $this->view->userPairs = $userPairs; + $this->view->gitlabUsers = $this->gitlab->apiGetUsers($gitlab); + $this->view->matchedResult = $this->gitlab->getMatchedUsers($gitlabID, $this->view->gitlabUsers, $zentaoUsers); + $this->display(); + } + + /** + * Bind product and gitlab projects. + * + * @param int $gitlabID + * @access public + * @return void + */ + public function bindProduct($gitlabID) + { + $this->view->projectPairs = $this->gitlab->getProjectPairs($gitlabID); + $this->view->title = $this->lang->gitlab->bindProduct; + $this->display(); + } + + /** + * Delete a gitlab. + * + * @param int $id + * @access public + * @return void + */ + public function delete($id, $confim = 'no') + { + if($confim != 'yes') die(js::confirm($this->lang->gitlab->confirmDelete, inlink('delete', "id=$id&confirm=yes"))); + + $this->gitlab->delete(TABLE_PIPELINE, $id); + die(js::reload('parent')); + } + + /** + * Check post token has admin permissions. + * + * @access public + * @return void + */ + public function checkToken() + { + if(strpos($this->post->url, 'http') !== 0) $this->send(array('result' => 'fail', 'message' => array('url' => array($this->lang->gitlab->hostError)))); + if(!$this->post->token) $this->send(array('result' => 'fail', 'message' => array('token' => array($this->lang->gitlab->tokenError)))); + + $user = $this->gitlab->apiGetCurrentUser($this->post->url, $this->post->token); + + if(!is_object($user)) $this->send(array('result' => 'fail', 'message' => array('url' => array($this->lang->gitlab->hostError)))); + if(!isset($user->is_admin) or !$user->is_admin) $this->send(array('result' => 'fail', 'message' => array('token' => array($this->lang->gitlab->tokenError)))); + } + + /** + * Webhook api. + * + * @access public + * @return void + */ + public function webhook() + { + $this->gitlab->webhookCheckToken(); + $product = $this->get->product; + $gitlab = $this->get->gitlab; + $project = $this->get->project; + + $input = file_get_contents('php://input'); + $requestBody = json_decode($input); + $result = $this->gitlab->webhookParseBody($requestBody, $gitlab); + + $logFile = $this->app->getLogRoot() . 'webhook.'. date('Ymd') . '.log.php'; + if(!file_exists($logFile)) file_put_contents($logFile, ''); + + $fh = @fopen($logFile, 'a'); + if($fh) + { + fwrite($fh, date('Ymd H:i:s') . ": " . $this->app->getURI() . "\n"); + fwrite($fh, "JSON: \n " . $input . "\n"); + fwrite($fh, "Parsed object: {$result->issue->objectType} :\n " . print_r($result->object, true) . "\n"); + fclose($fh); + } + + if($result->action == 'updateissue' and isset($result->changes->assignees)) $this->gitlab->webhookAssignIssue($result); + + //if($result->action = 'reopenissue') $this->gitlab->webhookIssueReopen($gitlab, $result); + + if($result->action == 'closeissue') $this->gitlab->webhookCloseIssue($result); + + if($result->action == 'updateissue') $this->gitlab->webhookSyncIssue($gitlab, $result); + + $this->view->result = 'success'; + $this->view->status = 'ok'; + $this->view->data = $result->object; + $this->display(); + } + + public function test($id = 11) + { + $gitlabID = 1; $projectID = 7; + $task = $this->loadModel('task')->getByID($id); + $relations = $this->gitlab->getRelationByObject('task', $task->id); + + a($relations);exit; + $issue = $this->gitlab->taskToIssue($gitlabID, $projectID, $task); + $issue = $this->gitlab->apiCreateIssue($gitlabID, $projectID, $issue); + $this->gitlab->saveIssueRelation('task', $task, $gitlabID, $issue); + exit; + } + + /** + * Import gitlab issue to zentaopms. + * + * @param int $repoID + * @access public + * @return void + */ + public function importIssue($repoID) + { + $repo = $this->loadModel('repo')->getRepoByID($repoID); + $productIDList = explode(',', $repo->product); + $gitlabID = $repo->gitlab; + $projectID = $repo->project; + + if($_POST) + { + $executionList = $this->post->executionList; + $objectTypeList = $this->post->objectTypeList; + $productList = $this->post->productList; + + $failedIssues = array(); + foreach($executionList as $issueID => $executionID) + { + if($executionID) + { + $objectType = $this->config->gitlab->objectTypes[$objectTypeList[$issueID]]; + $issue = $this->gitlab->apiGetSingleIssue($gitlabID, $projectID, $issueID); + $issue->objectType = $objectType; + $issue->objectID = 0; // meet the required parameters for issueToZentaoObject. + if(isset($issue->assignee)) $issue->assignee_id = $issue->assignee->id; + $issue->updated_by_id = $issue->author->id; // here can be replaced by current zentao user. + + $object = $this->gitlab->issueToZentaoObject($issue, $gitlabID); + $object->product = $productList[$issueID]; + $object->execution = $executionID; + $clonedObject = clone $object; + + if($objectType == 'task') $objectID = $this->loadModel('task')->createTaskFromGitlabIssue($clonedObject, $executionID); + if($objectType == 'bug') $objectID = $this->loadModel('bug')->createBugFromGitlabIssue($clonedObject, $executionID); + if($objectType == 'story') $objectID = $this->loadModel('story')->createStoryFromGitlabIssue($clonedObject, $executionID); + + if($objectID) + { + $object->id = $objectID; + $this->gitlab->saveImportedIssue($gitlabID, $projectID, $objectType, $objectID, $issue, $object); + } + else + { + $failedIssues[] = $issue->iid; + } + } + else + { + if($productList[$issueID] != 0) $this->send(array('result' => 'fail', 'message' => $this->lang->gitlab->importIssueError, 'locate' => $this->server->http_referer)); + } + } + + if($failedIssues) $this->send(array('result' => 'success', 'message' => $this->lang->gitlab->importIssueWarn, 'locate' => $this->server->http_referer)); + $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->server->http_referer)); + } + + $savedIssueIDList = $this->dao->select('BID as issueID')->from(TABLE_RELATION) + ->where('relation')->eq('gitlab') + ->andWhere('BType')->eq('issue') + ->andWhere('BVersion')->eq($projectID) + ->andWhere('extra')->eq($gitlabID) + ->fetchAll('issueID'); + + /* 'not[iids]' option in gitlab API has a issue when iids is too long. */ + $gitlabIssues = $this->gitlab->apiGetIssues($gitlabID, $projectID, '&state=opened'); + foreach($gitlabIssues as $index => $issue) + { + foreach($savedIssueIDList as $savedIssueID) + { + if($issue->iid == $savedIssueID->issueID) + { + unset($gitlabIssues[$index]); + break; + } + } + } + + $products = array(); + $products[] = ''; + foreach($productIDList as $productID) + { + $products[$productID] = $this->loadModel("product")->getByID($productID)->name; + } + + $this->view->importable = empty($gitlabIssues) ? false : true; + $this->view->products = $products; + $this->view->gitlabID = $gitlabID; + $this->view->gitlabProjectID = $projectID; + $this->view->objectTypes = $this->config->gitlab->objectTypes; + + $this->view->gitlabIssues = $gitlabIssues; + $this->display(); + } + + /** + * Ajax get executions by productID. + * + * @param int $productID + * @access public + * @return string + */ + public function ajaxGetExecutionsByProduct($productID) + { + if(!$productID) $this->send(array('message' => array())); + + $executions = $this->loadModel('product')->getAllExecutionPairsByProduct($productID); + $options = ""; + foreach($executions as $index =>$execution) + { + $options .= ""; + } + die($options); + } + +} diff --git a/module/gitlab/js/importissue.js b/module/gitlab/js/importissue.js new file mode 100644 index 0000000000..d48d5a6028 --- /dev/null +++ b/module/gitlab/js/importissue.js @@ -0,0 +1,18 @@ +$(document).ready(function() +{ + $('[name^=productList').change(function() + { + var execution = $(this); + host = execution.val(); + if(host == '') return false; + executions = ''; + url = createLink('gitlab', 'ajaxGetExecutionsByProduct', "host=" + host); + + $.get(url, function(response) + { + execution = execution.parent().next().find('select'); + execution.html('').append(response); + execution.chosen().trigger("chosen:updated"); + }); + }); +}); diff --git a/module/gitlab/lang/de.php b/module/gitlab/lang/de.php new file mode 100644 index 0000000000..4e2c99ff52 --- /dev/null +++ b/module/gitlab/lang/de.php @@ -0,0 +1,19 @@ +jenkins->common = 'Jenkins'; +$lang->jenkins->browse = 'Browse Jenkins'; +$lang->jenkins->create = 'Create Jenkins'; +$lang->jenkins->edit = 'Edit Jenkins'; +$lang->jenkins->delete = 'Delete'; +$lang->jenkins->confirmDelete = 'Do you want to delete this Jenkins server?'; + +$lang->jenkins->id = 'ID'; +$lang->jenkins->name = 'Name'; +$lang->jenkins->url = 'Service URL'; +$lang->jenkins->token = 'Token'; +$lang->jenkins->account = 'UserName'; +$lang->jenkins->password = 'Password'; + +$lang->jenkins->lblCreate = 'Create Jenkins Server'; +$lang->jenkins->desc = 'Description'; +$lang->jenkins->tokenFirst = 'Use token if not empty.'; +$lang->jenkins->tips = 'Cancel "Prevent Cross Site Request Forgery exploits" when using password.'; diff --git a/module/gitlab/lang/en.php b/module/gitlab/lang/en.php new file mode 100644 index 0000000000..dd8b436308 --- /dev/null +++ b/module/gitlab/lang/en.php @@ -0,0 +1,22 @@ +jenkins->common = 'Jenkins'; +$lang->jenkins->browse = 'Jenkins'; +$lang->jenkins->create = 'Create Jenkins'; +$lang->jenkins->edit = 'Edit Jenkins'; +$lang->jenkins->delete = 'Delete'; +$lang->jenkins->confirmDelete = 'Do you want to delete this Jenkins server?'; + +$lang->jenkins->browseAction = 'Jenkins List'; +$lang->jenkins->deleteAction = 'Delete Jenkins'; + +$lang->jenkins->id = 'ID'; +$lang->jenkins->name = 'Name'; +$lang->jenkins->url = 'URL'; +$lang->jenkins->token = 'Token'; +$lang->jenkins->account = 'Username'; +$lang->jenkins->password = 'Password'; + +$lang->jenkins->lblCreate = 'Create Jenkins Server'; +$lang->jenkins->desc = 'Description'; +$lang->jenkins->tokenFirst = 'Use token if not empty.'; +$lang->jenkins->tips = 'Cancel "Prevent Cross Site Request Forgery exploits" when using password.'; diff --git a/module/gitlab/lang/fr.php b/module/gitlab/lang/fr.php new file mode 100644 index 0000000000..d3dcf35cbd --- /dev/null +++ b/module/gitlab/lang/fr.php @@ -0,0 +1,20 @@ +jenkins->common = 'Jenkins'; +$lang->jenkins->browse = 'Afficher Jenkins'; +$lang->jenkins->create = 'Cr閑r Jenkins'; +$lang->jenkins->edit = 'Editer Jenkins'; +$lang->jenkins->delete = 'Supprimer'; +$lang->jenkins->confirmDelete = 'Voulez-vous supprimer ce serveur Jenkins ?'; + +$lang->jenkins->id = 'ID'; +$lang->jenkins->name = 'Nom'; +$lang->jenkins->url = 'Service URL'; +$lang->jenkins->token = 'Token'; +$lang->jenkins->account = 'UserName'; +$lang->jenkins->password = 'Password'; + +$lang->jenkins->lblCreate = 'Cr閑r Serveur Jenkins'; +$lang->jenkins->desc = 'Description'; +$lang->jenkins->tokenFirst = 'Utiliser un Token si non vide.'; +$lang->jenkins->tips = 'Cancel "Prevent Cross Site Request Forgery exploits" when using password.'; +$lang->jenkins->tips = "Annuler Emp阠her les exploits de contrefa鏾n de demande intersite lors de l'utilisation du mot de passe."; diff --git a/module/gitlab/lang/vi.php b/module/gitlab/lang/vi.php new file mode 100644 index 0000000000..b5a5f5d46d --- /dev/null +++ b/module/gitlab/lang/vi.php @@ -0,0 +1,19 @@ +jenkins->common = 'Jenkins'; +$lang->jenkins->browse = 'Jenkins'; +$lang->jenkins->create = 'Tạo Jenkins'; +$lang->jenkins->edit = 'Sửa Jenkins'; +$lang->jenkins->delete = 'Xóa'; +$lang->jenkins->confirmDelete = 'Bạn có muốn xóa máy chủ Jenkins này?'; + +$lang->jenkins->id = 'ID'; +$lang->jenkins->name = 'Tên'; +$lang->jenkins->url = 'URL'; +$lang->jenkins->token = 'Token'; +$lang->jenkins->account = 'Người dùng'; +$lang->jenkins->password = 'Mật khẩu'; + +$lang->jenkins->lblCreate = 'Tạo Jenkins Server'; +$lang->jenkins->desc = 'Mô tả'; +$lang->jenkins->tokenFirst = 'Sử dụng token nếu không trống.'; +$lang->jenkins->tips = 'Hủy "Ngăn chặn khai thác yêu cầu trang web giả mạo" khi sử dụng mật khẩu.'; diff --git a/module/gitlab/lang/zh-cn.php b/module/gitlab/lang/zh-cn.php new file mode 100644 index 0000000000..923bcf7baf --- /dev/null +++ b/module/gitlab/lang/zh-cn.php @@ -0,0 +1,44 @@ +gitlab = new stdclass; +$lang->gitlab->common = 'Gitlab'; +$lang->gitlab->browse = '浏览gitlab'; +$lang->gitlab->create = '添加gitlab'; +$lang->gitlab->edit = '编辑gitlab'; +$lang->gitlab->bindUser = '绑定用户'; +$lang->gitlab->bindProduct = '关联产品'; +$lang->gitlab->importIssue = '关联issue'; +$lang->gitlab->delete = '删除'; +$lang->gitlab->confirmDelete = '确认删除该gitlab吗?'; +$lang->gitlab->gitlabAccount = 'gitlab用户'; +$lang->gitlab->zentaoAccount = '禅道用户'; + +$lang->gitlab->browseAction = 'gitlab列表'; +$lang->gitlab->deleteAction = '删除gitlab'; +$lang->gitlab->gitlabProject = "{$lang->gitlab->common}项目"; +$lang->gitlab->gitlabIssue = "{$lang->gitlab->common}issue"; +$lang->gitlab->zentaoProduct = '禅道产品'; +$lang->gitlab->objectType = '类型'; // task, bug, story + +$lang->gitlab->id = 'ID'; +$lang->gitlab->name = "{$lang->gitlab->common}名称"; +$lang->gitlab->url = '服务地址'; +$lang->gitlab->token = 'Token'; +$lang->gitlab->defaultProject = '默认项目'; +$lang->gitlab->private = 'MD5验证'; + +$lang->gitlab->lblCreate = '添加gitlab服务器'; +$lang->gitlab->desc = '描述'; +$lang->gitlab->tokenFirst = 'Token不为空时,优先使用Token。'; +$lang->gitlab->tips = '使用密码时,请在gitlab全局安全设置中禁用"防止跨站点请求伪造"选项。'; + +$lang->gitlab->placeholder = new stdclass; +$lang->gitlab->placeholder->name = ''; +$lang->gitlab->placeholder->url = "请填写Gitlab Server首页的访问地址,如:https://gitlab.zentao.net。"; +$lang->gitlab->placeholder->token = "请填写具有admin权限账户的access token"; + +$lang->gitlab->noImportableIssues = "目前没有可供导入的issue。"; +$lang->gitlab->tokenError = "当前token非管理员权限。"; +$lang->gitlab->hostError = "无效的gitlab服务地址。"; +$lang->gitlab->bindUserError = "不能重复绑定用户 %s"; +$lang->gitlab->importIssueError = "未选择该issue所属的执行。"; +$lang->gitlab->importIssueWarn = "存在导入失败的issue,可再次尝试导入。"; diff --git a/module/gitlab/lang/zh-tw.php b/module/gitlab/lang/zh-tw.php new file mode 100644 index 0000000000..e19ffbbae3 --- /dev/null +++ b/module/gitlab/lang/zh-tw.php @@ -0,0 +1,22 @@ +jenkins->common = 'Jenkins'; +$lang->jenkins->browse = '瀏覽Jenkins'; +$lang->jenkins->create = '添加Jenkins'; +$lang->jenkins->edit = '編輯Jenkins'; +$lang->jenkins->delete = '刪除'; +$lang->jenkins->confirmDelete = '確認刪除該Jenkins嗎?'; + +$lang->jenkins->browseAction = 'Jenkins列表'; +$lang->jenkins->deleteAction = '刪除Jenkins'; + +$lang->jenkins->id = 'ID'; +$lang->jenkins->name = '名稱'; +$lang->jenkins->url = '服務地址'; +$lang->jenkins->token = 'Token'; +$lang->jenkins->account = '用戶名'; +$lang->jenkins->password = '密碼'; + +$lang->jenkins->lblCreate = '添加Jenkins伺服器'; +$lang->jenkins->desc = '描述'; +$lang->jenkins->tokenFirst = 'Token不為空時,優先使用Token。'; +$lang->jenkins->tips = '使用密碼時,請在Jenkins全局安全設置中禁用"防止跨站點請求偽造"選項。'; diff --git a/module/gitlab/model.php b/module/gitlab/model.php new file mode 100644 index 0000000000..4a9296442f --- /dev/null +++ b/module/gitlab/model.php @@ -0,0 +1,1227 @@ + + * @package product + * @version $Id: $ + * @link http://www.zentao.net + */ + +class gitlabModel extends model +{ + /** + * Get a gitlab by id. + * + * @param int $id + * @access public + * @return object + */ + public function getByID($id) + { + return $this->loadModel('pipeline')->getByID($id); + } + + /** + * Get gitlab list. + * + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getList($orderBy = 'id_desc', $pager = null) + { + return $this->loadModel('pipeline')->getList('gitlab', $orderBy, $pager); + } + + /** + * Get gitlab pairs + * + * @return array + */ + public function getPairs() + { + return $this->loadModel('pipeline')->getPairs('gitlab'); + } + + /** + * Get gitlab api base url by gitlab id. + * + * @param int $id + * @access public + * @return string + */ + public function getApiRoot($id) + { + $gitlab = $this->getByID($id); + if(!$gitlab) return ''; + return rtrim($gitlab->url, '/').'/api/v4%s'."?private_token={$gitlab->token}"; + } + + /** + * Get gitlab user id zentao account pairs of one gitlab. + * + * @param int $gitlab + * @access public + * @return array + */ + public function getUserIdAccountPairs($gitlab) + { + return $this->dao->select('openID,account')->from(TABLE_OAUTH) + ->where('providerType')->eq('gitlab') + ->andWhere('providerID')->eq($gitlab) + ->fetchPairs(); + } + + /** + * Get zentao account gitlab user id pairs of one gitlab. + * + * @param int $gitlab + * @access public + * @return array + */ + public function getUserAccountIdPairs($gitlab) + { + return $this->dao->select('account,openID')->from(TABLE_OAUTH) + ->where('providerType')->eq('gitlab') + ->andWhere('providerID')->eq($gitlab) + ->fetchPairs(); + } + + /** + * Get project pairs of one gitlab. + * + * @param int $gitlabID + * @access public + * @return array + */ + public function getProjectPairs($gitlabID) + { + $projects = $this->apiGetProjects($gitlabID); + + $projectPairs = array(); + foreach($projects as $project) $projectPairs[$project->id] = $project->name_with_namespace; + + return $projectPairs; + } + + /** + * Get matched gitlab users. + * + * @param array $gitlabUsers + * @param array $zentaoUsers + * @access public + * @return array + */ + public function getMatchedUsers($gitlabID, $gitlabUsers, $zentaoUsers) + { + $matches = new stdclass; + foreach($gitlabUsers as $gitlabUser) + { + foreach($zentaoUsers as $zentaoUser) + { + if($gitlabUser->account == $zentaoUser->account) $matches->accounts[$gitlabUser->account][] = $zentaoUser->account; + if($gitlabUser->realname == $zentaoUser->realname) $matches->names[$gitlabUser->realname][] = $zentaoUser->account; + if($gitlabUser->email == $zentaoUser->email) $matches->emails[$gitlabUser->email][] = $zentaoUser->account; + } + } + + $bindedUsers = $this->dao->select('openID,account') + ->from(TABLE_OAUTH) + ->where('providerType')->eq('gitlab') + ->andWhere('providerID')->eq($gitlabID) + ->fetchPairs(); + + $matchedUsers = array(); + foreach($gitlabUsers as $gitlabUser) + { + if(isset($bindedUsers[$gitlabUser->id])) + { + $gitlabUser->zentaoAccount = $bindedUsers[$gitlabUser->id]; + $matchedUsers[] = $gitlabUser; + continue; + } + + $matchedZentaoUsers = array(); + if(isset($matches->accounts[$gitlabUser->account])) $matchedZentaoUsers = array_merge($matchedZentaoUsers, $matches->accounts[$gitlabUser->account]); + if(isset($matches->emails[$gitlabUser->email])) $matchedZentaoUsers = array_merge($matchedZentaoUsers, $matches->emails[$gitlabUser->email]); + if(isset($matches->names[$gitlabUser->realname])) $matchedZentaoUsers = array_merge($matchedZentaoUsers, $matches->names[$gitlabUser->realname]); + + $matchedZentaoUsers = array_unique($matchedZentaoUsers); + if(count($matchedZentaoUsers) == 1) + { + $gitlabUser->zentaoAccount = current($matchedZentaoUsers); + $matchedUsers[] = $gitlabUser; + } + } + + return $matchedUsers; + } + + /** + * Get gitlab projects by executionID. + * + * @param int $executionID + * @access public + * @return array + */ + public function getProjectsByExecution($executionID) + { + $products = $this->loadModel('execution')->getProducts($executionID, false); + $productIdList = array_keys($products); + + return $this->dao->select('AID,BID as gitlabProject') + ->from(TABLE_RELATION) + ->where('relation')->eq('interrated') + ->andWhere('AType')->eq('gitlab') + ->andWhere('BType')->eq('gitlabProject') + ->andWhere('product')->in($productIdList) + ->fetchGroup('AID'); + } + + /** + * Get executions by one product for gitlab module. + * + * @param int $productID + * @access public + * @return array + */ + public function getExecutionsByProduct($productID) + { + return $this->dao->select('distinct execution')->from(TABLE_RELATION) + ->where('relation')->eq('interrated') + ->andWhere('AType')->eq('gitlab') + ->andWhere('BType')->eq('gitlabProject') + ->andWhere('product')->eq($productID) + ->fetchAll('execution'); + } + + /** + * Get gitlabID and projectID. + * + * @param string $objectType + * @param int $objectID + * @access public + * @return object + */ + public function getRelationByObject($objectType, $objectID) + { + return $this->dao->select('*, extra as gitlabID, BVersion as projectID, BID as issueID')->from(TABLE_RELATION) + ->where('relation')->eq('gitlab') + ->andWhere('Atype')->eq($objectType) + ->andWhere('AID')->eq($objectID) + ->fetch(); + } + + /** + * Get issue id list group by obejct. + * + * @param string $objectType + * @param int $objectID + * @access public + * @return object + */ + public function getIssueListByObjects($objectType, $objects) + { + return $this->dao->select('*, extra as gitlabID, BVersion as projectID, BID as issueID')->from(TABLE_RELATION) + ->where('relation')->eq('gitlab') + ->andWhere('Atype')->eq($objectType) + ->andWhere('AID')->in($objects) + ->fetchAll('AID'); + } + + + /** + * Get gitlab userID by account. + * + * @param int $gitlabID + * @param string $account + * @access public + * @return arary + */ + public function getGitlabUserID($gitlabID, $account) + { + return $this->dao->select('openID')->from(TABLE_OAUTH) + ->where('providerType')->eq('gitlab') + ->andWhere('providerID')->eq($gitlabID) + ->andWhere('account')->eq($account) + ->fetch('openID'); + } + + /** + * Create a gitlab. + * + * @access public + * @return bool + */ + public function create() + { + return $this->loadModel('pipeline')->create('gitlab'); + } + + /** + * Update a gitlab. + * + * @param int $id + * @access public + * @return bool + */ + public function update($id) + { + return $this->loadModel('pipeline')->update($id); + } + + /** + * Send an api get request. + * + * @param int|string $host gitlab server ID | gitlab host url. + * @param int $api + * @param int $data + * @param int $options + * @access public + * @return object + */ + public function apiGet($host, $api, $data = array(), $options = array()) + { + if(is_numeric($host)) $host = $this->getApiRoot($host); + if(strpos($host, 'http://') !== 0 and strpos($host, 'https://') !== 0) return false; + + $url = sprintf($host, $api); + return json_decode(commonModel::http($url, $data, $options)); + } + + /** + * Send an api post request. + * + * @param int|string $host gitlab server ID | gitlab host url. + * @param int $api + * @param int $data + * @param int $options + * @access public + * @return object + */ + public function apiPost($host, $api, $data = array(), $options = array()) + { + if(is_numeric($host)) $host = $this->getApiRoot($host); + if(strpos($host, 'http://') !== 0 and strpos($host, 'https://') !== 0) return false; + + $url = sprintf($apiRoot, $api); + return json_decode(commonModel::http($url, $data, $options)); + } + + /** + * Get current user. + * + * @param string $host + * @param string $token + * @access public + * @return array + */ + public function apiGetCurrentUser($host, $token) + { + $host = rtrim($host, '/') . "/api/v4%s?private_token=$token"; + return $this->apiGet($host, '/user'); + } + + /** + * Get gitlab user list. + * + * @param string $host + * @param string $token + * @access public + * @return array + */ + public function apiGetUsers($gitlab) + { + $response = $this->apiGet($gitlab->id, '/users'); + + if (!$response) return array(); + + $users = array(); + foreach($response as $gitlabUser) + { + $user = new stdclass; + $user->id = $gitlabUser->id; + $user->realname = $gitlabUser->name; + $user->account = $gitlabUser->username; + $user->email = $gitlabUser->email; + $user->avatar = $gitlabUser->avatar_url; + + $users[] = $user; + } + + return $users; + } + + /** + * Get projects of one gitlab. + * + * @param int $gitlabID + * @access public + * @return void + */ + public function apiGetProjects($gitlabID) + { + $gitlab = $this->getByID($gitlabID); + if(!$gitlab) return array(); + $host = rtrim($gitlab->url, '/'); + $host .= '/api/v4/projects'; + + $allResults = array(); + for($page = 1; true; $page ++) + { + $results = json_decode(commonModel::http($host . "?private_token={$gitlab->token}&simple=true&membership=true&page={$page}&per_page=100")); + if(empty($results) or $page > 10) break; + $allResults = $allResults + $results; + } + return $allResults; + } + + /** + * Get hooks. + * + * @param int $gitlabID + * @param int $projectID + * @access public + * @return void + */ + public function apiGetHooks($gitlabID, $projectID) + { + $apiRoot = $this->getApiRoot($gitlabID); + $apiPath = "/projects/{$projectID}/hooks"; + $url = sprintf($apiRoot, $apiPath); + $response = json_decode(commonModel::http($url)); + return $response; + } + + /** + * Get specific hook by api. + * + * @param int $gitlabID + * @param int $projectID + * @param int $hookID + * @access public + * @return object + */ + public function apiGetHook($gitlabID, $projectID, $hookID) + { + $apiRoot = $this->getApiRoot($gitlabID); + $apiPath = "/projects/$projectID/hooks/$hookID)"; + $url = sprintf($apiRoot, $apiPath); + $response = commonModel::http($url); + return $response; + } + + /** + * Create hook by api. + * + * @param int $gitlabID + * @param int $projectID + * @param string $url + * @access public + * @return object + */ + public function apiCreateHook($gitlabID, $projectID, $url) + { + $apiRoot = $this->getApiRoot($gitlabID); + + $accessToken = $this->dao->select('private as accessToken')->from(TABLE_PIPELINE) + ->where('id')->eq($gitlabID) + ->fetch('accessToken'); + + $postData = new stdclass; + $postData->enable_ssl_verification = "false"; + $postData->issues_events = "true"; + $postData->merge_requests_events = "true"; + $postData->push_events = "true"; + $postData->tag_push_events = "true"; + $postData->url = $url; + $postData->token = $accessToken; + + $url = sprintf($apiRoot, "/projects/{$projectID}/hooks"); + return commonModel::http($url, $postData); + } + + /** + * Delete hook by api. + * + * @param int $gitlabID + * @param int $projectID + * @param int $hookID + * @access public + * @return null|object + */ + public function apiDeleteHook($gitlabID, $projectID, $hookID) + { + $apiRoot = $this->getApiRoot($gitlabID); + $url = sprintf($apiRoot, "/projects/{$projectID}/hooks/{$hookID}"); + + return commonModel::http($url, null, array(CURLOPT_CUSTOMREQUEST => 'delete')); + } + + /** + * Update hook by api. + * + * @param int $gitlabID + * @param int $projectID + * @param int $hookID + * @access public + * @return object + */ + public function apiUpdateHook($gitlabID, $projectID, $hookID) + { + $apiRoot = $this->getApiRoot($gitlabID); + + $postData = new stdclass; + $postData->enable_ssl_verification = "false"; + $postData->issues_events = "true"; + $postData->merge_requests_events = "true"; + $postData->push_events = "true"; + $postData->tag_push_events = "true"; + $postData->note_events = "true"; + $postData->url = $url; + $postData->token = $token; + + $url = sprintf($apiRoot, "/projects/{$projectID}/hooks/{$hookID}"); + return commonModel::http($url, $postData, $options = array(CURLOPT_CUSTOMREQUEST => 'PUT')); + } + + /** + * Create Label for gitlab project. + * + * @param int $gitlabID + * @param int $projectID + * @param object $label + * @access public + * @return object + */ + public function apiCreateLabel($gitlabID, $projectID, $label) + { + if(empty($label->name) or empty($label->color)) return false; + + $apiRoot = $this->getApiRoot($gitlabID); + $url = sprintf($apiRoot, "/projects/{$projectID}/labels/"); + return json_decode(commonModel::http($url, $label)); + } + + /** + * Get labels of project by api. + * + * @param int $gitlabID + * @param int $projectID + * @access public + * @return array + */ + public function apiGetLabels($gitlabID, $projectID) + { + $apiRoot = $this->getApiRoot($gitlabID); + $url = sprintf($apiRoot, "/projects/{$projectID}/labels/"); + $response = commonModel::http($url); + + return json_decode($response); + } + + /** + * Delete a Label with labelName by api. + * + * @param int $gitlabID + * @param int $projectID + * @param string $labelName + * @access public + * @return object + */ + public function apiDeleteLabel($gitlabID, $projectID, $labelName) + { + $labels = $this->apiGetLabels($gitlabID, $projectID); + foreach($labels as $label) + { + if($label->name == $labelName) $labelID = $label->id; + } + + if(empty($labelID)) return false; + + $apiRoot = $this->getApiRoot($gitlabID); + $url = sprintf($apiRoot, "/projects/{$projectID}/labels/{$labelID}"); + + return json_decode(commonModel::http($url, null, $options = array(CURLOPT_CUSTOMREQUEST => 'DELETE'))); + } + + /** + * Get single issue by api. + * + * @param int $gitlabID + * @param int $projectID + * @param int $issueID + * @access public + * @return object + */ + public function apiGetSingleIssue($gitlabID, $projectID, $issueID) + { + $url = sprintf($this->getApiRoot($gitlabID), "/projects/$projectID/issues/{$issueID}"); + return json_decode(commonModel::http($url)); + } + + /** + * Get gitlab issues by api. + * + * @param int $gitlabID + * @param int $projectID + * @param string $options + * @access public + * @return object + */ + public function apiGetIssues($gitlabID, $projectID, $options = null) + { + // TODO(dingguodong) not pagination yet. + if($options) + { + $url = sprintf($this->getApiRoot($gitlabID), "/projects/{$projectID}/issues") . '&per_page=20' . $options; + } + else + { + $url = sprintf($this->getApiRoot($gitlabID), "/projects/{$projectID}/issues") . '&per_page=20'; + } + return json_decode(commonModel::http($url)); + } + + /** + * Create issue by api. + * + * @param int $gitlabID + * @param int $projectID + * @param string $objectType + * @param int $objectID + * @param object $object + * @access public + * @return object + */ + public function apiCreateIssue($gitlabID, $projectID, $objectType, $objectID, $object) + { + $label = $this->createZentaoObjectLabel($gitlabID, $projectID, $objectType, $objectID); + if(!isset($object->id)) $object->id = $objectID; + $issue = $this->loadModel('gitlab')->parseObjectToIssue($gitlabID, $projectID, $objectType, $object); + if(isset($label->name)) $issue->labels = $label->name; + foreach($this->config->gitlab->skippedFields->issueCreate[$objectType] as $field) + { + if(isset($issue->$field)) unset($issue->$field); + } + + $apiRoot = $this->getApiRoot($gitlabID); + $url = sprintf($apiRoot, "/projects/{$projectID}/issues/"); + + $response = json_decode(commonModel::http($url, $issue)); + if(!$response) return false; + + return $this->saveIssueRelation($objectType, $object, $gitlabID, $response); + } + + /** + * Update issue by gitlab API. + * + * @param int $gitlabID + * @param int $projectID + * @param int $issueID + * @param string $objectType + * @param object $object + * @param int $objectID + * @access public + * @return object + */ + public function apiUpdateIssue($gitlabID, $projectID, $issueID, $objectType, $object, $objectID = null) + { + $oldObject = clone $object; + /* Get full object when desc is empty. */ + if(!isset($object->description) || (isset($object->description) && $object->description == '')) $object = $this->loadModel($objectType)->getByID($objectID); + foreach($oldObject as $index => $attribute) + { + if($index != 'description') $object->$index = $attribute; + } + + if(!isset($object->id) && !empty($objectID)) $object->id = $objectID; + $issue = $this->parseObjectToIssue($gitlabID, $projectID, $objectType, $object); + $apiRoot = $this->getApiRoot($gitlabID); + $url = sprintf($apiRoot, "/projects/{$projectID}/issues/{$issueID}"); + return json_decode(commonModel::http($url, $issue, $options = array(CURLOPT_CUSTOMREQUEST => 'PUT'))); + } + + /** + * Delete an issue by api. + * + * @param int $gitlabID + * @param int $projectID + * @param int $issueID + * @access public + * @return object + */ + public function apiDeleteIssue($gitlabID, $projectID, $issueID) + { + $apiRoot = $this->getApiRoot($gitlabID); + $url = sprintf($apiRoot, "/projects/{$projectID}/issues/{$issueID}"); + return json_decode(commonModel::http($url, null, array(CURLOPT_CUSTOMREQUEST => 'DELETE'))); + } + + /** + * Check webhook token by HTTP_X_GITLAB_TOKEN. + * + * @access public + * @return void + */ + public function webhookCheckToken() + { + $gitlab = $this->getByID($this->get->gitlab); + if($gitlab->private != $_SERVER["HTTP_X_GITLAB_TOKEN"]) die('Token error.'); + } + + /** + * Parse webhook body function. + * + * @param object $body + * @param int $gitlabID + * @access public + * @return object + */ + public function webhookParseBody($body, $gitlabID) + { + $type = zget($body, 'object_kind', ''); + if(!$type or !is_callable(array($this, "webhookParse{$type}"))) return false; + return call_user_func_array(array($this, "webhookParse{$type}"), array('body' => $body, $gitlabID)); + } + + /** + * Parse Webhook issue. + * + * @param object $body + * @param int $gitlabID + * @access public + * @return object + */ + public function WebhookParseIssue($body, $gitlabID) + { + $object = $this->webhookParseObject($body->labels); + if(empty($object)) return null; + + $issue = new stdclass; + $issue->action = $body->object_attributes->action . $body->object_kind; + $issue->issue = $body->object_attributes; + $issue->changes = $body->changes; + $issue->objectType = $object->type; + $issue->objectID = $object->id; + + $issue->issue->objectType = $object->type; + $issue->issue->objectID = $object->id; + + /* Parse markdown description to html. */ + $issue->issue->description = $this->app->loadClass('hyperdown')->makeHtml($issue->issue->description); + + if(!isset($this->config->gitlab->maps->{$object->type})) return false; + $issue->object = $this->issueToZentaoObject($issue->issue, $gitlabID, $body->changes); + return $issue; + } + + /** + * Webhook parse note. + * + * @param object $body + * @access public + * @return void + */ + public function webhookParseNote($body) + { + //@todo + } + + /** + * Webhook sync issue. + * + * @param object $issue + * @param int $objectType + * @param int $objectID + * @access public + * @return void + */ + public function webhookSyncIssue($gitlabID, $issue) + { + $tableName = zget($this->config->gitlab->objectTables, $issue->objectType, ''); + if($tableName) $this->dao->update($tableName)->data($issue->object)->where('id')->eq($issue->objectID)->exec(); + return !dao::isError(); + } + + /** + * Parse zentao object from labels. + * + * @param array $labels + * @access public + * @return object + */ + public function webhookParseObject($labels) + { + $object = null; + $objectType = ''; + foreach($labels as $label) + { + if(preg_match($this->config->gitlab->labelPattern->story, $label->title)) $objectType = 'story'; + if(preg_match($this->config->gitlab->labelPattern->task, $label->title)) $objectType = 'task'; + if(preg_match($this->config->gitlab->labelPattern->bug, $label->title)) $objectType = 'bug'; + + if($objectType) + { + list($prefix, $id) = explode('/', $label->title); + $object = new stdclass; + $object->id = $id; + $object->type = $objectType; + } + } + + return $object; + } + + /** + * Process webhook issue assign option. + * + * @param int $gitlabID + * @param object $issue + * @access public + * @return bool + */ + public function webhookAssignIssue($issue) + { + $tableName = zget($this->config->gitlab->objectTables, $issue->objectType, ''); + if(!$tableName) return false; + + $data = $issue->object; + $data->assignedDate = $issue->object->lastEditedDate; + $data->assignedTo = $issue->object->assignedTo; + + $this->dao->update($tableName)->data($data)->where('id')->eq($issue->objectID)->exec(); + if(dao::isError()) return false; + + $oldObject = $this->dao->findById($issue->objectID)->from($tableName)->fetch(); + $changes = common::createChanges($oldObject, $data); + $actionID = $this->loadModel('action')->create($issue->objectType, $issue->objectID, 'Assigned', "Assigned by webhook by gitlab issue : {$issue->issue->url}", $data->assignedTo); + $this->action->logHistory($actionID, $changes); + + return true; + } + + /** + * Process issue close option. + * + * @param int $gitlabID + * @param object $issue + * @access public + * @return bool + */ + public function webhookCloseIssue($issue) + { + $tableName = zget($this->config->gitlab->objectTables, $issue->objectType, ''); + if(!$tableName) return false; + + $data = $issue->object; + $data->assignedTo = 'closed'; + $data->status = 'closed'; + $data->closedBy = $issue->object->lastEditedBy; + $data->closedDate = $issue->object->lastEditedDate; + + $this->dao->update($tableName)->data($data)->where('id')->eq($issue->objectID)->exec(); + if(dao::isError()) return false; + + $oldObject = $this->dao->findById($issue->objectID)->from($tableName)->fetch(); + $changes = common::createChanges($oldObject, $data); + $actionID = $this->loadModel('action')->create($issue->objectType, $issue->objectID, 'Closed', "Closed by gitlab issue: {$issue->issue->url}."); + $this->action->logHistory($actionID, $changes); + return true; + } + + + /** + * Create zentao object label for gitlab project. + * + * @param int $gitlabID + * @param int $projectID + * @param string $objectType + * @param string $objectID + * @access public + * @return object + */ + public function createZentaoObjectLabel($gitlabID, $projectID, $objectType, $objectID) + { + $label = new stdclass; + $label->name = sprintf($this->config->gitlab->zentaoObjectLabel->name, $objectType, $objectID); + $label->color = $this->config->gitlab->zentaoObjectLabel->color->$objectType; + $label->description = common::getSysURL() . helper::createLink($objectType, 'view', "id={$objectID}"); + + return $this->apiCreateLabel($gitlabID, $projectID, $label); + } + + /** + * Create relationship between zentao product and gitlab project. + * + * @param int $gitlabID + * @param int $projectID + * @access public + * @return void + */ + public function saveProjectRelation($products, $gitlabID, $gitlabProjectID) + { + $programs = $this->dao->select('id,program')->from(TABLE_PRODUCT)->where('id')->in($products)->fetchPairs(); + + $relation = new stdclass; + $relation->execution = 0; + $relation->AType = 'gitlab'; + $relation->AID = $gitlabID; + $relation->AVersion = ''; + $relation->relation = 'interrated'; + $relation->BType = 'gitlabProject'; + $relation->BID = $gitlabProjectID; + $relation->BVersion = ''; + $relation->extra = ''; + + foreach($products as $product) + { + $relation->project = zget($programs, $product, 0); + $relation->product = $product; + $this->dao->replace(TABLE_RELATION)->data($relation)->exec(); + } + return true; + } + + /** + * Delete project relation. + * + * condition: when user deleting a repo. + * + * @param int $repoID + * @access public + * @return void + */ + public function deleteProjectRelation($repoID) + { + $repo = $this->dao->select('product,path as gitlabProjectID,client as gitlabID')->from(TABLE_REPO) + ->where('id')->eq($repoID) + ->andWhere('deleted')->eq(0) + ->fetch(); + if(empty($repo)) return false; + + $productIDList = explode(',', $repo->product); + foreach($productIDList as $product) + { + $this->dao->delete()->from(TABLE_RELATION) + ->where('product')->eq($product) + ->andWhere('AType')->eq('gitlab') + ->andWhere('BType')->eq('gitlabProject') + ->andWhere('relation')->eq('interrated') + ->andWhere('AID')->eq($repo->gitlabID) + ->andWhere('BID')->eq($repo->gitlabProjectID) + ->exec(); + } + } + + /** + * Create webhook for zentao. + * + * @param int $gitlabID + * @param int $projectID + * @access public + * @return bool + */ + public function initWebhooks($products, $gitlabID, $projectID) + { + $gitlab = $this->getByID($gitlabID); + $webhooks = $this->apiGetHooks($gitlabID, $projectID); + foreach($products as $index => $product) + { + $url = sprintf($this->config->gitlab->webhookURL, commonModel::getSysURL(), $product, $gitlabID); + foreach($webhooks as $webhook) if($webhook->url == $url) continue; + $response = $this->apiCreateHook($gitlabID, $projectID, $url); + } + + return true; + } + + /** + * Delete an issue from zentao and gitlab. + * + * @param int $gitlabID + * @param int $projectID + * @param string $objectType + * @param int $objectID + * @param int $issueID + * @access public + * @return void + */ + public function deleteIssue($objectType, $objectID, $issueID) + { + $object = $this->loadModel($objectType)->getByID($objectID); + $relation = $this->getRelationByObject($objectType, $objectID); + if(!empty($relation)) $this->dao->delete()->from(TABLE_RELATION)->where('id')->eq($relation->id)->exec(); + $this->apiDeleteIssue($relation->gitlabID, $relation->projectID, $issueID); + } + + /** + * Save synced issue to relation table. + * + * @param string $objectType + * @param object $object + * @param int $gitlab + * @param object $issue + * @access public + * @return void + */ + public function saveIssueRelation($objectType, $object, $gitlabID, $issue) + { + if(empty($issue->iid) or empty($issue->project_id)) return false; + + $relation = new stdclass; + $relation->product = zget($object, 'product', 0); + $relation->execution = zget($object, 'execution', 0); + $relation->AType = $objectType; + $relation->AID = $object->id; + $relation->AVersion = ''; + $relation->relation = 'gitlab'; + $relation->BType = 'issue'; + $relation->BID = $issue->iid; + $relation->BVersion = $issue->project_id; + $relation->extra = $gitlabID; + $this->dao->replace(TABLE_RELATION)->data($relation)->exec(); + } + + /** + * Save imported issue. + * + * @param int $gitlabID + * @param int $projectID + * @param string $objectType + * @param int $objectID + * @param object $issue + * @access public + * @return void + */ + public function saveImportedIssue($gitlabID, $projectID, $objectType, $objectID, $issue, $object) + { + $label = $this->createZentaoObjectLabel($gitlabID, $projectID, $objectType, $objectID); + $data = new stdclass; + if(isset($label->name)) $data->labels = $label->name; + + $apiRoot = $this->getApiRoot($gitlabID); + $url = sprintf($apiRoot, "/projects/{$projectID}/issues/{$issue->iid}"); + commonModel::http($url, $data, $options = array(CURLOPT_CUSTOMREQUEST => 'PUT')); + $this->saveIssueRelation($objectType, $object, $gitlabID, $issue); + } + + /** + * Parse task to issue. + * + * @param int $gitlabID + * @param int $gitlabProjectID + * @param object $task + * @access public + * @return object + */ + public function taskToIssue($gitlabID, $gitlabProjectID, $task) + { + $map = $this->config->gitlab->maps->task; + $issue = new stdclass; + $gitlabUsers = $this->getUserAccountIdPairs($gitlabID); + + foreach($map as $taskField => $config) + { + $value = ''; + list($field, $optionType, $options) = explode('|', $config); + if($optionType == 'field') $value = $task->$taskField; + if($optionType == 'userPairs') $value = zget($gitlabUsers, $task->$taskField); + if($optionType == 'configItems') + { + $value = zget($this->config->gitlab->$options, $task->$taskField, ''); + } + if($value) $issue->$field = $value; + } + + if($isset($issue->assignee_id) and $issue->assignee_id == 'closed') unset($issue->assignee_id); + + /* issue->state is null when creating it, we should put status_event when updating it. */ + if(isset($issue->state) and $issue->state == 'closed') $issue->state_event='close'; + if(isset($issue->state) and $issue->state == 'opened') $issue->state_event='reopen'; + + /* Append this object link in zentao to gitlab issue description */ + $zentaoLink = common::getSysURL() . helper::createLink('task', 'view', "taskID={$task->id}"); + $issue->description = $issue->description . "\n\n" . $zentaoLink; + + return $issue; + } + + /** + * Parse story to issue. + * + * @param int $gitlabID + * @param int $gitlabProjectID + * @param object $story + * @access public + * @return object + */ + public function storyToIssue($gitlabID, $gitlabProjectID, $story) + { + $map = $this->config->gitlab->maps->story; + $issue = new stdclass; + $gitlabUsers = $this->getUserAccountIdPairs($gitlabID); + if(empty($gitlabUsers)) return false; + + foreach($map as $storyField => $config) + { + $value = ''; + list($field, $optionType, $options) = explode('|', $config); + if($optionType == 'field') $value = $story->$storyField; + if($optionType == 'fields') $value = $story->$storyField . "\n\n" . $story->$options; + if($optionType == 'userPairs') + { + $value = zget($gitlabUsers, $story->$storyField); + } + if($optionType == 'configItems') + { + $value = zget($this->config->gitlab->$options, $story->$storyField, ''); + } + if($value) $issue->$field = $value; + } + + if($issue->assignee_id == 'closed') unset($issue->assignee_id); + + /* issue->state is null when creating it, we should put status_event when updating it. */ + if(isset($issue->state) and $issue->state == 'closed') $issue->state_event='close'; + if(isset($issue->state) and $issue->state == 'opened') $issue->state_event='reopen'; + + /* Append this object link in zentao to gitlab issue description */ + $zentaoLink = common::getSysURL() . helper::createLink('story', 'view', "storyID={$story->id}"); + $issue->description = $issue->description . "\n\n" . $zentaoLink; + + return $issue; + } + + /** + * Parse bug to issue. + * + * @param int $gitlabID + * @param int $projectID + * @param object $story + * @access public + * @return object + */ + public function bugToIssue($gitlabID, $projectID, $bug) + { + $map = $this->config->gitlab->maps->bug; + $issue = new stdclass; + $gitlabUsers = $this->getUserAccountIdPairs($gitlabID); + if(empty($gitlabUsers)) return false; + + foreach($map as $bugField => $config) + { + $value = ''; + list($field, $optionType, $options) = explode('|', $config); + if($optionType == 'field') $value = $bug->$bugField; + if($optionType == 'fields') $value = $bug->$bugField . "\n\n" . $bug->$options; + if($optionType == 'userPairs') + { + $value = zget($gitlabUsers, $bug->$bugField); + } + if($optionType == 'configItems') + { + $value = zget($this->config->gitlab->$options, $bug->$bugField, ''); + } + if($value) $issue->$field = $value; + } + + if($issue->assignee_id == 'closed') unset($issue->assignee_id); + + /* issue->state is null when creating it, we should put status_event when updating it. */ + if(isset($issue->state) and $issue->state == 'closed') $issue->state_event='close'; + if(isset($issue->state) and $issue->state == 'opened') $issue->state_event='reopen'; + + /* Append this object link in zentao to gitlab issue description */ + $zentaoLink = common::getSysURL() . helper::createLink('bug', 'view', "bugID={$bug->id}"); + $issue->description = $issue->description . "\n\n" . $zentaoLink; + + return $issue; + } + + /** + * Parse zentao object to issue. object can be task, bug and story. + * + * @param int $gitlabID + * @param int $projectID + * @param string $objectType + * @param object $object + * @access public + * @return object + */ + public function parseObjectToIssue($gitlabID, $projectID, $objectType, $object) + { + $gitlabUsers = $this->getUserAccountIdPairs($gitlabID); + if(empty($gitlabUsers)) return false; + $issue = new stdclass; + $map = $this->config->gitlab->maps->$objectType; + foreach($map as $objectField => $config) + { + $value = ''; + list($field, $optionType, $options) = explode('|', $config); + if($optionType == 'field') $value = $object->$objectField; + if($optionType == 'fields') $value = $object->$objectField . "\n\n" . $object->$options; + if($optionType == 'userPairs') + { + $value = zget($gitlabUsers, $object->$objectField); + } + if($optionType == 'configItems') + { + $value = zget($this->config->gitlab->$options, $object->$objectField, ''); + } + if($value) $issue->$field = $value; + } + if(isset($issue->assignee_id) and $issue->assignee_id == 'closed') unset($issue->assignee_id); + + /* issue->state is null when creating it, we should put status_event when updating it. */ + if(isset($issue->state) and $issue->state == 'closed') $issue->state_event='close'; + if(isset($issue->state) and $issue->state == 'opened') $issue->state_event='reopen'; + + /* Append this object link in zentao to gitlab issue description */ + $zentaoLink = common::getSysURL() . helper::createLink($objectType, 'view', "id={$object->id}"); + if(strpos($issue->description, $zentaoLink) == false) $issue->description = $issue->description . "\n\n" . $zentaoLink; + + return $issue; + } + + /** + * Parse issue to zentao object. + * + * @param object $issue + * @param int $gitlabID + * @access public + * @return object + */ + public function issueToZentaoObject($issue, $gitlabID, $changes = null) + { + if(!isset($this->config->gitlab->maps->{$issue->objectType})) return null; + + if(isset($changes->assignees)) $changes->assignee_id = true; + $maps = $this->config->gitlab->maps->{$issue->objectType}; + $gitlabUsers = $this->getUserIdAccountPairs($gitlabID); + + $object = new stdclass; + $object->id = $issue->objectID; + foreach($maps as $zentaoField => $config) + { + $value = ''; + list($gitlabField, $optionType, $options) = explode('|', $config); + if(!isset($changes->$gitlabField) and $object->id != 0) continue; + if($optionType == 'field') $value = $issue->$gitlabField; + if($optionType == 'fields') $value = $issue->$gitlabField; // TODO(dingguodong) not implemented. + if($options == 'date') $value = $value ? date('Y-m-d', strtotime($value)) : '0000-00-00'; + if($options == 'datetime') $value = $value ? date('Y-m-d H:i:s', strtotime($value)) : '0000-00-00 00:00:00'; + if($optionType == 'userPairs' and isset($issue->$gitlabField)) $value = zget($gitlabUsers, $issue->$gitlabField); + if($optionType == 'configItems' and isset($issue->$gitlabField)) $value = array_search($issue->$gitlabField, $this->config->gitlab->$options); + if($value) $object->$zentaoField = $value; + + if($gitlabField == "description") $object->$zentaoField .= "
" . $issue->web_url; + } + return $object; + } +} diff --git a/module/gitlab/view/binduser.html.php b/module/gitlab/view/binduser.html.php new file mode 100644 index 0000000000..a0cf0dd17f --- /dev/null +++ b/module/gitlab/view/binduser.html.php @@ -0,0 +1,63 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
+
+

gitlab->bindUser;?>

+
+
+
+ + + + + + + + + + zentaoAccount)) continue;?> + + + + + + + + zentaoAccount)) continue;?> + + + + + + + + + + + + +
gitlab->gitlabAccount;?>gitlab->zentaoAccount;?>
avatar, "height=40");?> + realname;?> +
account . " <" . $gitlabUser->email . ">"; ?> +
id]", $userPairs, '', "class='form-control select chosen'" );?>
avatar, "height=40");?> + realname;?> +
account . " <" . $gitlabUser->email . ">";?> +
id]", $userPairs, $gitlabUser->zentaoAccount, "class='form-control select chosen'" );?>
+ save);?> + +
+
+
+
+ diff --git a/module/gitlab/view/browse.html.php b/module/gitlab/view/browse.html.php new file mode 100644 index 0000000000..dd3311b1dd --- /dev/null +++ b/module/gitlab/view/browse.html.php @@ -0,0 +1,53 @@ + + * @package gitlab + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
+
+ + + + recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}"; ?> + + + + + + + + $gitlab): ?> + + + + + + + + +
gitlab->id); ?>gitlab->name); ?>gitlab->url); ?>actions; ?>
name; ?>url; ?> + createLink('gitlab', 'delete', "gitlabID=$id"), '', 'hiddenwin', "title='{$lang->gitlab->delete}' class='btn'"); + ?> +
+ + + +
+
+ diff --git a/module/gitlab/view/create.html.php b/module/gitlab/view/create.html.php new file mode 100644 index 0000000000..1675041925 --- /dev/null +++ b/module/gitlab/view/create.html.php @@ -0,0 +1,48 @@ + + * @package gitlab + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
+
+
+
+

gitlab->lblCreate;?>

+
+
+ + + + + + + + + + + + + + + + + + +
gitlab->name; ?>gitlab->placeholder->name}'"); ?>
gitlab->url; ?>gitlab->placeholder->url}'"); ?>
gitlab->token;?>gitlab->placeholder->token}'");?>
+ + +
+
+
+
+
+ diff --git a/module/gitlab/view/edit.html.php b/module/gitlab/view/edit.html.php new file mode 100644 index 0000000000..81d37d99f6 --- /dev/null +++ b/module/gitlab/view/edit.html.php @@ -0,0 +1,47 @@ + + * @package gitlab + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
+
+
+
+

gitlab->edit; ?>

+
+
+ + + + + + + + + + + + + + + + + + + +
gitlab->name; ?>name, "class='form-control' placeholder='{$lang->gitlab->placeholder->name}'"); ?>
gitlab->url; ?>url, "class='form-control' placeholder='{$lang->gitlab->placeholder->url}'"); ?>
gitlab->token;?>token, "class='form-control' placeholder='{$lang->gitlab->placeholder->token}'");?>
+ +
+
+
+
+
+ diff --git a/module/gitlab/view/importissue.html.php b/module/gitlab/view/importissue.html.php new file mode 100644 index 0000000000..98ca018d8f --- /dev/null +++ b/module/gitlab/view/importissue.html.php @@ -0,0 +1,57 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
+
+

gitlab->importIssue;?>

+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
gitlab->gitlabIssue;?>gitlab->objectType;?>product->common;?>execution->common;?>
web_url}' target='_blank'>$issue->title"; ?>iid}]", $objectTypes, '', "class='form-control select chosen'" );?>iid}]", $products, '', "class='form-control select chosen'" );?>iid}]", '', '', "class='form-control select chosen'" );?>
+ save);?> + +
+
+
+ + gitlab->noImportableIssues; ?> + +
+ diff --git a/module/jenkins/control.php b/module/jenkins/control.php index 70c030d2a4..1a77a02d27 100644 --- a/module/jenkins/control.php +++ b/module/jenkins/control.php @@ -111,7 +111,7 @@ class jenkins extends control { if($confim != 'yes') die(js::confirm($this->lang->jenkins->confirmDelete, inlink('delete', "id=$id&confirm=yes"))); - $this->jenkins->delete(TABLE_JENKINS, $id); + $this->jenkins->delete(TABLE_PIPELINE, $id); die(js::reload('parent')); } diff --git a/module/jenkins/model.php b/module/jenkins/model.php index 2d12469a3f..48eaa13a1b 100644 --- a/module/jenkins/model.php +++ b/module/jenkins/model.php @@ -21,10 +21,7 @@ class jenkinsModel extends model */ public function getByID($id) { - $jenkins = $this->dao->select('*')->from(TABLE_JENKINS)->where('id')->eq($id)->fetch(); - $jenkins->password = base64_decode($jenkins->password); - - return $jenkins; + return $this->loadModel('pipeline')->getByID($id); } /** @@ -37,11 +34,7 @@ class jenkinsModel extends model */ public function getList($orderBy = 'id_desc', $pager = null) { - return $this->dao->select('*')->from(TABLE_JENKINS) - ->where('deleted')->eq('0') - ->orderBy($orderBy) - ->page($pager) - ->fetchAll('id'); + return $this->loadModel('pipeline')->getList('jenkins', $orderBy, $pager); } /** @@ -51,11 +44,7 @@ class jenkinsModel extends model */ public function getPairs() { - $jenkins = $this->dao->select('id,name')->from(TABLE_JENKINS) - ->where('deleted')->eq('0') - ->orderBy('id')->fetchPairs('id', 'name'); - $jenkins = array('' => '') + $jenkins; - return $jenkins; + return $this->loadModel('pipeline')->getPairs('jenkins'); } /** @@ -94,21 +83,7 @@ class jenkinsModel extends model */ public function create() { - $jenkins = fixer::input('post') - ->add('createdBy', $this->app->user->account) - ->add('createdDate', helper::now()) - ->skipSpecial('url,token,account,password') - ->get(); - - $jenkins->password = base64_encode($jenkins->password); - - $this->dao->insert(TABLE_JENKINS)->data($jenkins) - ->batchCheck($this->config->jenkins->create->requiredFields, 'notempty') - ->batchCheck("url", 'URL') - ->autoCheck() - ->exec(); - if(dao::isError()) return false; - return $this->dao->lastInsertId(); + return $this->loadModel('pipeline')->create('jenkins'); } /** @@ -120,20 +95,6 @@ class jenkinsModel extends model */ public function update($id) { - $jenkins = fixer::input('post') - ->add('editedBy', $this->app->user->account) - ->add('editedDate', helper::now()) - ->skipSpecial('url,token,account,password') - ->get(); - - $jenkins->password = base64_encode($jenkins->password); - - $this->dao->update(TABLE_JENKINS)->data($jenkins) - ->batchCheck($this->config->jenkins->edit->requiredFields, 'notempty') - ->batchCheck("url", 'URL') - ->autoCheck() - ->where('id')->eq($id) - ->exec(); - return !dao::isError(); + return $this->loadModel('pipeline')->update($id); } } diff --git a/module/job/model.php b/module/job/model.php index 5d19e6c298..c948ada4e7 100644 --- a/module/job/model.php +++ b/module/job/model.php @@ -35,7 +35,7 @@ class jobModel extends model { return $this->dao->select('t1.*, t2.name as repoName, t3.name as jenkinsName')->from(TABLE_JOB)->alias('t1') ->leftJoin(TABLE_REPO)->alias('t2')->on('t1.repo=t2.id') - ->leftJoin(TABLE_JENKINS)->alias('t3')->on('t1.jkHost=t3.id') + ->leftJoin(TABLE_PIPELINE)->alias('t3')->on('t1.jkHost=t3.id') ->where('t1.deleted')->eq('0') ->orderBy($orderBy) ->page($pager) @@ -290,7 +290,7 @@ class jobModel extends model { $job = $this->dao->select('t1.id,t1.name,t1.product,t1.repo,t1.jkJob,t1.triggerType,t1.atTime,t1.customParam,t2.name as jenkinsName,t2.url,t2.account,t2.token,t2.password') ->from(TABLE_JOB)->alias('t1') - ->leftJoin(TABLE_JENKINS)->alias('t2')->on('t1.jkHost=t2.id') + ->leftJoin(TABLE_PIPELINE)->alias('t2')->on('t1.jkHost=t2.id') ->where('t1.id')->eq($id) ->fetch(); if(!$job) return false; diff --git a/module/pipeline/config.php b/module/pipeline/config.php new file mode 100644 index 0000000000..29bc37b2ea --- /dev/null +++ b/module/pipeline/config.php @@ -0,0 +1,5 @@ +pipeline->create = new stdclass(); +$config->pipeline->edit = new stdclass(); +$config->pipeline->create->requiredFields = 'name,url,type'; +$config->pipeline->edit->requiredFields = 'name,url,type'; diff --git a/module/pipeline/lang/zh-cn.php b/module/pipeline/lang/zh-cn.php new file mode 100644 index 0000000000..b949e9998d --- /dev/null +++ b/module/pipeline/lang/zh-cn.php @@ -0,0 +1,7 @@ +pipeline->id = 'ID'; +$lang->pipeline->name = '名称'; +$lang->pipeline->url = '服务地址'; +$lang->pipeline->token = 'Token'; +$lang->pipeline->account = '用户名'; +$lang->pipeline->password = '密码'; diff --git a/module/pipeline/model.php b/module/pipeline/model.php new file mode 100644 index 0000000000..5e7fdba12e --- /dev/null +++ b/module/pipeline/model.php @@ -0,0 +1,123 @@ + + * @package product + * @version $Id: $ + * @link http://www.zentao.net + */ + +class pipelineModel extends model +{ + /** + * Get a pipeline by id. + * + * @param int $id + * @access public + * @return object + */ + public function getByID($id) + { + $pipeline = $this->dao->select('*')->from(TABLE_PIPELINE)->where('id')->eq($id)->fetch(); + if($pipeline) + { + $pipeline->password = base64_decode($pipeline->password); + } + return $pipeline; + } + + /** + * Get pipeline list. + * + * @param string $type jenkins|gitlab + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getList($type = 'jenkins', $orderBy = 'id_desc', $pager = null) + { + return $this->dao->select('*')->from(TABLE_PIPELINE) + ->where('deleted')->eq('0') + ->AndWhere('type')->eq($type) + ->orderBy($orderBy) + ->page($pager) + ->fetchAll('id'); + } + + /** + * Get pipeline pairs + * + * @return array + */ + public function getPairs($type) + { + $pipeline = $this->dao->select('id,name')->from(TABLE_PIPELINE) + ->where('deleted')->eq('0') + ->AndWhere('type')->eq($type) + ->orderBy('id')->fetchPairs('id', 'name'); + $pipeline = array('' => '') + $pipeline; + return $pipeline; + } + + /** + * Create a pipeline. + * + * @access public + * @return bool + */ + public function create($type) + { + $pipeline = fixer::input('post') + ->add('type', $type) + ->add('private',md5(rand(10,113450))) + ->add('createdBy', $this->app->user->account) + ->add('createdDate', helper::now()) + ->skipSpecial('url,token,account,password') + ->get(); + if($type == 'gitlab') $pipeline->url = rtrim($pipeline->url, '/'); + + if(isset($pipeline->password)) $pipeline->password = base64_encode($pipeline->password); + + $this->dao->insert(TABLE_PIPELINE)->data($pipeline) + ->batchCheck($this->config->pipeline->create->requiredFields, 'notempty') + ->batchCheck("url", 'URL') + ->autoCheck() + ->exec(); + if(dao::isError()) return false; + + return $this->dao->lastInsertId(); + } + + /** + * Update a pipeline. + * + * @param int $id + * @access public + * @return bool + */ + public function update($id) + { + $pipeline = fixer::input('post') + ->add('editedBy', $this->app->user->account) + ->add('editedDate', helper::now()) + ->skipSpecial('url,token,account,password') + ->get(); + + $type = $this->dao->select('type')->from(TABLE_PIPELINE)->where('id')->eq($id)->fetch('type'); + if($type == 'gitlab') $pipeline->url = rtrim($pipeline->url, '/'); + if(isset($pipeline->password)) $pipeline->password = base64_encode($pipeline->password); + + $this->dao->update(TABLE_PIPELINE)->data($pipeline) + ->batchCheck($this->config->pipeline->edit->requiredFields, 'notempty') + ->batchCheck("url", 'URL') + ->autoCheck() + ->where('id')->eq($id) + ->exec(); + + return !dao::isError(); + } +} diff --git a/module/repo/control.php b/module/repo/control.php index 4a747571c8..c12c786b4e 100644 --- a/module/repo/control.php +++ b/module/repo/control.php @@ -129,11 +129,12 @@ class repo extends control $this->app->loadLang('action'); - $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->create; - $this->view->position[] = $this->lang->repo->create; - $this->view->groups = $this->loadModel('group')->getPairs(); - $this->view->users = $this->loadModel('user')->getPairs('noletter|noempty|nodeleted'); - $this->view->products = $this->loadModel('product')->getProductPairsByProject($objectID); + $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->create; + $this->view->position[] = $this->lang->repo->create; + $this->view->groups = $this->loadModel('group')->getPairs(); + $this->view->users = $this->loadModel('user')->getPairs('noletter|noempty|nodeleted'); + $this->view->products = $this->loadModel('product')->getProductPairsByProject($objectID); + $this->view->gitlabHosts = $this->loadModel('gitlab')->getPairs(); $this->display(); } @@ -168,21 +169,24 @@ class repo extends control if($repo->SCM == 'Gitlab') { - $projects = $this->repo->getGitlabProjects($repo->client, $repo->password); + $projects = $this->loadModel('gitlab')->apiGetProjects($repo->gitlab, $repo->password); + $options = array(); - foreach($projects as $project) $options[$project->id] = $project->name . ':' . $project->http_url_to_repo; + foreach($projects as $project) $options[$project->id] = $project->name_with_namespace; + $this->view->projects = $options; } - $repo->repoType = $repo->id . '-' . $repo->SCM; - $this->view->repo = $repo; - $this->view->repoID = $repoID; - $this->view->objectID = $objectID; - $this->view->groups = $this->loadModel('group')->getPairs(); - $this->view->users = $this->loadModel('user')->getPairs('noletter|noempty|nodeleted'); - $this->view->products = $objectID ? $this->loadModel('product')->getProductPairsByProject($objectID) : $this->loadModel('product')->getPairs(); + $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->edit; + $repo->repoType = $repo->id . '-' . $repo->SCM; + $this->view->repo = $repo; + $this->view->repoID = $repoID; + $this->view->objectID = $objectID; + $this->view->groups = $this->loadModel('group')->getPairs(); + $this->view->users = $this->loadModel('user')->getPairs('noletter|noempty|nodeleted'); + $this->view->products = $objectID ? $this->loadModel('product')->getProductPairsByProject($objectID) : $this->loadModel('product')->getPairs(); + $this->view->gitlabHosts = $this->loadModel('gitlab')->getPairs(); - $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->edit; $this->view->position[] = html::a(inlink('maintain'), $this->lang->repo->common); $this->view->position[] = $this->lang->repo->edit; @@ -205,6 +209,9 @@ class repo extends control die(js::confirm($this->lang->repo->notice->delete, $this->repo->createLink('delete', "repoID=$repoID&objectID=$objectID&confirm=yes"))); } + /* Delete project relation for gitlab type. */ + $this->loadModel('gitlab')->deleteProjectRelation($repoID); + $relationID = $this->dao->select('id')->from(TABLE_RELATION)->where('extra')->eq($repoID)->fetch(); if($relationID) { @@ -1105,25 +1112,24 @@ class repo extends control /** * Ajax get gitlab projects. * - * @param string $host - * @param string $token + * @param int $host * @access public * @return void */ - public function ajaxGetGitlabProjects($host, $token) + public function ajaxGetGitlabProjects($host, $projectIdList = '') { - $host = helper::safe64Decode($host); - $projects = $this->repo->getGitlabProjects($host, $token); - - if(!$projects) $this->send(array('message' => array())); - - $options = ""; - foreach($projects as $project) - { - $options .= ""; - } - die($options); - } + $projects = $this->loadModel('gitlab')->apiGetProjects($host); + + if(!$projects) $this->send(array('message' => array())); + $projectIdList = $projectIdList ? explode(',', $projectIdList) : null; + $options = ""; + foreach($projects as $project) + { + if(!empty($projectIdList) and $project and !in_array($project->id, $projectIdList)) continue; + $options .= ""; + } + die($options); + } /** * Ajax get branch drop menu. diff --git a/module/repo/js/create.js b/module/repo/js/create.js index 0af0d52404..2b9bff72e5 100644 --- a/module/repo/js/create.js +++ b/module/repo/js/create.js @@ -7,12 +7,11 @@ $(function() $form.css('min-height', $form.height()); }) - $('#gitlabHost, #gitlabToken').change(function() + $('#gitlabHost').change(function() { - host = Base64.encode($('#gitlabHost').val()); - token = $('#gitlabToken').val(); - url = createLink('repo', 'ajaxgetgitlabprojects', "host=" + host + '&token=' + token); - if(host == '' || token == '') return false; + host = $('#gitlabHost').val(); + url = createLink('repo', 'ajaxgetgitlabprojects', "host=" + host); + if(host == '') return false; $.get(url, function(response) { diff --git a/module/repo/js/edit.js b/module/repo/js/edit.js index cc79cbb2a5..e500686f04 100644 --- a/module/repo/js/edit.js +++ b/module/repo/js/edit.js @@ -7,12 +7,11 @@ $(function() $form.css('min-height', $form.height()); }) - $('#gitlabHost, #gitlabToken').change(function() + $('#gitlabHost').change(function() { - host = Base64.encode($('#gitlabHost').val()); - token = $('#gitlabToken').val(); - url = createLink('repo', 'ajaxgetgitlabprojects', "host=" + host + '&token=' + token); - if(host == '' || token == '') return false; + host = $('#gitlabHost').val(); + if(host == '') return false; + url = createLink('repo', 'ajaxgetgitlabprojects', "host=" + host); $.get(url, function(response) { @@ -28,7 +27,6 @@ $(function() $('#name').val($option.data('name')); $(this).chosen().trigger("chosen:updated"); }); - }); function scmChanged(scm) diff --git a/module/repo/lang/zh-cn.php b/module/repo/lang/zh-cn.php index 25fbf8e9a0..8dbdf28398 100644 --- a/module/repo/lang/zh-cn.php +++ b/module/repo/lang/zh-cn.php @@ -178,8 +178,9 @@ $lang->repo->error->encoding = "编码可能错误,请更换编码重试 $lang->repo->error->deleted = "删除版本库失败,当前版本库有提交记录与设计关联"; $lang->repo->error->clientPath = "客户端安装目录不能有空格!"; -$lang->repo->syncTips = '请参照这里,设置版本库定时同步。'; -$lang->repo->encodingsTips = "提交日志的编码,可以用逗号连接起来的多个,比如utf-8。"; +$lang->repo->syncTips = '请参照这里,设置版本库定时同步。'; +$lang->repo->encodingsTips = "提交日志的编码,可以用逗号连接起来的多个,比如utf-8。"; +$lang->repo->pathTipsForGitlab = "GitLab 项目URL"; $lang->repo->example = new stdclass(); $lang->repo->example->client = new stdclass(); diff --git a/module/repo/model.php b/module/repo/model.php index 864987d800..7be326bdc2 100644 --- a/module/repo/model.php +++ b/module/repo/model.php @@ -128,7 +128,8 @@ class repoModel extends model $repos = $this->dao->select('*')->from(TABLE_REPO) ->where('deleted')->eq('0') ->orderBy($orderBy) - ->page($pager)->fetchAll('id'); + ->page($pager) + ->fetchAll('id'); /* Get products. */ $productIdList = $this->loadModel('product')->getProductIDByProject($projectID, false); @@ -152,6 +153,8 @@ class repoModel extends model if(!$hasPriv) unset($repos[$i]); } } + + if($repo->SCM == 'Gitlab') $repo = $this->processGitlab($repo); } return $repos; @@ -195,15 +198,16 @@ class repoModel extends model if(!$this->checkConnection()) return false; $data = fixer::input('post') - ->setIf($this->post->SCM == 'Gitlab', 'password', $this->post->gitlabToken) + ->setIf($this->post->SCM == 'Gitlab', 'password', '') + ->setIf($this->post->SCM == 'Gitlab', 'path', $this->post->gitlabProject) ->setIf($this->post->SCM == 'Gitlab', 'client', $this->post->gitlabHost) - ->setIf($this->post->SCM == 'Gitlab', 'extra', $this->post->gitlabProject) ->skipSpecial('path,client,account,password') ->setDefault('product', '') ->join('product', ',') ->get(); - if($this->post->SCM == 'Gitlab') $data->path = sprintf($this->config->repo->gitlab->apiPath, $data->gitlabHost, $this->post->gitlabProject); + /* see this file in 1783G: processGitlab::$repo->path */ + if($this->post->SCM == 'Gitlab') $data->path = $this->post->gitlabProject; $data->acl = empty($data->acl) ? '' : json_encode($data->acl); @@ -226,6 +230,13 @@ class repoModel extends model if(!dao::isError()) $this->rmClientVersionFile(); + if($this->post->SCM == 'Gitlab') + { + $this->loadModel("gitlab")->saveProjectRelation($this->post->product, $this->post->gitlabHost, $this->post->gitlabProject); + + /* create webhook for zentao */ + $this->loadModel("gitlab")->initWebhooks($this->post->product, $this->post->gitlabHost, $this->post->gitlabProject); + } return $this->dao->lastInsertID(); } @@ -241,9 +252,9 @@ class repoModel extends model $repo = $this->getRepoByID($id); $data = fixer::input('post') - ->setIf($this->post->SCM == 'Gitlab', 'password', $this->post->gitlabToken) + ->setIf($this->post->SCM == 'Gitlab', 'password', '') + ->setIf($this->post->SCM == 'Gitlab', 'path', $this->post->gitlabProject) ->setIf($this->post->SCM == 'Gitlab', 'client', $this->post->gitlabHost) - ->setIf($this->post->SCM == 'Gitlab', 'extra', $this->post->gitlabProject) ->setDefault('client', 'svn') ->setDefault('prefix', $repo->prefix) ->setDefault('product', '') @@ -256,6 +267,7 @@ class repoModel extends model $data->path = sprintf($this->config->repo->gitlab->apiPath, $data->gitlabHost, $this->post->gitlabProject); $data->prefix = ''; } + if($data->path != $repo->path) $data->synced = 0; $data->acl = empty($data->acl) ? '' : json_encode($data->acl); @@ -276,6 +288,7 @@ class repoModel extends model if($data->client != $repo->client and !$this->checkClient()) return false; if(!$this->checkConnection()) return false; + if($data->encrypt == 'base64') $data->password = base64_encode($data->password); $this->dao->update(TABLE_REPO)->data($data, $skip = 'gitlabHost,gitlabToken,gitlabProject') ->batchCheck($this->config->repo->edit->requiredFields, 'notempty') @@ -286,12 +299,15 @@ class repoModel extends model $this->rmClientVersionFile(); + if($repo->SCM == 'Gitlab') $this->loadModel("gitlab")->saveProjectRelation($this->post->product, $this->post->gitlabHost, $this->post->gitlabProject); if($repo->path != $data->path) { $this->dao->delete()->from(TABLE_REPOHISTORY)->where('repo')->eq($id)->exec(); $this->dao->delete()->from(TABLE_REPOFILES)->where('repo')->eq($id)->exec(); - return false; + if($repo->SCM == 'Gitlab') $this->loadModel("gitlab")->initWebhooks($this->post->product, $this->post->gitlabHost, $this->post->gitlabProject); + return false; } + return true; } @@ -375,6 +391,7 @@ class repoModel extends model if(!$repo) return false; if($repo->encrypt == 'base64') $repo->password = base64_decode($repo->password); + if($repo->SCM == 'Gitlab') $reps = $this->processGitlab($repo); $repo->acl = json_decode($repo->acl); return $repo; } @@ -1750,4 +1767,16 @@ class repoModel extends model return $allResults; } + + public function processGitlab($repo) + { + $gitlab = $this->loadModel('gitlab')->getByID($repo->client); + if(!$gitlab) return $repo; + $repo->gitlab = $gitlab->id; + $repo->project = $repo->path; + $repo->path = sprintf($this->config->repo->gitlab->apiPath, $gitlab->url, $repo->path); + $repo->client = $gitlab->url; + $repo->password = $gitlab->token; + return $repo; + } } diff --git a/module/repo/view/create.html.php b/module/repo/view/create.html.php index 446da06b4e..49a7a42c35 100644 --- a/module/repo/view/create.html.php +++ b/module/repo/view/create.html.php @@ -34,11 +34,7 @@ repo->gitlabHost;?> - repo->placeholder->gitlabHost}'");?> - - - repo->gitlabToken;?> - + repo->placeholder->gitlabHost}'");?> repo->gitlabProject;?> diff --git a/module/repo/view/edit.html.php b/module/repo/view/edit.html.php index 15f6d76846..cda78336be 100644 --- a/module/repo/view/edit.html.php +++ b/module/repo/view/edit.html.php @@ -34,21 +34,15 @@ repo->type; ?> repo->scmList, $repo->SCM, "onchange='scmChanged(this.value)' class='form-control'"); ?> - - repo->syncTips; ?> - + repo->syncTips; ?> repo->gitlabHost;?> - client, "class='form-control' placeholder='{$lang->repo->placeholder->gitlabHost}'");?> - - - repo->gitlabToken;?> - password, "class='form-control'");?> + gitlab, "class='form-control' placeholder='{$lang->repo->placeholder->gitlabHost}'");?> repo->gitlabProject;?> - extra, "class='form-control chosen'");?> + project, "class='form-control chosen'");?> repo->name; ?> @@ -59,8 +53,8 @@ repo->path; ?> path, "class='form-control'"); ?> - repo->example->path->git;?> - repo->example->path->svn;?> + repo->example->path->git;?> + repo->example->path->svn;?> @@ -72,8 +66,8 @@ repo->client;?> client, "class='form-control'")?> - repo->example->client->git;?> - repo->example->client->svn;?> + repo->example->client->git;?> + repo->example->client->svn;?> diff --git a/module/repo/view/maintain.html.php b/module/repo/view/maintain.html.php index 2cb0aced53..cd358fb3ce 100644 --- a/module/repo/view/maintain.html.php +++ b/module/repo/view/maintain.html.php @@ -25,7 +25,7 @@ repo->id); ?> repo->type); ?> repo->name); ?> - repo->product); ?> + repo->product); ?> repo->path; ?> actions; ?> @@ -45,10 +45,11 @@ } ?> - path; ?> + path; ?> id&objectID=$objectID", '', 'list', 'edit'); + common::printIcon('repo', 'edit', "repoID=$repo->id&objectID=$objectID", '', 'list', 'edit'); + if(strtolower($repo->SCM) == "gitlab") common::printIcon('gitlab', 'importIssue', "repo={$repo->id}", '', 'list', 'link'); // disable button instead of hiding it. if(common::hasPriv('repo', 'delete')) echo html::a($this->createLink('repo', 'delete', "repoID=$repo->id&objectID=$objectID"), '', 'hiddenwin', "title='{$lang->repo->delete}' class='btn'"); ?> diff --git a/module/story/config.php b/module/story/config.php index 8e63ffc7d6..8150edbb1c 100644 --- a/module/story/config.php +++ b/module/story/config.php @@ -4,10 +4,10 @@ $config->story = new stdclass(); $config->story->batchCreate = 10; $config->story->affectedFixedNum = 7; $config->story->needReview = 1; +$config->story->removeFields = 'objectTypeList,productList,executionList,gitlabID,gitlabProjectID,execution'; $config->story->batchClose = new stdclass(); $config->story->batchClose->columns = 10; - $config->story->create = new stdclass(); $config->story->edit = new stdclass(); $config->story->change = new stdclass(); diff --git a/module/story/control.php b/module/story/control.php index 25754add29..54b879c137 100644 --- a/module/story/control.php +++ b/module/story/control.php @@ -295,6 +295,13 @@ class story extends control $this->view->customFields = $customFields; $this->view->showFields = $this->config->story->custom->createFields; + $allGitlabs = $this->loadModel('gitlab')->getPairs(); + $executionID = $objectID; + $gitlabProjects = $this->loadModel('gitlab')->getProjectsByExecution($executionID); + foreach($allGitlabs as $id => $name) if($id and !isset($gitlabProjects[$id])) unset($allGitlabs[$id]); + $this->view->gitlabList = $allGitlabs; + $this->view->gitlabProjects = $gitlabProjects; + $this->view->title = $product->name . $this->lang->colon . $this->lang->story->create; $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$productID&branch=$branch"), $product->name); $this->view->position[] = $this->lang->story->common; @@ -983,6 +990,10 @@ class story extends control } else { + /* Delete related issue in gitlab. */ + $relation = $this->loadModel('gitlab')->getRelationByObject('story', $storyID); + if(!empty($relation)) $this->loadModel('gitlab')->deleteIssue('story', $storyID, $relation->issueID); + $this->story->delete(TABLE_STORY, $storyID); if($story->parent > 0) { diff --git a/module/story/js/create.js b/module/story/js/create.js index 473c38bd5f..4f410a918b 100644 --- a/module/story/js/create.js +++ b/module/story/js/create.js @@ -34,3 +34,22 @@ function refreshPlan() $(window).unload(function(){ if(blockID) window.parent.refreshBlock($('#block' + blockID)); }); + +$(document).ready(function() +{ + $('#gitlab').change(function() + { + host = $('#gitlab').val(); + if(host == '') return false; + projects = ''; + $.each(gitlabProjects[host], function(id, obj){projects = projects + ',' + obj.gitlabProject}); + url = createLink('repo', 'ajaxgetgitlabprojects', "host=" + host + "&projects=" + projects); + + $.get(url, function(response) + { + $('#gitlabProject').html('').append(response); + $('#gitlabProject').chosen().trigger("chosen:updated");; + }); + + }); +}); diff --git a/module/story/lang/zh-cn.php b/module/story/lang/zh-cn.php index e4629a5735..faefd42ec8 100644 --- a/module/story/lang/zh-cn.php +++ b/module/story/lang/zh-cn.php @@ -406,3 +406,5 @@ $lang->story->categoryList['safe'] = '安全'; $lang->story->categoryList['experience'] = '体验'; $lang->story->categoryList['improve'] = '改进'; $lang->story->categoryList['other'] = '其他'; + +$lang->story->sync2Gitlab = '同步到Gitlab'; diff --git a/module/story/model.php b/module/story/model.php index 9b3143ad49..98d1cc5b20 100644 --- a/module/story/model.php +++ b/module/story/model.php @@ -217,7 +217,7 @@ class storyModel extends model /* Check repeat story. */ $result = $this->loadModel('common')->removeDuplicate('story', $story, "product={$story->product}"); - if($result['stop']) return array('status' => 'exists', 'id' => $result['duplicate']); + if(isset($result['stop']) and $result['stop']) return array('status' => 'exists', 'id' => $result['duplicate']); if($this->checkForceReview()) $story->status = 'draft'; if($story->status == 'draft') $story->stage = $this->post->plan > 0 ? 'planned' : 'wait'; @@ -234,7 +234,7 @@ class storyModel extends model $requiredFields = trim($requiredFields, ','); - $this->dao->insert(TABLE_STORY)->data($story, 'spec,verify')->autoCheck()->batchCheck($requiredFields, 'notempty')->exec(); + $this->dao->insert(TABLE_STORY)->data($story, 'spec,verify,gitlab,gitlabProject')->autoCheck()->batchCheck($requiredFields, 'notempty')->exec(); if(!dao::isError()) { $storyID = $this->dao->lastInsertID(); @@ -340,11 +340,56 @@ class storyModel extends model /* Callback the callable method to process the related data for object that is transfered to story. */ if($from && is_callable(array($this, $this->config->story->fromObjects[$from]['callback']))) call_user_func(array($this, $this->config->story->fromObjects[$from]['callback']), $storyID); + /* push this story to gitlab issue */ + $object = $this->getByID($storyID); + $this->loadModel('gitlab')->apiCreateIssue($this->post->gitlab, $this->post->gitlabProject, 'story', $storyID, $object); + return array('status' => 'created', 'id' => $storyID); } return false; } + /** + * Create story from gitlab issue. + * + * @param object $story + * @param int $executionID + * @access public + * @return int + */ + public function createStoryFromGitlabIssue($story, $executionID) + { + $story->status = 'active'; + $story->stage = 'projected'; + $story->openedBy = $this->app->user->account; + $story->version = 1; + $story->assignedDate = isset($story->assignedTo) ? helper::now() : 0; + + if(isset($story->execution)) unset($story->execution); + + $requiredFields = $this->config->story->create->requiredFields; + $this->dao->insert(TABLE_STORY)->data($story, 'spec,verify,gitlab,gitlabProject')->autoCheck()->batchCheck($requiredFields, 'notempty')->exec(); + if(!dao::isError()) + { + $storyID = $this->dao->lastInsertID(); + + $data = new stdclass(); + $data->story = $storyID; + $data->version = 1; + $data->title = $story->title; + $data->spec = $story->spec; + $data->verify = $story->spec; + $this->dao->insert(TABLE_STORYSPEC)->data($data)->exec(); + + /* Link story to execution. */ + $this->linkStory($executionID, $story->product, $storyID); + + return $storyID; + } + + return false; + } + /** * Batch create stories. * @@ -442,7 +487,6 @@ class storyModel extends model dao::$errors['message'][] = sprintf($this->lang->error->notempty, $this->lang->story->$field); return false; } - $data[$i] = $story; } @@ -654,6 +698,11 @@ class storyModel extends model } $this->file->updateObjectID($this->post->uid, $storyID, 'story'); + + /* update story to gitlab issue. */ + $relation = $this->loadModel('gitlab')->getRelationByObject('story', $storyID); + if($relation) $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'story', $story, $storyID); + return common::createChanges($oldStory, $story); } } @@ -833,6 +882,10 @@ class storyModel extends model } } + /* update story to gitlab issue. */ + $relation = $this->loadModel('gitlab')->getRelationByObject('story', $storyID); + if($relation) $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'story', $story, $storyID); + unset($oldStory->parent); unset($story->parent); return common::createChanges($oldStory, $story); @@ -1163,6 +1216,10 @@ class storyModel extends model /* Update story sort of plan when story plan has changed. */ if($oldStory->plan != $story->plan) $this->updateStoryOrderOfPlan($storyID, $story->plan, $oldStory->plan); + /* update story to gitlab issue. */ + $relation = $this->loadModel('gitlab')->getRelationByObject('story', $storyID); + if(!empty($relation)) $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'story', $story, $storyID); + $this->executeHooks($storyID); if($story->type == 'story') $this->batchChangeStage(array($storyID), $story->stage); if($story->closedReason == 'done') $this->loadModel('score')->create('story', 'close'); @@ -1409,7 +1466,7 @@ class storyModel extends model $oldStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); $now = helper::now(); $story = fixer::input('post') - ->add('assignedTo', 'closed') + ->add('assignedTo', 'closed') ->add('status', 'closed') ->add('stage', 'closed') ->setDefault('lastEditedBy', $this->app->user->account) @@ -1428,6 +1485,14 @@ class storyModel extends model ->checkIF($story->closedReason == 'duplicate', 'duplicateStory', 'notempty') ->where('id')->eq($storyID)->exec(); + $relation = $this->loadModel('gitlab')->getRelationByObject('story', $storyID); + + if(!empty($relation)) + { + $currentIssue = $this->loadModel('gitlab')->apiGetSingleIssue($relation->gitlabID, $relation->projectID, $relation->issueID); + if($currentIssue->state != 'closed') $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'story', $story, $storyID); + } + /* Update parent story status. */ if($oldStory->parent > 0) $this->updateParentStatus($storyID, $oldStory->parent); $this->setStage($storyID); @@ -1494,6 +1559,13 @@ class storyModel extends model if($oldStory->parent > 0) $this->updateParentStatus($storyID, $oldStory->parent); $this->setStage($storyID); $allChanges[$storyID] = common::createChanges($oldStory, $story); + + $relation = $this->loadModel('gitlab')->getRelationByObject('story', $storyID); + if(!empty($relation)) + { + $currentIssue = $this->loadModel('gitlab')->apiGetSingleIssue($relation->gitlabID, $relation->projectID, $relation->issueID); + if($currentIssue->state != 'closed') $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'story', $story, $storyID); + } } else { @@ -1769,8 +1841,13 @@ class storyModel extends model $story->assignedTo = $assignedTo; $story->assignedDate = $now; - $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq((int)$storyID)->exec(); - if(!dao::isError()) return common::createChanges($oldStory, $story); + if(!dao::isError()) + { + $relation = $this->loadModel('gitlab')->getRelationByObject('story', $storyID); + $story->assignee_id = $this->loadModel('gitlab')->getGitlabUserID($relation->gitlabID, $story->assignedTo); + if($story->assignee_id != '') $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'story', $story, $storyID); + return common::createChanges($oldStory, $story); + } return false; } @@ -1799,7 +1876,16 @@ class storyModel extends model $story->assignedDate = $now; $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq((int)$storyID)->exec(); - if(!dao::isError()) $allChanges[$storyID] = common::createChanges($oldStory, $story); + if(!dao::isError()) + { + $relation = $this->loadModel('gitlab')->getRelationByObject('story', $storyID); + $story->assignee_id = $this->loadModel('gitlab')->getGitlabUserID($relation->gitlabID, $story->assignedTo); + if($story->assignee_id != '') $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'story', $story, $storyID); + + /* Push this story to gitlab issue. */ + $relation = $this->loadModel('gitlab')->getRelationByObject('story', $storyID); + if(!empty($relation)) $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'story', $story, $storyID); + } } return $allChanges; } @@ -1834,6 +1920,10 @@ class storyModel extends model /* Update parent story status. */ if($oldStory->parent > 0) $this->updateParentStatus($storyID, $oldStory->parent); + /* Push this story to gitlab issue. */ + $relation = $this->loadModel('gitlab')->getRelationByObject('story', $storyID); + if(!empty($relation)) $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'story', $story, $storyID); + return common::createChanges($oldStory, $story); } diff --git a/module/story/view/create.html.php b/module/story/view/create.html.php index 8e46723c86..f09edc694b 100644 --- a/module/story/view/create.html.php +++ b/module/story/view/create.html.php @@ -17,6 +17,7 @@ story->placeholder); ?> + @@ -251,6 +252,14 @@ + + + story->sync2Gitlab;?> + + + + + diff --git a/module/task/config.php b/module/task/config.php index 5db5eb88ae..67fe4ffabc 100644 --- a/module/task/config.php +++ b/module/task/config.php @@ -26,6 +26,7 @@ $config->task->editor->activate = array('id' => 'comment', 'tools' => 'simpleToo $config->task->editor->cancel = array('id' => 'comment', 'tools' => 'simpleTools'); $config->task->editor->pause = array('id' => 'comment', 'tools' => 'simpleTools'); +$config->task->removeFields = 'objectTypeList,productList,executionList,gitlabID,gitlabProjectID,product'; $config->task->exportFields = ' id, execution, module, story, name, desc, diff --git a/module/task/control.php b/module/task/control.php index 93989a71fa..31f084cbf2 100644 --- a/module/task/control.php +++ b/module/task/control.php @@ -92,12 +92,14 @@ class task extends control setcookie('lastTaskModule', (int)$this->post->module, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, false); if($this->post->execution) $executionID = (int)$this->post->execution; + + /* Create task here. */ $tasksID = $this->task->create($executionID); if(dao::isError()) { $response['result'] = 'fail'; $response['message'] = dao::getError(); - $this->send($response); + return $this->send($response); } /* if the count of tasksID is 1 then check exists. */ @@ -108,7 +110,7 @@ class task extends control { $response['locate'] = $this->createLink('task', 'view', "taskID={$taskID['id']}"); $response['message'] = sprintf($this->lang->duplicate, $this->lang->task->common); - $this->send($response); + return $this->send($response); } } @@ -132,41 +134,41 @@ class task extends control $this->executeHooks($taskID); /* Return task id when call the API. */ - if($this->viewType == 'json') $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $taskID)); + if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $taskID)); /* If link from no head then reload. */ - if(isonlybody()) $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'parent')); + if(isonlybody()) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'parent')); /* Locate the browser. */ if($this->app->getViewType() == 'xhtml') { $taskLink = $this->createLink('task', 'view', "taskID=$taskID"); $response['locate'] = $taskLink; - $this->send($response); + return $this->send($response); } if($this->post->after == 'continueAdding') { $response['message'] = $this->lang->task->successSaved . $this->lang->task->afterChoices['continueAdding']; $response['locate'] = $this->createLink('task', 'create', "executionID=$executionID&storyID={$this->post->story}&moduleID=$moduleID"); - $this->send($response); + return $this->send($response); } elseif($this->post->after == 'toTaskList') { setcookie('moduleBrowseParam', 0, 0, $this->config->webRoot, '', $this->config->cookieSecure, false); $taskLink = $this->createLink('execution', 'task', "executionID=$executionID&status=unclosed¶m=0&orderBy=id_desc"); $response['locate'] = $taskLink; - $this->send($response); + return $this->send($response); } elseif($this->post->after == 'toStoryList') { $response['locate'] = $storyLink; - $this->send($response); + return $this->send($response); } else { $response['locate'] = $taskLink; - $this->send($response); + return $this->send($response); } } @@ -215,6 +217,12 @@ class task extends control foreach(explode(',', $this->config->task->customCreateFields) as $field) $customFields[$field] = $this->lang->task->$field; if($execution->type == 'ops') unset($customFields['story']); + $allGitlabs = $this->loadModel('gitlab')->getPairs(); + $gitlabProjects = $this->loadModel('gitlab')->getProjectsByExecution($executionID); + foreach($allGitlabs as $id => $name) if($id and !isset($gitlabProjects[$id])) unset($allGitlabs[$id]); + $this->view->gitlabList = $allGitlabs; + $this->view->gitlabProjects = $gitlabProjects; + $this->view->customFields = $customFields; $this->view->showFields = $this->config->task->custom->createFields; $this->view->showAllModule = $showAllModule; @@ -230,6 +238,7 @@ class task extends control $this->view->members = $members; $this->view->blockID = $blockID; $this->view->moduleOptionMenu = $moduleOptionMenu; + $this->display(); } @@ -285,17 +294,17 @@ class task extends control if(!empty($_POST)) { $mails = $this->task->batchCreate($executionID); - if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError())); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); $taskIDList = array(); foreach($mails as $mail) $taskIDList[] = $mail->taskID; /* Return task id list when call the API. */ - if($this->viewType == 'json') $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'idList' => $taskIDList)); + if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'idList' => $taskIDList)); /* Locate the browser. */ - if(!empty($iframe)) $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'parent')); - $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $taskLink)); + if(!empty($iframe)) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'parent')); + return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $taskLink)); } /* Set Custom*/ @@ -372,7 +381,7 @@ class task extends control if($comment == false) { $changes = $this->task->update($taskID); - if(dao::isError()) die(js::error(dao::getError())); + if(dao::isError()) return print(js::error(dao::getError())); $files = $this->loadModel('file')->saveUpload('task', $taskID); } @@ -399,9 +408,10 @@ class task extends control } } } + if(defined('RUN_MODE') && RUN_MODE == 'api') { - die(array('status' => 'success', 'data' => $taskID)); + return $this->send(array('status' => 'success', 'data' => $taskID)); } else { @@ -552,7 +562,10 @@ class task extends control { $this->loadModel('action'); $changes = $this->task->assign($taskID); + + if($this->viewType == 'json') return $this->send(array('result' => 'fail', 'message' => dao::getError())); if(dao::isError()) die(js::error(dao::getError())); + $actionID = $this->action->create('task', $taskID, 'Assigned', $this->post->comment, $this->post->assignedTo); $this->action->logHistory($actionID, $changes); @@ -564,6 +577,9 @@ class task extends control $task = $this->task->getByID($taskID); + $relation = $this->loadModel('gitlab')->getRelationByObject('task', $taskID); + $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'task', $task, $taskID); + $members = $this->loadModel('user')->getTeamMemberPairs($executionID, 'execution', 'nodeleted'); /* Compute next assignedTo. */ @@ -743,7 +759,12 @@ class task extends control { $this->loadModel('action'); $changes = $this->task->start($taskID); - if(dao::isError()) die(js::error(dao::getError())); + + if(dao::isError()) + { + if($this->viewType == 'json') return $this->send(array('result' => 'fail', 'message' => dao::getError())); + die(js::error(dao::getError())); + } if($this->post->comment != '' or !empty($changes)) { @@ -898,7 +919,11 @@ class task extends control { $this->loadModel('action'); $changes = $this->task->finish($taskID); - if(dao::isError()) die(js::error(dao::getError())); + if(dao::isError()) + { + if($this->viewType == 'json') return $this->send(array('result' => 'fail', 'message' => dao::getError())); + die(js::error(dao::getError())); + } $files = $this->loadModel('file')->saveUpload('task', $taskID); $task = $this->task->getById($taskID); @@ -927,7 +952,7 @@ class task extends control if(isonlybody()) die(js::closeModal('parent.parent', 'this')); if(defined('RUN_MODE') && RUN_MODE == 'api') { - die(array('status' => 'success', 'data' => $taskID)); + return $this->send(array('result' => 'success', 'data' => $taskID)); } else { @@ -1264,14 +1289,18 @@ class task extends control public function delete($executionID, $taskID, $confirm = 'no') { $task = $this->task->getById($taskID); - if($task->parent < 0) die(js::alert($this->lang->task->cannotDeleteParent)); + if($task->parent < 0) return print(js::alert($this->lang->task->cannotDeleteParent)); if($confirm == 'no') { - die(js::confirm($this->lang->task->confirmDelete, inlink('delete', "executionID=$executionID&taskID=$taskID&confirm=yes"))); + return print(js::confirm($this->lang->task->confirmDelete, inlink('delete', "executionID=$executionID&taskID=$taskID&confirm=yes"))); } else { + /* Delete related issue in gitlab. */ + $relation = $this->loadModel('gitlab')->getRelationByObject('task', $taskID); + if(!empty($relation)) $this->loadModel('gitlab')->deleteIssue('task', $taskID, $relation->issueID); + $this->task->delete(TABLE_TASK, $taskID); if($task->parent > 0) { @@ -1283,7 +1312,7 @@ class task extends control $this->executeHooks($taskID); - die(js::locate($this->session->taskList, 'parent')); + return print(js::locate($this->session->taskList, 'parent')); } } diff --git a/module/task/js/create.js b/module/task/js/create.js index a0e2460ed1..f47496be8d 100644 --- a/module/task/js/create.js +++ b/module/task/js/create.js @@ -479,6 +479,23 @@ $(document).ready(function() $('#moduleIdBox #module').val(moduleID).attr('onchange', "setStories(this.value, " + executionID + ")").chosen(); }); }); + + $('#gitlab').change(function() + { + host = $('#gitlab').val(); + if(host == '') return false; + projects = ''; + $.each(gitlabProjects[host], function(id, obj){projects = projects + ',' + obj.gitlabProject}); + url = createLink('repo', 'ajaxgetgitlabprojects', "host=" + host + "&projects=" + projects); + + $.get(url, function(response) + { + $('#gitlabProject').html('').append(response); + $('#gitlabProject').chosen().trigger("chosen:updated");; + }); + + }); + }); $(document).on('click', '#testStory_chosen,#story_chosen', function() diff --git a/module/task/lang/zh-cn.php b/module/task/lang/zh-cn.php index ba81bcb2e5..a0119d0fe1 100644 --- a/module/task/lang/zh-cn.php +++ b/module/task/lang/zh-cn.php @@ -67,6 +67,7 @@ $lang->task->storyVersion = "{$lang->SRCommon}版本"; $lang->task->color = '标题颜色'; $lang->task->name = '任务名称'; $lang->task->type = '任务类型'; +$lang->task->sync2Gitlab = '同步到Gitlab'; $lang->task->pri = '优先级'; $lang->task->mailto = '抄送给'; $lang->task->estimate = '最初预计'; diff --git a/module/task/model.php b/module/task/model.php index 381ec5cfc6..4d3f7b1f2b 100644 --- a/module/task/model.php +++ b/module/task/model.php @@ -22,6 +22,7 @@ class taskModel extends model */ public function create($executionID) { + if($this->post->estimate < 0) { dao::$errors[] = $this->lang->task->error->recordMinus; @@ -64,7 +65,7 @@ class taskModel extends model ->cleanINT('execution,story,module') ->stripTags($this->config->task->editor->create['id'], $this->config->allowedTags) ->join('mailto', ',') - ->remove('after,files,labels,assignedTo,uid,storyEstimate,storyDesc,storyPri,team,teamEstimate,teamMember,multiple,teams,contactListMenu,selectTestStory,testStory,testPri,testEstStarted,testDeadline,testAssignedTo,testEstimate') + ->remove('after,files,labels,assignedTo,uid,storyEstimate,storyDesc,storyPri,team,teamEstimate,teamMember,multiple,teams,contactListMenu,selectTestStory,testStory,testPri,testEstStarted,testDeadline,testAssignedTo,testEstimate,sync') ->add('version', 1) ->get(); @@ -109,17 +110,21 @@ class taskModel extends model /* Fix Bug #2466 */ if($this->post->multiple) $task->assignedTo = ''; - $this->dao->insert(TABLE_TASK)->data($task) + $this->dao->insert(TABLE_TASK)->data($task, $skip = 'gitlab,gitlabProject') ->autoCheck() ->batchCheck($requiredFields, 'notempty') ->checkIF($task->estimate != '', 'estimate', 'float') ->checkIF(!helper::isZeroDate($task->deadline), 'deadline', 'ge', $task->estStarted) ->exec(); - + if(dao::isError()) return false; $taskID = $this->dao->lastInsertID(); + /* Sync this task to gitlab issue. */ + $object = $this->getByID($taskID); + $this->loadModel('gitlab')->apiCreateIssue($this->post->gitlab, $this->post->gitlabProject, 'task', $taskID, $object); + /* Mark design version.*/ if(isset($task->design) && !empty($task->design)) { @@ -379,6 +384,11 @@ class taskModel extends model if(dao::isError()) return false; $taskID = $this->dao->lastInsertID(); + + /* Sync this task to gitlab issue. */ + $object = $this->getByID($taskID); + $this->loadModel('gitlab')->apiCreateIssue($this->post->gitlab, $this->post->gitlabProject,'task', $taskID, $object); + $taskSpec = new stdClass(); $taskSpec->task = $taskID; $taskSpec->version = $task->version; @@ -447,6 +457,37 @@ class taskModel extends model return $mails; } + /** + * Create task from gitlab issue. + * + * @param object $task + * @param int $executionID + * @access public + * @return int + */ + public function createTaskFromGitlabIssue($task, $executionID) + { + $task->version = 1; + $task->openedBy = $this->app->user->account; + $task->assignedDate = isset($task->assignedTo) ? helper::now() : 0; + $task->story = 0; + $task->module = 0; + $task->estimate = 0; + $task->estStarted = '0000-00-00'; + $task->left = 1; + $task->type = 'devel'; + + $this->dao->insert(TABLE_TASK)->data($task, $skip = 'id,product') + ->autoCheck() + ->batchCheck($this->config->task->create->requiredFields, 'notempty') + ->checkIF(!helper::isZeroDate($task->deadline), 'deadline', 'ge', $task->estStarted) + ->exec(); + + if(dao::isError()) return false; + + return $this->dao->lastInsertID(); + } + /** * Compute parent task working hours. * @@ -940,6 +981,10 @@ class taskModel extends model ->batchCheckIF($task->closedReason == 'cancel', 'finishedBy, finishedDate', 'empty') ->where('id')->eq((int)$taskID)->exec(); + /* update task to gitlab issue. */ + $relation = $this->loadModel('gitlab')->getRelationByObject('task', $taskID); + if(!empty($relation)) $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'task', $task, $taskID); + if(!dao::isError()) { /* Mark design version.*/ @@ -1173,6 +1218,14 @@ class taskModel extends model $tasks[$taskID] = $task; } + $issues = $this->loadModel('gitlab')->getIssueListByObjects('task', $taskID); + foreach($tasks as $taskID => $task) + { + $issue = zget($issues, $taskID, 0); + if(!$issue) continue; + $this->loadModel('gitlab')->apiUpdateIssue($issue->gitlabID, $issue->projectID, $issue->issueID, $task); + } + /* Check field not empty. */ foreach($tasks as $taskID => $task) { @@ -1242,6 +1295,10 @@ class taskModel extends model if($task->status == 'done') $this->loadModel('score')->create('task', 'finish', $taskID); if($task->status == 'closed') $this->loadModel('score')->create('task', 'close', $taskID); $allChanges[$taskID] = common::createChanges($oldTask, $task); + + /* update task to gitlab issue. */ + $relation = $this->loadModel('gitlab')->getRelationByObject('task', $taskID); + if(!empty($relation)) $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'task', $task, $taskID); } else { @@ -1327,6 +1384,10 @@ class taskModel extends model ->autoCheck() ->check('left', 'float') ->where('id')->eq($taskID)->exec(); + + $task = $this->getById($taskID); + $relation = $this->loadModel('gitlab')->getRelationByObject('task', $taskID); + if(!empty($relation)) $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'task', $task, $taskID); if(!dao::isError()) return common::createChanges($oldTask, $task); } @@ -1663,6 +1724,13 @@ class taskModel extends model ->where('id')->eq((int)$taskID) ->exec(); + $relation = $this->loadModel('gitlab')->getRelationByObject('task', $taskID); + if(!empty($relation)) + { + $currentIssue = $this->loadModel('gitlab')->apiGetSingleIssue($relation->gitlabID, $relation->projectID, $relation->issueID); + if($currentIssue->state != 'closed') $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'task', $task, $taskID); + } + if($oldTask->parent > 0) $this->updateParentStatus($taskID); if($oldTask->story) $this->loadModel('story')->setStage($oldTask->story); if($task->status == 'done' && !dao::isError()) $this->loadModel('score')->create('task', 'finish', $taskID); @@ -1719,6 +1787,13 @@ class taskModel extends model $this->dao->update(TABLE_TASK)->data($task)->autoCheck()->where('id')->eq((int)$taskID)->exec(); + $relation = $this->loadModel('gitlab')->getRelationByObject('task', $taskID); + if(!empty($relation)) + { + $currentIssue = $this->loadModel('gitlab')->apiGetSingleIssue($relation->gitlabID, $relation->projectID, $relation->issueID); + if($currentIssue->state != 'closed') $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'bug', $task, $taskID); + } + if(!dao::isError()) { if($oldTask->parent > 0) $this->updateParentStatus($taskID); @@ -1762,6 +1837,14 @@ class taskModel extends model $this->dao->update(TABLE_TASK)->set('assignedTo=openedBy')->where('parent')->eq((int)$taskID)->exec(); } if($oldTask->story) $this->loadModel('story')->setStage($oldTask->story); + + $relation = $this->loadModel('gitlab')->getRelationByObject('task', $taskID); + if(!empty($relation)) + { + $currentIssue = $this->loadModel('gitlab')->apiGetSingleIssue($relation->gitlabID, $relation->projectID, $relation->issueID); + if($currentIssue->state != 'closed') $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'task', $task, $taskID); + } + if(!dao::isError()) return common::createChanges($oldTask, $task); } @@ -1826,6 +1909,10 @@ class taskModel extends model $this->computeWorkingHours($taskID); } if($oldTask->story) $this->loadModel('story')->setStage($oldTask->story); + + $relation = $this->loadModel('gitlab')->getRelationByObject('task', $taskID); + if(!empty($relation)) $this->loadModel('gitlab')->apiUpdateIssue($relation->gitlabID, $relation->projectID, $relation->issueID, 'task', $task, $taskID); + if(!dao::isError()) return common::createChanges($oldTask, $task); } diff --git a/module/task/view/create.html.php b/module/task/view/create.html.php index 492f20a3d4..060395a804 100644 --- a/module/task/view/create.html.php +++ b/module/task/view/create.html.php @@ -15,6 +15,7 @@ id));?> +
@@ -239,6 +240,13 @@ + + + task->sync2Gitlab;?> + + + + task->afterSubmit;?> task->afterChoices, !empty($task->id) ? 'toTaskList' : 'continueAdding');?> diff --git a/module/upgrade/model.php b/module/upgrade/model.php index 6d3111ef7a..2f5d6c5489 100644 --- a/module/upgrade/model.php +++ b/module/upgrade/model.php @@ -5082,4 +5082,30 @@ class upgradeModel extends model $this->dao->delete()->from(TABLE_GROUP)->where('id')->in(array_keys($projectAdmins))->exec(); return true; } + + public function processGitlabRepo() + { + $repoList = $this->dao->select('*')->from(TABLE_REPO)->where('SCM')->eq('Gitlab')->fetchAll(); + foreach($repoList as $repo) + { + /* Create gitlab from repo. */ + $gitlab = new stdclass; + $gitlab->type = 'gitlab'; + $gitlab->name = $repo->client; + $gitlab->token = base64_decode($repo->password); + $gitlab->private = md5(uniqid()); + $this->dao->insert(TABLE_PIPELINE)->data($gitlab)->exec(); + + $gitlabID = $this->dao->lastInsertID(); + + preg_match('/projects\/(\d+)\/repository/', $repo->path, $matches); + $gitlabProject = $matches[1]; + $products = explode(',', $repo->product); + $this->loadModel('gitlab')->saveProjectRelation($products, $gitlabID, $gitlabProject); + + $this->dao->update(TABLE_REPO)->set('client')->eq($gitlabID)->set('path')->eq($gitlabProject)->where('id')->eq($repo->id)->exec(); + + $this->loadModel("gitlab")->initWebhooks($products, $gitlabID, $gitlabProject); + } + } } diff --git a/www/api.php b/www/api.php index 6b299d2cce..a6d46cc923 100644 --- a/www/api.php +++ b/www/api.php @@ -18,7 +18,8 @@ define('RUN_MODE', 'api'); ob_start(); /* Load the framework. */ -include '../framework/router.class.php'; +include '../framework/api/router.class.php'; +include '../framework/api/entry.class.php'; include '../framework/control.class.php'; include '../framework/model.class.php'; include '../framework/helper.class.php'; @@ -27,13 +28,13 @@ include '../framework/helper.class.php'; $startTime = getTime(); /* Instance the app. */ -$app = router::createApp('pms', dirname(dirname(__FILE__)), 'router'); +$app = router::createApp('pms', dirname(dirname(__FILE__)), 'api'); /* Run the app. */ $common = $app->loadCommon(); /* Check entry. */ -$common->checkEntry(); +if(!$app->version) $common->checkEntry(); $common->loadConfigFromDB(); /* Set default params. */ @@ -41,18 +42,10 @@ $config->requestType = 'GET'; $config->default->view = 'json'; $app->parseRequest(); -$common->checkPriv(); +if(!$app->version) $common->checkPriv(); $app->loadModule(); -$output = json_decode(ob_get_clean()); -$data = new stdClass(); -$data->status = isset($output->status) ? $output->status : $output->result; -if(isset($output->message)) $data->message = $output->message; -if(isset($output->data)) $data->data = json_decode($output->data); -$output = json_encode($data); - -unset($_SESSION['ENTRY_CODE']); -unset($_SESSION['VALID_ENTRY']); +$output = ob_get_clean(); /* Flush the buffer. */ -echo helper::removeUTF8Bom($output); +echo helper::removeUTF8Bom($app->formatData($output));