model.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. <?php
  2. /**
  3. * The model file of gitea module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Chenqi <chenqi@cnezsoft.com>
  8. * @package gitea
  9. * @link https://www.zentao.net
  10. */
  11. class giteaModel extends model
  12. {
  13. const HOOK_PUSH_EVENT = 'Push Hook';
  14. /* Gitlab access level. */
  15. public $noAccess = 0;
  16. public $developerAccess = 30;
  17. public $maintainerAccess = 40;
  18. /**
  19. * 获取Gitea请求地址信息。
  20. * Get gitea api base url by gitea id.
  21. *
  22. * @param int $giteaID
  23. * @param bool $sudo
  24. * @access public
  25. * @return string
  26. */
  27. public function getApiRoot($giteaID, $sudo = true)
  28. {
  29. $gitea = $this->fetchByID($giteaID);
  30. if(!$gitea || $gitea->type != 'gitea') return '';
  31. $sudoParam = '';
  32. if($sudo && !$this->app->user->admin)
  33. {
  34. $openID = $this->loadModel('pipeline')->getOpenIdByAccount($giteaID, 'gitea', $this->app->user->account);
  35. if($openID) $sudoParam = "&sudo={$openID}";
  36. }
  37. return rtrim($gitea->url, '/') . '/api/v1%s' . "?token={$gitea->token}" . $sudoParam;
  38. }
  39. /**
  40. * Gitea用户和禅道用户绑定。
  41. * Bind gitea user and zentao user.
  42. *
  43. * @param int $giteaID
  44. * @param array $users
  45. * @param array $giteaNames
  46. * @access public
  47. * @return bool
  48. */
  49. public function bindUser($giteaID, $users, $giteaNames)
  50. {
  51. $repeatUsers = array();
  52. $userPairs = array();
  53. foreach($users as $openID => $account)
  54. {
  55. if(empty($account)) continue;
  56. if(in_array($account, $userPairs)) $repeatUsers[] = $account;
  57. $userPairs[$openID] = $account;
  58. }
  59. /* Check user repeat bind. */
  60. if($repeatUsers)
  61. {
  62. $userList = $this->loadModel('user')->getRealNameAndEmails($repeatUsers);
  63. dao::$errors = sprintf($this->lang->gitea->bindUserError, join(',', helper::arrayColumn($userList, 'realname')));
  64. return false;
  65. }
  66. $bindedUsers = $this->dao->select('openID,account')->from(TABLE_OAUTH)
  67. ->where('providerType')->eq('gitea')
  68. ->andWhere('providerID')->eq($giteaID)
  69. ->fetchPairs();
  70. $this->dao->delete()->from(TABLE_OAUTH)->where('providerType')->eq('gitea')->andWhere('providerID')->eq($giteaID)->exec();
  71. $this->loadModel('action');
  72. foreach($userPairs as $openID => $account)
  73. {
  74. /* If user binded user is change, delete it. */
  75. if(isset($bindedUsers[$openID]) && $bindedUsers[$openID] != $account) $this->action->create('giteauser', $giteaID, 'unbind', '', $giteaNames[$openID]);
  76. /* Add zentao user and gitea user binded. */
  77. $user = new stdclass();
  78. $user->providerID = $giteaID;
  79. $user->providerType = 'gitea';
  80. $user->account = $account;
  81. $user->openID = $giteaNames[$openID];
  82. $this->dao->insert(TABLE_OAUTH)->data($user)->exec();
  83. $this->action->create('giteauser', $giteaID, 'bind', '', $giteaNames[$openID]);
  84. }
  85. return !dao::isError();
  86. }
  87. /**
  88. * 解析翻译接口返回的错误信息。
  89. * Api error handling.
  90. *
  91. * @param object $response
  92. * @access public
  93. * @return bool
  94. */
  95. public function apiErrorHandling($response)
  96. {
  97. if(!empty($response->error))
  98. {
  99. dao::$errors[] = $response->error;
  100. return false;
  101. }
  102. if(empty($response->message))
  103. {
  104. dao::$errors[] = 'error';
  105. return false;
  106. }
  107. $errorMsg = array();
  108. if(is_string($response->message)) $errorMsg[] = $response->message;
  109. if(is_array($response->message))
  110. {
  111. foreach($response->message as $fieldErrors)
  112. {
  113. if(is_string($fieldErrors)) $fieldErrors = array($fieldErrors);
  114. foreach($fieldErrors as $error) $errorMsg[] = $error;
  115. }
  116. }
  117. foreach($errorMsg as $error) $this->parseApiError($error);
  118. return false;
  119. }
  120. /**
  121. * 解析api返回的错误信息。
  122. * Parse api error.
  123. *
  124. * @param string $message
  125. * @access public
  126. * @return void
  127. */
  128. public function parseApiError($message)
  129. {
  130. $errorKey = array_search($message, $this->lang->gitea->apiError);
  131. if($errorKey === false)
  132. {
  133. dao::$errors[] = $message;
  134. }
  135. else
  136. {
  137. $field = $this->lang->gitea->errorKey[$errorKey];
  138. dao::$errors[$field] = zget($this->lang->gitea->errorLang, $errorKey);
  139. }
  140. }
  141. /**
  142. * 检测token是否有效。
  143. * Check token access.
  144. *
  145. * @param string $url
  146. * @param string $token
  147. * @access public
  148. * @return bool
  149. */
  150. public function checkTokenAccess($url = '', $token = '')
  151. {
  152. $apiRoot = rtrim($url, '/') . '/api/v1%s' . "?token={$token}";
  153. $url = sprintf($apiRoot, "/admin/users") . "&limit=1";
  154. $users = json_decode(commonModel::http($url));
  155. if(empty($users)) return false;
  156. if(isset($users->message) || isset($users->error)) return false;
  157. return true;
  158. }
  159. /**
  160. * 通过API获取Gitea项目信息。
  161. * Get project by api.
  162. *
  163. * @param int $giteaID
  164. * @param string $projectID
  165. * @access public
  166. * @return object|null
  167. */
  168. public function apiGetSingleProject($giteaID, $projectID)
  169. {
  170. $apiRoot = $this->getApiRoot($giteaID);
  171. if(!$apiRoot) return null;
  172. $url = sprintf($apiRoot, "/repos/$projectID");
  173. $project = json_decode(commonModel::http($url));
  174. if(isset($project->name))
  175. {
  176. $project->name_with_namespace = $project->full_name;
  177. $project->path_with_namespace = $project->full_name;
  178. $project->http_url_to_repo = $project->html_url;
  179. $project->name_with_namespace = $project->full_name;
  180. $gitea = $this->fetchByID($giteaID);
  181. $oauth = "oauth2:{$gitea->token}@";
  182. $project->tokenCloneUrl = preg_replace('/(http(s)?:\/\/)/', "\$1$oauth", $project->html_url);
  183. $project->tokenCloneUrl = str_replace(array('https://', 'http://'), strstr($url, ':', true) . '://', $project->tokenCloneUrl);
  184. }
  185. return $project;
  186. }
  187. /**
  188. * 通过API获取Gitea项目列表。
  189. * Get projects by api.
  190. *
  191. * @param int $giteaID
  192. * @param bool $sudo
  193. * @access public
  194. * @return array
  195. */
  196. public function apiGetProjects($giteaID, $sudo = true)
  197. {
  198. $apiRoot = $this->getApiRoot($giteaID, $sudo);
  199. if(!$apiRoot) return array();
  200. $url = sprintf($apiRoot, "/repos/search");
  201. $page = 1;
  202. $projects = array();
  203. while(true)
  204. {
  205. $results = json_decode(commonModel::http($url . "&page={$page}&limit=50"));
  206. if(empty($results->data) || !is_array($results->data)) break;
  207. $projects = array_merge($projects, $results->data);
  208. if(count($results->data) < 50) break;
  209. $page ++;
  210. }
  211. return $projects;
  212. }
  213. /**
  214. * 通过api获取组列表。
  215. * Get groups by api.
  216. *
  217. * @param int $giteaID
  218. * @param bool $sudo
  219. * @access public
  220. * @return array
  221. */
  222. public function apiGetGroups($giteaID, $sudo = true)
  223. {
  224. $apiRoot = $this->getApiRoot($giteaID, $sudo);
  225. if(!$apiRoot) return array();
  226. $url = sprintf($apiRoot, "/orgs");
  227. $allResults = array();
  228. for($page = 1; true; $page++)
  229. {
  230. $results = json_decode(commonModel::http($url . "&page={$page}&limit=50"));
  231. if(empty($results) || isset($results->message)) break;
  232. $allResults = array_merge($allResults, $results);
  233. if(count($results) < 50) break;
  234. }
  235. return $allResults;
  236. }
  237. /**
  238. * 通过API获取Gitea用户列表。
  239. * Get gitea user list.
  240. *
  241. * @param int $giteaID
  242. * @param bool $onlyLinked
  243. * @access public
  244. * @return array
  245. */
  246. public function apiGetUsers($giteaID, $onlyLinked = false)
  247. {
  248. $apiRoot = $this->getApiRoot($giteaID, strtolower($this->app->rawMethod) != 'binduser');
  249. if(empty($apiRoot)) return array();
  250. $page = 1;
  251. $users = array();
  252. while(true)
  253. {
  254. $url = sprintf($apiRoot, "/users/search") . "&page={$page}&limit=50";
  255. $result = json_decode(commonModel::http($url));
  256. if(empty($result->data)) break;
  257. $users = array_merge($users, $result->data);
  258. $page ++;
  259. }
  260. if(empty($users)) return array();
  261. /* Get linked users. */
  262. $linkedUsers = array();
  263. if($onlyLinked) $linkedUsers = $this->loadModel('pipeline')->getUserBindedPairs($giteaID, 'gitea', 'openID,account');
  264. $userList = array();
  265. foreach($users as $giteaUser)
  266. {
  267. if($onlyLinked && !isset($linkedUsers[$giteaUser->username])) continue;
  268. $user = new stdclass();
  269. $user->id = $giteaUser->id;
  270. $user->realname = $giteaUser->full_name ? $giteaUser->full_name : $giteaUser->username;
  271. $user->account = $giteaUser->username;
  272. $user->email = zget($giteaUser, 'email', '');
  273. $user->avatar = $giteaUser->avatar_url;
  274. $user->createdAt = zget($giteaUser, 'created', '');
  275. $user->lastActivityOn = zget($giteaUser, 'last_login', '');
  276. $userList[] = $user;
  277. }
  278. return $userList;
  279. }
  280. /**
  281. * 通过API获取Gitea项目分支列表。
  282. * Get project repository branches by api.
  283. *
  284. * @param int $giteaID
  285. * @param string $project
  286. * @access public
  287. * @return array
  288. */
  289. public function apiGetBranches($giteaID, $project)
  290. {
  291. $url = sprintf($this->getApiRoot($giteaID), "/repos/{$project}/branches");
  292. $branches = array();
  293. for($page = 1; true; $page++)
  294. {
  295. $results = json_decode(commonModel::http($url . "&page={$page}&limit=50"));
  296. if(!is_array($results)) break;
  297. if(!empty($results)) $branches = array_merge($branches, $results);
  298. if(count($results) < 100) break;
  299. }
  300. return $branches;
  301. }
  302. /**
  303. * 通过API获取Gitea项目分支信息。
  304. * Get single branch by API.
  305. *
  306. * @param int $giteaID
  307. * @param string $project
  308. * @param string $branchName
  309. * @access public
  310. * @return object|null
  311. */
  312. public function apiGetSingleBranch($giteaID, $project, $branchName)
  313. {
  314. if(empty($branchName)) return null;
  315. $url = sprintf($this->getApiRoot($giteaID), "/repos/$project/branches/$branchName");
  316. $branch = json_decode(commonModel::http($url));
  317. if(isset($branch->name))
  318. {
  319. $gitea = $this->fetchByID($giteaID);
  320. $branch->web_url = "{$gitea->url}/$project/src/branch/$branchName";
  321. }
  322. return $branch;
  323. }
  324. /**
  325. * 通过API获取Gitea项目分支保护信息。
  326. * Get protect branches of one project.
  327. *
  328. * @param int $giteaID
  329. * @param string $project
  330. * @param string $keyword
  331. * @access public
  332. * @return array|null
  333. */
  334. public function apiGetBranchPrivs($giteaID, $project, $keyword = '')
  335. {
  336. $url = sprintf($this->getApiRoot($giteaID), "/repos/$project/branch_protections");
  337. $branches = json_decode(commonModel::http($url));
  338. if(!is_array($branches)) return array();
  339. $keyword = urlencode($keyword);
  340. $newBranches = array();
  341. foreach($branches as $branch)
  342. {
  343. $branch->name = $branch->branch_name;
  344. if(empty($keyword) || stristr($branch->name, $keyword)) $newBranches[] = $branch;
  345. }
  346. return $newBranches;
  347. }
  348. /**
  349. * API获取项目的合并请求列表。
  350. * Get Merge Requests by API.
  351. *
  352. * @param int $giteaID
  353. * @param string $project
  354. * @access public
  355. * @return array
  356. */
  357. public function apiGetMergeRequests($giteaID, $project)
  358. {
  359. $apiRoot = $this->getApiRoot($giteaID, false);
  360. if(!$apiRoot) return array();
  361. $apiPath = "/repos/{$project}/pulls";
  362. $url = sprintf($apiRoot, $apiPath);
  363. $mrList = json_decode(common::http($url));
  364. if(!is_array($mrList)) return array();
  365. foreach($mrList as $mr)
  366. {
  367. $mr->web_url = $mr->url;
  368. $mr->iid = $mr->number;
  369. $mr->state = $mr->state == 'open' ? 'opened' : $mr->state;
  370. if($mr->merged) $mr->state = 'merged';
  371. $mr->merge_status = $mr->mergeable ? 'can_be_merged' : 'cannot_be_merged';
  372. $mr->description = $mr->body;
  373. $mr->target_branch = $mr->base->ref;
  374. $mr->source_branch = $mr->head->ref;
  375. $mr->source_project_id = $project;
  376. $mr->target_project_id = $project;
  377. $mr->has_conflicts = !$mr->mergeable;
  378. $mr->is_draft = strpos($mr->title, 'Draft:') === 0;
  379. }
  380. return $mrList;
  381. }
  382. }