* @package gitlab * @link https://www.zentao.net */ class gitlabZen extends gitlab { /** * 检查绑定用户是否重复。 * Check bind user repeat. * * @param array $zentaoUsers * @param array $userPairs * @access protected * @return array */ protected function checkUserRepeat(array $zentaoUsers, array $userPairs): array { $accountList = array(); $repeatUsers = array(); foreach($zentaoUsers as $openID => $user) { if(empty($user)) continue; if(isset($accountList[$user])) $repeatUsers[] = zget($userPairs, $user); $accountList[$user] = $openID; } if(count($repeatUsers)) return array('result' => 'fail', 'message' => sprintf($this->lang->gitlab->bindUserError, join(',', array_unique($repeatUsers)))); return array('result' => 'success'); } /** * 绑定用户。 * Bind user. * * @param int $gitlabID * @param array $users * @param array $gitlabNames * @param array $zentaoUsers * @access protected * @return void */ protected function bindUsers(int $gitlabID, array $users, array $gitlabNames, array $zentaoUsers): void { $user = new stdclass; $user->providerID = $gitlabID; $user->providerType = 'gitlab'; $oldUsers = $this->dao->select('*')->from(TABLE_OAUTH)->where('providerType')->eq($user->providerType)->andWhere('providerID')->eq($user->providerID)->fetchAll('openID'); foreach($users as $openID => $account) { $existAccount = isset($oldUsers[$openID]) ? $oldUsers[$openID] : ''; if($existAccount and $existAccount->account != $account) { $this->dao->delete() ->from(TABLE_OAUTH) ->where('openID')->eq($openID) ->andWhere('providerType')->eq($user->providerType) ->andWhere('providerID')->eq($user->providerID) ->exec(); $this->loadModel('action')->create('gitlabuser', $openID, 'unbind', '', sprintf($this->lang->gitlab->bindDynamic, $gitlabNames[$openID], $zentaoUsers[$existAccount->account]->realname)); } if(!$existAccount or $existAccount->account != $account) { if(!$account) continue; $user->account = $account; $user->openID = $openID; $this->dao->insert(TABLE_OAUTH)->data($user)->exec(); $this->loadModel('action')->create('gitlabuser', $openID, 'bind', '', sprintf($this->lang->gitlab->bindDynamic, $gitlabNames[$openID], $zentaoUsers[$account]->realname)); } } } /** * 记录webhook日志。 * Record webhook logs. * * @param string $input * @param object $result * @access protected * @return void */ protected function recordWebhookLogs(string $input, object $result): void { $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); } } /** * 获取gitlab组新增、删除、更新成员数据。 * Get group added、deleted、updated member data. * * @param array $currentMembers * @param array $newMembers * @access protected * @return array */ protected function getGroupMemberData(array $currentMembers, array $newMembers): array { /* Get the updated,deleted data. */ $addedMembers = $deletedMembers = $updatedMembers = array(); foreach($currentMembers as $currentMember) { $memberID = $currentMember->id; if(empty($newMembers[$memberID])) { $deletedMembers[] = $memberID; } else { if($newMembers[$memberID]->access_level != $currentMember->access_level or $newMembers[$memberID]->expires_at != $currentMember->expires_at) { $updatedData = new stdClass(); $updatedData->user_id = $memberID; $updatedData->access_level = $newMembers[$memberID]->access_level; $updatedData->expires_at = $newMembers[$memberID]->expires_at; $updatedMembers[] = $updatedData; } } } /* Get the added data. */ foreach($newMembers as $id => $newMember) { $exist = false; foreach($currentMembers as $currentMember) { if($currentMember->id == $id) { $exist = true; break; } } if($exist == false) { $addedData = new stdClass(); $addedData->user_id = $id; $addedData->access_level = $newMembers[$id]->access_level; $addedData->expires_at = $newMembers[$id]->expires_at; $addedMembers[] = $addedData; } } return array($addedMembers, $deletedMembers, $updatedMembers); } /** * 获取项目成员数据。 * Get project member data. * * @param array $gitlabCurrentMembers * @param array $newGitlabMembers * @param array $bindedUsers * @param array $accounts * @param array $originalUsers * @access protected * @return array */ protected function getProjectMemberData(array $gitlabCurrentMembers, array $newGitlabMembers, array $bindedUsers, array $accounts, array $originalUsers): array { $addedMembers = $updatedMembers = $deletedMembers = array(); /* Get the updated data. */ foreach($gitlabCurrentMembers as $gitlabCurrentMember) { $memberID = isset($gitlabCurrentMember->id) ? $gitlabCurrentMember->id : 0; if(!isset($newGitlabMembers[$memberID])) continue; if($newGitlabMembers[$memberID]->access_level != $gitlabCurrentMember->access_level or $newGitlabMembers[$memberID]->expires_at != $gitlabCurrentMember->expires_at) { $updatedData = new stdClass(); $updatedData->user_id = $memberID; $updatedData->access_level = $newGitlabMembers[$memberID]->access_level; $updatedData->expires_at = $newGitlabMembers[$memberID]->expires_at; $updatedMembers[] = $updatedData; } } /* Get the added data. */ foreach($newGitlabMembers as $id => $newMember) { $exist = false; foreach($gitlabCurrentMembers as $gitlabCurrentMember) { if($gitlabCurrentMember->id == $id) { $exist = true; break; } } if($exist == false) { $addedData = new stdClass(); $addedData->user_id = $id; $addedData->access_level = $newGitlabMembers[$id]->access_level; $addedData->expires_at = $newGitlabMembers[$id]->expires_at; $addedMembers[] = $addedData; } } /* Get the deleted data. */ foreach($originalUsers as $user) { if(!in_array($user, $accounts) and isset($bindedUsers[$user])) { $exist = false; foreach($gitlabCurrentMembers as $gitlabCurrentMember) { if($gitlabCurrentMember->id == $bindedUsers[$user]) { $exist = true; $deletedMembers[] = $gitlabCurrentMember->id; break; } } } } return array($addedMembers, $deletedMembers, $updatedMembers); } /** * 检查是否绑定用户。 * Check has binded user. * * @param int $gitlabID * @access protected * @return void */ protected function checkBindedUser(int $gitlabID) { if($this->app->user->admin) return; $openID = $this->loadModel('pipeline')->getOpenIdByAccount($gitlabID, 'gitlab', $this->app->user->account); if(!$openID) return $this->sendError($this->lang->gitlab->mustBindUser, true); } /** * 解析webhook事件。 * Parse webhook body function. * * @param object $body * @param int $gitlabID * @access protected * @return object */ protected function webhookParseBody(object $body, int $gitlabID): object { $type = zget($body, 'object_kind', ''); if(!$type or !is_callable(array($this, "webhookParse{$type}"))) return false; // fix php 8.0 bug. link: https://www.php.net/manual/zh/function.call-user-func-array.php#125953 return call_user_func_array(array($this, "webhookParse{$type}"), array($body, $gitlabID)); } /** * 解析webhook触发的issue。 * Parse Webhook issue. * * @param object $body * @param int $gitlabID * @access protected * @return object */ protected function webhookParseIssue(object $body, int $gitlabID): object { $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 = commonModel::processMarkdown($issue->issue->description); if(!isset($this->config->gitlab->maps->{$object->type})) return false; $issue->object = $this->issueToZentaoObject($issue->issue, $gitlabID, $body->changes); return $issue; } /** * 通过标签解析禅道对象。 * Parse zentao object from labels. * * @param array $labels * @access protected * @return object */ protected function webhookParseObject(array $labels): object { $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; } /** * 解析gitlab issue为禅道任务、bug、需求。 * Parse issue to zentao object. * * @param object $issue * @param int $gitlabID * @param object $changes * @access protected * @return object */ protected function issueToZentaoObject(object $issue, int $gitlabID, ?object $changes = null): object { 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->loadModel('pipeline')->getUserBindedPairs($gitlabID, 'gitlab', 'openID,account'); $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' or $optionType == 'fields') $value = $issue->$gitlabField; 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); /* Execute this line even `$value == ""`, such as `$issue->description == ""`. */ if($value or $value == "") $object->$zentaoField = $value; if($gitlabField == "description") $object->$zentaoField .= "

web_url}\" target=\"_blank\">{$issue->web_url}"; } return $object; } }