zen.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. <?php
  2. /**
  3. * The zen file of gitlab module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
  6. * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Gang Zeng<zenggang@easycorp.ltd>
  8. * @package gitlab
  9. * @link https://www.zentao.net
  10. */
  11. class gitlabZen extends gitlab
  12. {
  13. /**
  14. * 检查绑定用户是否重复。
  15. * Check bind user repeat.
  16. *
  17. * @param array $zentaoUsers
  18. * @param array $userPairs
  19. * @access protected
  20. * @return array
  21. */
  22. protected function checkUserRepeat($zentaoUsers, $userPairs)
  23. {
  24. $accountList = array();
  25. $repeatUsers = array();
  26. foreach($zentaoUsers as $openID => $user)
  27. {
  28. if(empty($user)) continue;
  29. if(isset($accountList[$user])) $repeatUsers[] = zget($userPairs, $user);
  30. $accountList[$user] = $openID;
  31. }
  32. if(count($repeatUsers)) return array('result' => 'fail', 'message' => sprintf($this->lang->gitlab->bindUserError, join(',', array_unique($repeatUsers))));
  33. return array('result' => 'success');
  34. }
  35. /**
  36. * 绑定用户。
  37. * Bind user.
  38. *
  39. * @param int $gitlabID
  40. * @param array $users
  41. * @param array $gitlabNames
  42. * @param array $zentaoUsers
  43. * @access protected
  44. * @return void
  45. */
  46. protected function bindUsers($gitlabID, $users, $gitlabNames, $zentaoUsers)
  47. {
  48. $user = new stdclass;
  49. $user->providerID = $gitlabID;
  50. $user->providerType = 'gitlab';
  51. $oldUsers = $this->dao->select('*')->from(TABLE_OAUTH)->where('providerType')->eq($user->providerType)->andWhere('providerID')->eq($user->providerID)->fetchAll('openID');
  52. foreach($users as $openID => $account)
  53. {
  54. $existAccount = isset($oldUsers[$openID]) ? $oldUsers[$openID] : '';
  55. if($existAccount and $existAccount->account != $account)
  56. {
  57. $this->dao->delete()
  58. ->from(TABLE_OAUTH)
  59. ->where('openID')->eq($openID)
  60. ->andWhere('providerType')->eq($user->providerType)
  61. ->andWhere('providerID')->eq($user->providerID)
  62. ->exec();
  63. $this->loadModel('action')->create('gitlabuser', $openID, 'unbind', '', sprintf($this->lang->gitlab->bindDynamic, $gitlabNames[$openID], $zentaoUsers[$existAccount->account]->realname));
  64. }
  65. if(!$existAccount or $existAccount->account != $account)
  66. {
  67. if(!$account) continue;
  68. $user->account = $account;
  69. $user->openID = $openID;
  70. $this->dao->insert(TABLE_OAUTH)->data($user)->exec();
  71. $this->loadModel('action')->create('gitlabuser', $openID, 'bind', '', sprintf($this->lang->gitlab->bindDynamic, $gitlabNames[$openID], $zentaoUsers[$account]->realname));
  72. }
  73. }
  74. }
  75. /**
  76. * 记录webhook日志。
  77. * Record webhook logs.
  78. *
  79. * @param string $input
  80. * @param object $result
  81. * @access protected
  82. * @return void
  83. */
  84. protected function recordWebhookLogs($input, $result)
  85. {
  86. $logFile = $this->app->getLogRoot() . 'webhook.'. date('Ymd') . '.log.php';
  87. if(!file_exists($logFile)) file_put_contents($logFile, '<?php die(); ?' . '>');
  88. $fh = @fopen($logFile, 'a');
  89. if($fh)
  90. {
  91. fwrite($fh, date('Ymd H:i:s') . ": " . $this->app->getURI() . "\n");
  92. fwrite($fh, "JSON: \n " . $input . "\n");
  93. fwrite($fh, "Parsed object: {$result->issue->objectType} :\n " . print_r($result->object, true) . "\n");
  94. fclose($fh);
  95. }
  96. }
  97. /**
  98. * 获取gitlab组新增、删除、更新成员数据。
  99. * Get group added、deleted、updated member data.
  100. *
  101. * @param array $currentMembers
  102. * @param array $newMembers
  103. * @access protected
  104. * @return array
  105. */
  106. protected function getGroupMemberData($currentMembers, $newMembers)
  107. {
  108. /* Get the updated,deleted data. */
  109. $addedMembers = $deletedMembers = $updatedMembers = array();
  110. foreach($currentMembers as $currentMember)
  111. {
  112. $memberID = $currentMember->id;
  113. if(empty($newMembers[$memberID]))
  114. {
  115. $deletedMembers[] = $memberID;
  116. }
  117. else
  118. {
  119. if($newMembers[$memberID]->access_level != $currentMember->access_level or $newMembers[$memberID]->expires_at != $currentMember->expires_at)
  120. {
  121. $updatedData = new stdClass();
  122. $updatedData->user_id = $memberID;
  123. $updatedData->access_level = $newMembers[$memberID]->access_level;
  124. $updatedData->expires_at = $newMembers[$memberID]->expires_at;
  125. $updatedMembers[] = $updatedData;
  126. }
  127. }
  128. }
  129. /* Get the added data. */
  130. foreach($newMembers as $id => $newMember)
  131. {
  132. $exist = false;
  133. foreach($currentMembers as $currentMember)
  134. {
  135. if($currentMember->id == $id)
  136. {
  137. $exist = true;
  138. break;
  139. }
  140. }
  141. if($exist == false)
  142. {
  143. $addedData = new stdClass();
  144. $addedData->user_id = $id;
  145. $addedData->access_level = $newMembers[$id]->access_level;
  146. $addedData->expires_at = $newMembers[$id]->expires_at;
  147. $addedMembers[] = $addedData;
  148. }
  149. }
  150. return array($addedMembers, $deletedMembers, $updatedMembers);
  151. }
  152. /**
  153. * 获取项目成员数据。
  154. * Get project member data.
  155. *
  156. * @param array $gitlabCurrentMembers
  157. * @param array $newGitlabMembers
  158. * @param array $bindedUsers
  159. * @param array $accounts
  160. * @param array $originalUsers
  161. * @access protected
  162. * @return array
  163. */
  164. protected function getProjectMemberData($gitlabCurrentMembers, $newGitlabMembers, $bindedUsers, $accounts, $originalUsers)
  165. {
  166. $addedMembers = $updatedMembers = $deletedMembers = array();
  167. /* Get the updated data. */
  168. foreach($gitlabCurrentMembers as $gitlabCurrentMember)
  169. {
  170. $memberID = isset($gitlabCurrentMember->id) ? $gitlabCurrentMember->id : 0;
  171. if(!isset($newGitlabMembers[$memberID])) continue;
  172. if($newGitlabMembers[$memberID]->access_level != $gitlabCurrentMember->access_level or $newGitlabMembers[$memberID]->expires_at != $gitlabCurrentMember->expires_at)
  173. {
  174. $updatedData = new stdClass();
  175. $updatedData->user_id = $memberID;
  176. $updatedData->access_level = $newGitlabMembers[$memberID]->access_level;
  177. $updatedData->expires_at = $newGitlabMembers[$memberID]->expires_at;
  178. $updatedMembers[] = $updatedData;
  179. }
  180. }
  181. /* Get the added data. */
  182. foreach($newGitlabMembers as $id => $newMember)
  183. {
  184. $exist = false;
  185. foreach($gitlabCurrentMembers as $gitlabCurrentMember)
  186. {
  187. if($gitlabCurrentMember->id == $id)
  188. {
  189. $exist = true;
  190. break;
  191. }
  192. }
  193. if($exist == false)
  194. {
  195. $addedData = new stdClass();
  196. $addedData->user_id = $id;
  197. $addedData->access_level = $newGitlabMembers[$id]->access_level;
  198. $addedData->expires_at = $newGitlabMembers[$id]->expires_at;
  199. $addedMembers[] = $addedData;
  200. }
  201. }
  202. /* Get the deleted data. */
  203. foreach($originalUsers as $user)
  204. {
  205. if(!in_array($user, $accounts) and isset($bindedUsers[$user]))
  206. {
  207. $exist = false;
  208. foreach($gitlabCurrentMembers as $gitlabCurrentMember)
  209. {
  210. if($gitlabCurrentMember->id == $bindedUsers[$user])
  211. {
  212. $exist = true;
  213. $deletedMembers[] = $gitlabCurrentMember->id;
  214. break;
  215. }
  216. }
  217. }
  218. }
  219. return array($addedMembers, $deletedMembers, $updatedMembers);
  220. }
  221. /**
  222. * 检查是否绑定用户。
  223. * Check has binded user.
  224. *
  225. * @param int $gitlabID
  226. * @access protected
  227. * @return void
  228. */
  229. protected function checkBindedUser($gitlabID)
  230. {
  231. if($this->app->user->admin) return;
  232. $openID = $this->loadModel('pipeline')->getOpenIdByAccount($gitlabID, 'gitlab', $this->app->user->account);
  233. if(!$openID) return $this->sendError($this->lang->gitlab->mustBindUser, true);
  234. }
  235. /**
  236. * 解析webhook事件。
  237. * Parse webhook body function.
  238. *
  239. * @param object $body
  240. * @param int $gitlabID
  241. * @access protected
  242. * @return object
  243. */
  244. protected function webhookParseBody($body, $gitlabID)
  245. {
  246. $type = zget($body, 'object_kind', '');
  247. if(!$type or !is_callable(array($this, "webhookParse{$type}"))) return false;
  248. // fix php 8.0 bug. link: https://www.php.net/manual/zh/function.call-user-func-array.php#125953
  249. return call_user_func_array(array($this, "webhookParse{$type}"), array($body, $gitlabID));
  250. }
  251. /**
  252. * 解析webhook触发的issue。
  253. * Parse Webhook issue.
  254. *
  255. * @param object $body
  256. * @param int $gitlabID
  257. * @access protected
  258. * @return object
  259. */
  260. protected function webhookParseIssue($body, $gitlabID)
  261. {
  262. $object = $this->webhookParseObject($body->labels);
  263. if(empty($object)) return null;
  264. $issue = new stdclass;
  265. $issue->action = $body->object_attributes->action . $body->object_kind;
  266. $issue->issue = $body->object_attributes;
  267. $issue->changes = $body->changes;
  268. $issue->objectType = $object->type;
  269. $issue->objectID = $object->id;
  270. $issue->issue->objectType = $object->type;
  271. $issue->issue->objectID = $object->id;
  272. /* Parse markdown description to html. */
  273. $issue->issue->description = commonModel::processMarkdown($issue->issue->description);
  274. if(!isset($this->config->gitlab->maps->{$object->type})) return false;
  275. $issue->object = $this->issueToZentaoObject($issue->issue, $gitlabID, $body->changes);
  276. return $issue;
  277. }
  278. /**
  279. * 通过标签解析禅道对象。
  280. * Parse zentao object from labels.
  281. *
  282. * @param array $labels
  283. * @access protected
  284. * @return object
  285. */
  286. protected function webhookParseObject($labels)
  287. {
  288. $object = null;
  289. $objectType = '';
  290. foreach($labels as $label)
  291. {
  292. if(preg_match($this->config->gitlab->labelPattern->story, $label->title)) $objectType = 'story';
  293. if(preg_match($this->config->gitlab->labelPattern->task, $label->title)) $objectType = 'task';
  294. if(preg_match($this->config->gitlab->labelPattern->bug, $label->title)) $objectType = 'bug';
  295. if($objectType)
  296. {
  297. list($prefix, $id) = explode('/', $label->title);
  298. $object = new stdclass();
  299. $object->id = $id;
  300. $object->type = $objectType;
  301. }
  302. }
  303. return $object;
  304. }
  305. /**
  306. * 解析gitlab issue为禅道任务、bug、需求。
  307. * Parse issue to zentao object.
  308. *
  309. * @param object $issue
  310. * @param int $gitlabID
  311. * @param object $changes
  312. * @access protected
  313. * @return object
  314. */
  315. protected function issueToZentaoObject($issue, $gitlabID, $changes = null)
  316. {
  317. if(!isset($this->config->gitlab->maps->{$issue->objectType})) return null;
  318. if(isset($changes->assignees)) $changes->assignee_id = true;
  319. $maps = $this->config->gitlab->maps->{$issue->objectType};
  320. $gitlabUsers = $this->loadModel('pipeline')->getUserBindedPairs($gitlabID, 'gitlab', 'openID,account');
  321. $object = new stdclass;
  322. $object->id = $issue->objectID;
  323. foreach($maps as $zentaoField => $config)
  324. {
  325. $value = '';
  326. list($gitlabField, $optionType, $options) = explode('|', $config);
  327. if(!isset($changes->$gitlabField) and $object->id != 0) continue;
  328. if($optionType == 'field' or $optionType == 'fields') $value = $issue->$gitlabField;
  329. if($options == 'date') $value = $value ? date('Y-m-d', strtotime($value)) : '0000-00-00';
  330. if($options == 'datetime') $value = $value ? date('Y-m-d H:i:s', strtotime($value)) : '0000-00-00 00:00:00';
  331. if($optionType == 'userPairs' and isset($issue->$gitlabField)) $value = zget($gitlabUsers, $issue->$gitlabField);
  332. if($optionType == 'configItems' and isset($issue->$gitlabField)) $value = array_search($issue->$gitlabField, $this->config->gitlab->$options);
  333. /* Execute this line even `$value == ""`, such as `$issue->description == ""`. */
  334. if($value or $value == "") $object->$zentaoField = $value;
  335. if($gitlabField == "description") $object->$zentaoField .= "<br><br><a href=\"{$issue->web_url}\" target=\"_blank\">{$issue->web_url}</a>";
  336. }
  337. return $object;
  338. }
  339. }