diff --git a/config/zentaopms.php b/config/zentaopms.php index 1c33bb6d96..646f396d0a 100644 --- a/config/zentaopms.php +++ b/config/zentaopms.php @@ -377,6 +377,7 @@ $config->objectTables['gitlab'] = TABLE_PIPELINE; $config->objectTables['jebkins'] = TABLE_PIPELINE; $config->objectTables['stage'] = TABLE_STAGE; $config->objectTables['apistruct'] = TABLE_APISTRUCT; +$config->objectTables['repo'] = TABLE_REPO; $config->newFeatures = array('introduction', 'tutorial', 'youngBlueTheme', 'visions'); @@ -388,3 +389,5 @@ $config->programPriv->scrum = array('story', 'projectstory', 'projectrelease $config->programPriv->waterfall = array_merge($config->programPriv->scrum, array('task', 'workestimation', 'durationestimation', 'budget', 'programplan', 'review', 'reviewissue', 'weekly', 'cm', 'milestone', 'design', 'issue', 'risk', 'opportunity', 'measrecord', 'auditplan', 'trainplan', 'gapanalysis', 'pssp', 'researchplan', 'researchreport')); $config->waterfallModules = array('workestimation', 'durationestimation', 'budget', 'programplan', 'review', 'reviewissue', 'weekly', 'cm', 'milestone', 'design', 'opportunity', 'auditplan', 'trainplan', 'gapanalysis', 'pssp', 'researchplan', 'researchreport'); + +$config->showMainMenu = true; diff --git a/db/update17.3.sql b/db/update17.3.sql new file mode 100644 index 0000000000..a000ab61d5 --- /dev/null +++ b/db/update17.3.sql @@ -0,0 +1,3 @@ +ALTER TABLE `zt_mr` CHANGE COLUMN `gitlabID` `hostID` mediumint(8) UNSIGNED NOT NULL AFTER `id`; +ALTER TABLE `zt_mr` MODIFY COLUMN `sourceProject` varchar(50) NOT NULL AFTER `hostID`; +ALTER TABLE `zt_mr` MODIFY COLUMN `targetProject` varchar(50) NOT NULL AFTER `sourceBranch`; diff --git a/db/zentao.sql b/db/zentao.sql index 808fa6805e..ad5feb37ee 100755 --- a/db/zentao.sql +++ b/db/zentao.sql @@ -955,10 +955,10 @@ CREATE TABLE IF NOT EXISTS `zt_module` ( -- DROP TABLE IF EXISTS `zt_mr`; CREATE TABLE IF NOT EXISTS `zt_mr` ( `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, - `gitlabID` mediumint(8) unsigned NOT NULL, - `sourceProject` int unsigned NOT NULL, + `hostID` mediumint(8) unsigned NOT NULL, + `sourceProject` varchar(50) NOT NULL, `sourceBranch` varchar(100) NOT NULL, - `targetProject` int unsigned NOT NULL, + `targetProject` varchar(50) NOT NULL, `targetBranch` varchar(100) NOT NULL, `mriid` int unsigned NOT NULL, `title` varchar(255) NOT NULL, diff --git a/framework/base/router.class.php b/framework/base/router.class.php index 039c2edd2a..60ef97b0b1 100644 --- a/framework/base/router.class.php +++ b/framework/base/router.class.php @@ -658,6 +658,20 @@ class baseRouter $_POST = validater::filterSuper($_POST); $_GET = validater::filterSuper($_GET); $_COOKIE = validater::filterSuper($_COOKIE); + + /* Filter common get and cookie vars. */ + if($this->config->framework->filterParam == 2) + { + global $filter; + foreach($filter->default->get as $key => $rules) + { + if(isset($_GET[$key]) and !validater::checkByRule($_GET[$key], $rules)) unset($_GET[$key]); + } + foreach($filter->default->cookie as $key => $rules) + { + if(isset($_COOKIE[$key]) and !validater::checkByRule($_COOKIE[$key], $rules)) unset($_COOKIE[$key]); + } + } } /** diff --git a/lib/scm/gitea.class.php b/lib/scm/gitea.class.php new file mode 100644 index 0000000000..b01638fa87 --- /dev/null +++ b/lib/scm/gitea.class.php @@ -0,0 +1,815 @@ +client = $client; + $this->root = rtrim($root, '/') . '/'; + $this->token = $password; + $this->branch = isset($_COOKIE['repoBranch']) ? $_COOKIE['repoBranch'] : 'HEAD'; + } + + /** + * List files. + * + * @param string $path + * @param string $revision + * @access public + * @return array + */ + public function ls($path, $revision = 'HEAD') + { + if(!scm::checkRevision($revision)) return array(); + $api = "contents"; + $path = ltrim($path, '/'); + if($path) $api .= "/$path"; + + $param = new stdclass(); + $param->ref = $revision; + $param->recursive = 0; + if(!empty($this->branch)) $param->ref = $this->branch; + + $list = $this->fetch($api, $param, true); + if(empty($list)) return array(); + + $infos = array(); + foreach($list as $file) + { + if(!isset($file->type)) continue; + + $info = new stdClass(); + $info->name = $file->name; + $info->kind = $file->type; + + if($file->type == 'file') + { + $file = $this->files($file->path, $this->branch); + + $info->revision = zget($file, 'revision', ''); + $info->comment = zget($file, 'comment', ''); + $info->account = zget($file, 'committer', ''); + $info->date = zget($file, 'date', ''); + $info->size = zget($file, 'size', ''); + } + else + { + $commits = $this->getCommitsByPath($file->path, '', '', 1, 1); + if(empty($commits) or !is_array($commits)) continue; + $commit = $commits[0]; + + $info->revision = $commit->sha; + $info->comment = $commit->commit->message; + $info->account = $commit->commit->author->name; + $info->date = date('Y-m-d H:i:s', strtotime($commit->commit->author->date)); + $info->size = 0; + } + + $infos[] = $info; + unset($info); + } + + /* Sort by kind */ + foreach($infos as $key => $info) $kinds[$key] = $info->kind; + if($infos) array_multisort($kinds, SORT_ASC, $infos); + return $infos; + } + + /** + * Get files info. + * + * The API path requested is: "GET /projects/:id/repository/files/:file_path". + * Known issue of GitLab API: if a '%' in 'file_path', GitLab API will show a error 'file_path should be a valid file path'. + * + * @param string $path + * @param string $ref + * @access public + * @return object + * @doc https://docs.gitea.com/ee/api/repository_files.html + */ + public function files($path, $ref = 'master') + { + $path = urlencode($path); + $api = "contents/$path"; + $file = $this->fetch($api, array('ref' => $ref)); + if(!isset($file->name)) return false; + + $commits = $this->getCommitsByPath($path, '', '', 1, 1); + $file->revision = $file->sha; + $file->size = $this->formatBytes($file->size); + + if(!empty($commits)) + { + $commit = $commits[0]; + + $file->revision = $commit->sha; + $file->committer = $commit->commit->author->name; + $file->comment = $commit->commit->message; + $file->date = date('Y-m-d H:i:s', strtotime($commit->commit->author->date)); + } + + return $file; + } + + /** + * Get tags + * + * @param string $path + * @param string $revision + * @access public + * @return array + */ + public function tags($path, $revision = 'HEAD') + { + $api = "tags"; + $tags = array(); + + $params = array(); + $params['limit'] = $this->pageLimit; + for($page = 1; true; $page ++) + { + $params['page'] = $page; + $list = $this->fetch($api, $params); + if(empty($list) or $list == '[]') break; + + foreach($list as $tag) $tags[] = $tag->name; + if(count($list) < $params['limit']) break; + } + + return $tags; + } + + /** + * Get branches. + * + * @access public + * @return array + */ + public function branch() + { + /* Max size of limit in gitea API is 50. */ + $params = array(); + $params['limit'] = $this->pageLimit; + + /* Get default branch. */ + $project = $this->fetch(''); + $defaultBranch = $project->default_branch; + + $branches = array(); + $default = array(); + for($page = 1; true; $page ++) + { + $params['page'] = $page; + $branchList = $this->fetch("branches", $params); + if(empty($branchList)) break; + + foreach($branchList as $branch) + { + if(!isset($branch->name)) continue; + if($branch->name == $defaultBranch) + { + $default[$branch->name] = $branch->name; + } + else + { + $branches[$branch->name] = $branch->name; + } + } + + /* Last page. */ + if(count($branchList) < $params['limit']) break; + } + + if(empty($branches) and empty($default)) $branches['master'] = 'master'; + asort($branches); + + $branches = $default + $branches; + return $branches; + } + + /** + * Get last log. + * + * @param string $path + * @param int $count + * @access public + * @return array + */ + public function getLastLog($path, $count = 10) + { + return $this->log($path); + } + + /** + * Get logs. + * + * @param string $path + * @param string $fromRevision + * @param string $toRevision + * @param int $count + * @access public + * @return array + */ + public function log($path, $fromRevision = 0, $toRevision = 'HEAD', $count = 0) + { + if(!scm::checkRevision($fromRevision)) return array(); + if(!scm::checkRevision($toRevision)) return array(); + + $path = ltrim($path, DIRECTORY_SEPARATOR); + $count = $count == 0 ? '' : "-n $count"; + $list = $this->getCommitsByPath($path, $fromRevision, $toRevision, 1, 1); + foreach($list as $commit) + { + if(isset($commit->sha)) $commit->diffs = $this->getFilesByCommit($commit->sha); + } + + return $this->parseLog($list); + } + + /** + * Blame file + * + * @param string $path + * @param string $revision + * @access public + * @return array + */ + public function blame($path, $revision) + { + return array(); + } + + /** + * Diff file. + * + * @param string $path + * @param string $fromRevision + * @param string $toRevision + * @param string $fromProject + * @param string $extra + * @access public + * @return array + */ + public function diff($path, $fromRevision, $toRevision, $fromProject = '', $extra = '') + { + if(!scm::checkRevision($fromRevision) and $extra != 'isBranchOrTag') return array(); + if(!scm::checkRevision($toRevision) and $extra != 'isBranchOrTag') return array(); + + $diffApi = "{$this->root}git/commits/$toRevision.diff?token={$this->token}"; + $diffs = commonModel::http($diffApi); + $lines = explode("\n", $diffs); + return $lines; + } + + /** + * Cat file. + * + * @param string $entry + * @param string $revision + * @access public + * @return string + */ + public function cat($entry, $revision = 'HEAD') + { + if(!scm::checkRevision($revision)) return false; + if($revision == 'HEAD' and $this->branch) $revision = $this->branch; + $file = $this->files($entry, $revision); + return base64_decode($file->content); + } + + /** + * Get info. + * + * @param string $entry + * @param string $revision + * @access public + * @return object + */ + public function info($entry, $revision = 'HEAD') + { + if(!scm::checkRevision($revision)) return false; + + $info = new stdclass(); + $info->kind = 'dir'; + $info->path = $entry; + $info->revision = $revision; + $info->root = ''; + if($revision == 'HEAD' and $this->branch) $info->revision = $this->branch; + + if($entry) + { + $parent = dirname($entry); + if($parent == '.') $parent = '/'; + if($parent == '') $parent = '/'; + $list = $this->tree($parent, 0); + $file = new stdclass(); + + foreach($list as $node) if($node->path == $entry) $file = $node; + + $commits = $this->getCommitsByPath($entry); + + if(!empty($commits)) $file->revision = zget($commits[0], 'id', ''); + $info->kind = (isset($file->type) and $file->type == 'tree') ? 'dir' : 'file'; + } + + return $info; + } + + /** + * Exec git cmd. + * + * @param string $cmd + * @access public + * @todo Exec commands by gitea api. + * @return array + */ + public function exec($cmd) + { + return execCmd(escapeCmd("$this->client $cmd"), 'array'); + } + + /** + * Parse diff. + * + * @param array $lines + * @access public + * @return array + */ + public function parseDiff($lines) + { + if(empty($lines)) return array(); + $diffs = array(); + $num = count($lines); + $endLine = end($lines); + if(strpos($endLine, '\ No newline at end of file') === 0) $num -= 1; + + $newFile = false; + $allFiles = array(); + for($i = 0; $i < $num; $i ++) + { + $diffFile = new stdclass(); + if(strpos($lines[$i], "diff --git ") === 0) + { + $fileInfo = explode(' ',$lines[$i]); + $fileName = substr($fileInfo[2], strpos($fileInfo[2], '/') + 1); + + /* Prevent duplicate display of files. */ + if(in_array($fileName, $allFiles)) continue; + $allFiles[] = $fileName; + + $diffFile->fileName = $fileName; + for($i++; $i < $num; $i ++) + { + $diff = new stdclass(); + /* Fix bug #1757. */ + if($lines[$i] == '+++ /dev/null') $newFile = true; + if(strpos($lines[$i], '+++', 0) !== false) continue; + if(strpos($lines[$i], '---', 0) !== false) continue; + if(strpos($lines[$i], '======', 0) !== false) continue; + if(preg_match('/^@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@/A', $lines[$i])) + { + $startLines = trim(str_replace(array('@', '+', '-'), '', $lines[$i])); + list($oldStartLine, $newStartLine) = explode(' ', $startLines); + list($diff->oldStartLine) = explode(',', $oldStartLine); + list($diff->newStartLine) = explode(',', $newStartLine); + $oldCurrentLine = $diff->oldStartLine; + $newCurrentLine = $diff->newStartLine; + if($newFile) + { + $oldCurrentLine = $diff->newStartLine; + $newCurrentLine = $diff->oldStartLine; + } + $newLines = array(); + for($i++; $i < $num; $i ++) + { + if(preg_match('/^@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@/A', $lines[$i])) + { + $i --; + break; + } + if(strpos($lines[$i], "diff --git ") === 0) break; + + $line = $lines[$i]; + if(strpos($line, '\ No newline at end of file') === 0)continue; + $sign = empty($line) ? '' : $line[0]; + if($sign == '-' and $newFile) $sign = '+'; + $type = $sign != '-' ? $sign == '+' ? 'new' : 'all' : 'old'; + if($sign == '-' || $sign == '+') + { + $line = substr_replace($line, ' ', 1, 0); + if($newFile) $line = preg_replace('/^\-/', '+', $line); + } + + $newLine = new stdclass(); + $newLine->type = $type; + $newLine->oldlc = $type != 'new' ? $oldCurrentLine : ''; + $newLine->newlc = $type != 'old' ? $newCurrentLine : ''; + $newLine->line = htmlSpecialString($line); + + if($type != 'new') $oldCurrentLine++; + if($type != 'old') $newCurrentLine++; + + $newLines[] = $newLine; + } + + $diff->lines = $newLines; + $diffFile->contents[] = $diff; + } + + if(isset($lines[$i]) and strpos($lines[$i], "diff --git ") === 0) + { + $i --; + $newFile = false; + break; + } + } + $diffs[] = $diffFile; + } + } + return $diffs; + } + + /** + * Get commit count. + * + * @param int $commits + * @param string $lastVersion + * @access public + * @return int + */ + public function getCommitCount($commits = 0, $lastVersion = '') + { + if(!scm::checkRevision($lastVersion)) return false; + + chdir($this->root); + $revision = $this->branch ? $this->branch : 'HEAD'; + return execCmd(escapeCmd("$this->client rev-list --count $revision -- ./"), 'string'); + } + + /** + * Get first revision. + * + * @access public + * @return string + */ + public function getFirstRevision() + { + chdir($this->root); + $list = execCmd(escapeCmd("$this->client rev-list --reverse HEAD -- ./"), 'array'); + return $list[0]; + } + + /** + * Get latest revision + * + * @access public + * @return string + */ + public function getLatestRevision() + { + chdir($this->root); + $revision = $this->branch ? $this->branch : 'HEAD'; + $list = execCmd(escapeCmd("$this->client rev-list -1 $revision -- ./"), 'array'); + return $list[0]; + } + + /** + * Get commits. + * + * @param string $version + * @param int $count + * @param string $branch + * @access public + * @return array + */ + public function getCommits($version = '', $count = 0, $branch = '') + { + if(!scm::checkRevision($version)) return array(); + $api = "commits"; + $commits = array(); + $files = array(); + + if(empty($count)) $count = $this->pageLimit; + + if(!empty($version) and $count == 1) + { + $commits = $this->fetch($api, array('limit' => 1, 'sha' => $version)); + $commit = $commits[0]; + if(isset($commit->sha)) + { + $log = new stdclass; + $log->committer = $commit->commit->author->name; + $log->revision = $commit->sha; + $log->comment = $commit->commit->message; + $log->time = date('Y-m-d H:i:s', strtotime($commit->commit->author->date)); + + $commits[$commit->sha] = $log; + $files[$commit->sha] = $this->getFilesByCommit($log->revision); + + return array('commits' => $commits, 'files' => $files); + } + } + + $params['sha'] = $branch; + if($version and $version != 'HEAD') + { + /* Get since param. */ + if(substr($version, 0, 5) == 'since') + { + $since = true; + $version = substr($version, 5); + } + + $committedDate = $this->getCommittedDate($version); + if(!$committedDate) return array('commits' => array(), 'files' => array()); + + if(!empty($since)) + { + $params['since'] = $committedDate; + } + else + { + $params['until'] = $committedDate; + } + } + + $list = $this->fetch($api, $params); + + foreach($list as $commit) + { + if(!isset($commit->commit) or !is_object($commit->commit)) continue; + + $log = new stdclass; + $log->committer = $commit->commit->author->name; + $log->revision = $commit->sha; + $log->comment = $commit->commit->message; + $log->time = date('Y-m-d H:i:s', strtotime($commit->commit->author->date)); + + $commits[$commit->sha] = $log; + $files[$commit->sha] = $this->getFilesByCommit($log->revision); + } + + return array('commits' => $commits, 'files' => $files); + } + + /** + * getCommit + * + * @param int $sha + * @access public + * @return void + */ + public function getCommittedDate($sha) + { + if(!scm::checkRevision($sha)) return null; + if(!$sha or $sha == 'HEAD') return date('c'); + + global $dao; + $time = $dao->select('time')->from(TABLE_REPOHISTORY)->where('revision')->eq($sha)->fetch('time'); + if($time) return date('c', strtotime($time)); + + $params = array(); + $params['sha'] = $sha; + $params['limit'] = 1; + $result = $this->fetch("commits", $params); + return (isset($resulti[0]->created)) ? date('Y-m-d H:i:s', strtotime($result->created)) : false; + } + + /** + * Get commits by path. + * + * @param string $path + * @param string $fromRevision + * @param string $toRevision + * @param int $perPage + * @access public + * @return array + */ + public function getCommitsByPath($path, $fromRevision = '', $toRevision = '', $perPage = 0, $limit = 0) + { + $path = ltrim($path, DIRECTORY_SEPARATOR); + $api = "commits"; + + if(!$limit) $limit = $this->pageLimit; + $param = new stdclass(); + $param->path = urldecode($path); + $param->limit = $limit; + $param->sha = ($toRevision != 'HEAD' and $toRevision) ? $toRevision : $this->branch; + + if($perPage) $param->page = $perPage; + return $this->fetch($api, $param); + } + + /** + * Get diff files by reviesion. + * + * @param int $reviesion + * @access public + * @return array + */ + public function getDiffFiles($revision) + { + $diffApi = "{$this->root}git/commits/$revision.patch?token={$this->token}"; + $diffs = commonModel::http($diffApi); + + $newFiles = array(); + $delFiles = array(); + + if(!empty($diffs)) + { + $diffs = explode("\n", $diffs); + foreach($diffs as $row) + { + preg_match('/^(\s)(create|delete)\smode\s\d+\s(.+)$/', $row, $matches); + if(count($matches) == 4 and !in_array($matches[3], $newFiles) and !in_array($matches[3], $delFiles)) + { + if($matches[2] == 'create') + { + $newFiles[] = $matches[3]; + } + elseif($matches[2] == 'delete') + { + $delFiles[] = $matches[3]; + } + } + } + } + + return array('newFiles' => $newFiles, 'delFiles' => $delFiles); + } + + /** + * Get files by commit. + * + * @param string $commit + * @access public + * @return void + */ + public function getFilesByCommit($revision) + { + if(!scm::checkRevision($revision)) return array(); + $api = "git/commits/$revision"; + $results = $this->fetch($api); + if(empty($results)) return array(); + + $diffFiles = $this->getDiffFiles($revision); + $files = array(); + foreach($results->files as $row) + { + $file = new stdclass(); + $file->revision = $revision; + $file->type = 'file'; + $file->path = '/' . $row->filename; + $file->action = 'M'; + if(in_array($row->filename, $diffFiles['newFiles'])) + { + $file->action = 'A'; + } + elseif(in_array($row->filename, $diffFiles['delFiles'])) + { + $file->action = 'D'; + } + + $files[] = $file; + } + + return $files; + } + + /** + * Repository/tree api. + * + * @param string $path + * @param bool $recursive + * @access public + * @return mixed + */ + public function tree($path, $recursive = 1) + { + $api = "contents"; + + $params = array(); + $params['path'] = ltrim($path, '/'); + $params['ref'] = $this->branch; + $params['recursive'] = (int) $recursive; + return $this->fetch($api, $params); + } + + /** + * Fetch data from gitea api. + * + * @param string $api + * @access public + * @return mixed + */ + public function fetch($api, $params = array(), $needToLoop = false) + { + $params = (array) $params; + $params['token'] = $this->token; + $params['limit'] = isset($params['limit']) ? $params['limit'] : $this->pageLimit; + + $api = ltrim($api, '/'); + $api = $this->root . $api . '?' . http_build_query($params); + if($needToLoop) + { + $allResults = array(); + for($page = 1; true; $page++) + { + $results = json_decode(commonModel::http($api . "&page={$page}")); + if(!is_array($results)) break; + if(!empty($results)) $allResults = array_merge($allResults, $results); + if(count($results) < $this->pageLimit) break; + } + + return $allResults; + } + else + { + $response = commonModel::http($api); + if(!empty(commonModel::$requestErrors)) + { + commonModel::$requestErrors = array(); + return array(); + } + + return json_decode($response); + } + } + + /** + * Format bytes shown. + * + * @param int $size + * @static + * @access public + * @return string + */ + public static function formatBytes($size) + { + if($size < 1024) return $size . 'Bytes'; + if(round($size / (1024 * 1024), 2) > 1) return round($size / (1024 * 1024), 2) . 'G'; + if(round($size / 1024, 2) > 1) return round($size / 1024, 2) . 'M'; + return round($size, 2) . 'KB'; + } + + /** + * Parse log. + * + * @param array $logs + * @access public + * @return array + */ + public function parseLog($logs) + { + $parsedLogs = array(); + $i = 0; + foreach($logs as $commit) + { + if(!isset($commit->sha)) continue; + $parsedLog = new stdclass(); + $parsedLog->revision = $commit->sha; + $parsedLog->committer = $commit->commit->author->name; + $parsedLog->time = date('Y-m-d H:i:s', strtotime($commit->commit->author->date)); + $parsedLog->comment = $commit->commit->message; + $parsedLog->change = array(); + foreach($commit->diffs as $diff) + { + $parsedLog->change[$diff->path] = array(); + $parsedLog->change[$diff->path]['action'] = $diff->action; + $parsedLog->change[$diff->path]['kind'] = $diff->type; + } + $parsedLogs[] = $parsedLog; + } + + return $parsedLogs; + } + + /** + * Get download url. + * + * @param string $branch + * @param string $ext + * @access public + * @return string + */ + public function getDownloadUrl($branch = 'master', $ext = 'zip') + { + $params['token'] = $this->token; + + return "{$this->root}archive/" . urlencode($branch) . ".{$ext}" . '?' . http_build_query($params); + } +} diff --git a/lib/scm/gitlab.class.php b/lib/scm/gitlab.class.php index 324d03a9cc..583b0b1475 100644 --- a/lib/scm/gitlab.class.php +++ b/lib/scm/gitlab.class.php @@ -834,4 +834,21 @@ class gitlab return $parsedLogs; } + + /** + * Get download url. + * + * @param string $branch + * @param string $ext + * @access public + * @return string + */ + public function getDownloadUrl($branch = 'master', $ext = 'zip') + { + $params = (array) $params; + $params['private_token'] = $this->token; + $params['sha'] = $branch; + + return "{$this->root}archive.{$ext}" . '?' . http_build_query($params); + } } diff --git a/lib/scm/scm.class.php b/lib/scm/scm.class.php index 06648998c3..4dc69b4236 100644 --- a/lib/scm/scm.class.php +++ b/lib/scm/scm.class.php @@ -244,6 +244,19 @@ class scm if(preg_match('/[^a-z0-9\-_\.\^\w][\x{4e00}-\x{9fa5}]/ui', $revision)) return false; return true; } + + /** + * Get download url. + * + * @param string $branch + * @param string $ext + * @access public + * @return string + */ + public function getDownloadUrl($branch = '', $ext = 'zip') + { + return $this->engine->getDownloadUrl($branch, $ext); + } } /** diff --git a/module/action/config.php b/module/action/config.php index cc841bc007..000cdcd5cf 100755 --- a/module/action/config.php +++ b/module/action/config.php @@ -46,6 +46,7 @@ $config->action->objectNameFields['gitlab'] = 'name'; $config->action->objectNameFields['gitea'] = 'name'; $config->action->objectNameFields['stage'] = 'name'; $config->action->objectNameFields['apistruct'] = 'name'; +$config->action->objectNameFields['repo'] = 'name'; $config->action->commonImgSize = 870; @@ -61,7 +62,7 @@ $config->action->majorList['execution'] = array('opened', 'edited'); $config->action->needGetProjectType = 'build,task,bug,case,testcase,caselib,testtask,testsuite,testreport,doc,issue,release,risk,design,opportunity,trainplan,gapanalysis,researchplan,researchreport,'; $config->action->needGetRelateField = ',story,productplan,release,task,build,bug,testcase,case,testtask,testreport,doc,doclib,issue,risk,opportunity,trainplan,gapanalysis,team,whitelist,researchplan,researchreport,meeting,kanbanlane,kanbancolumn,module,'; -$config->action->noLinkModules = ',doclib,module,webhook,gitlab,gitea,sonarqube,pipeline,jenkins,kanban,kanbanspace,kanbancolumn,kanbanlane,kanbanregion,kanbancard,execution,project,traincategory,apistruct,program,product,user,entry,'; +$config->action->noLinkModules = ',doclib,module,webhook,gitlab,gitea,sonarqube,pipeline,jenkins,kanban,kanbanspace,kanbancolumn,kanbanlane,kanbanregion,kanbancard,execution,project,traincategory,apistruct,program,product,user,entry,repo,'; $config->action->preferredTypeNum = 10; diff --git a/module/action/lang/de.php b/module/action/lang/de.php index d3f0dbfedd..a98ee3c3c0 100644 --- a/module/action/lang/de.php +++ b/module/action/lang/de.php @@ -133,6 +133,7 @@ $lang->action->objectTypes['gitlabbranch'] = 'GitLab Branch'; $lang->action->objectTypes['gitlabbranchpriv'] = 'GitLab Protected Branches'; $lang->action->objectTypes['gitlabtag'] = 'GitLab Tag'; $lang->action->objectTypes['gitlabtagpriv'] = 'GitLab Tag Protected'; +$lang->action->objectTypes['giteauser'] = 'Gitea User'; $lang->action->objectTypes['kanbanspace'] = 'Kanban Space'; $lang->action->objectTypes['kanban'] = 'Kanban'; $lang->action->objectTypes['kanbanregion'] = 'Kanban Region'; @@ -143,6 +144,7 @@ $lang->action->objectTypes['sonarqube'] = 'SonarQube Server'; $lang->action->objectTypes['sonarqubeproject'] = 'SonarQube Project'; $lang->action->objectTypes['stage'] = 'Stage'; $lang->action->objectTypes['patch'] = 'Patch'; +$lang->action->objectTypes['repo'] = 'Repo'; /* Used to describe operation history. */ $lang->action->desc = new stdclass(); diff --git a/module/action/lang/en.php b/module/action/lang/en.php index 0ab354241a..26fecace0b 100755 --- a/module/action/lang/en.php +++ b/module/action/lang/en.php @@ -133,6 +133,7 @@ $lang->action->objectTypes['gitlabbranch'] = 'GitLab Branch'; $lang->action->objectTypes['gitlabbranchpriv'] = 'GitLab Protected Branches'; $lang->action->objectTypes['gitlabtag'] = 'GitLab Tag'; $lang->action->objectTypes['gitlabtagpriv'] = 'GitLab Tag Protected'; +$lang->action->objectTypes['giteauser'] = 'Gitea User'; $lang->action->objectTypes['kanbanspace'] = 'Kanban Space'; $lang->action->objectTypes['kanban'] = 'Kanban'; $lang->action->objectTypes['kanbanregion'] = 'Kanban Region'; @@ -143,6 +144,7 @@ $lang->action->objectTypes['sonarqube'] = 'SonarQube Server'; $lang->action->objectTypes['sonarqubeproject'] = 'SonarQube Project'; $lang->action->objectTypes['stage'] = 'Stage'; $lang->action->objectTypes['patch'] = 'Patch'; +$lang->action->objectTypes['repo'] = 'Repo'; /* Used to describe operation history. */ $lang->action->desc = new stdclass(); diff --git a/module/action/lang/fr.php b/module/action/lang/fr.php index 01c2a52222..2d4a6ca759 100644 --- a/module/action/lang/fr.php +++ b/module/action/lang/fr.php @@ -133,6 +133,7 @@ $lang->action->objectTypes['gitlabbranch'] = 'GitLab Branch'; $lang->action->objectTypes['gitlabbranchpriv'] = 'GitLab Protected Branches'; $lang->action->objectTypes['gitlabtag'] = 'GitLab Tag'; $lang->action->objectTypes['gitlabtagpriv'] = 'GitLab Tag Protected'; +$lang->action->objectTypes['giteauser'] = 'Gitea User'; $lang->action->objectTypes['kanbanspace'] = 'Kanban Space'; $lang->action->objectTypes['kanban'] = 'Kanban'; $lang->action->objectTypes['kanbanregion'] = 'Kanban Region'; @@ -143,6 +144,7 @@ $lang->action->objectTypes['sonarqube'] = 'SonarQube Server'; $lang->action->objectTypes['sonarqubeproject'] = 'SonarQube Project'; $lang->action->objectTypes['stage'] = 'Stage'; $lang->action->objectTypes['patch'] = 'Patch'; +$lang->action->objectTypes['repo'] = 'Repo'; /* Used to describe operation history. */ $lang->action->desc = new stdclass(); diff --git a/module/action/lang/vi.php b/module/action/lang/vi.php index 681dc1b71f..56a4ac1674 100644 --- a/module/action/lang/vi.php +++ b/module/action/lang/vi.php @@ -111,6 +111,7 @@ $lang->action->objectTypes['gitlabbranch'] = 'GitLab Branch'; $lang->action->objectTypes['gitlabbranchpriv'] = 'GitLab Protected Branches'; $lang->action->objectTypes['gitlabtag'] = 'GitLab Tag'; $lang->action->objectTypes['gitlabtagpriv'] = 'GitLab Tag Protected'; +$lang->action->objectTypes['giteauser'] = 'Gitea User'; $lang->action->objectTypes['kanbanspace'] = 'Kanban Space'; $lang->action->objectTypes['kanban'] = 'Kanban'; $lang->action->objectTypes['kanbanregion'] = 'Kanban Region'; @@ -121,6 +122,7 @@ $lang->action->objectTypes['sonarqube'] = 'SonarQube Server'; $lang->action->objectTypes['sonarqubeproject'] = 'SonarQube Project'; $lang->action->objectTypes['stage'] = 'Stage'; $lang->action->objectTypes['patch'] = 'Patch'; +$lang->action->objectTypes['repo'] = 'Repo'; /* Used to describe operation history. */ $lang->action->desc = new stdclass(); diff --git a/module/action/lang/zh-cn.php b/module/action/lang/zh-cn.php index a9011287c3..8efd487286 100755 --- a/module/action/lang/zh-cn.php +++ b/module/action/lang/zh-cn.php @@ -133,6 +133,7 @@ $lang->action->objectTypes['gitlabbranch'] = 'GitLab分支'; $lang->action->objectTypes['gitlabbranchpriv'] = 'GitLab保护分支'; $lang->action->objectTypes['gitlabtag'] = 'GitLab标签'; $lang->action->objectTypes['gitlabtagpriv'] = 'GitLab标签保护'; +$lang->action->objectTypes['giteauser'] = 'Gitea用户'; $lang->action->objectTypes['kanbanspace'] = '看板空间'; $lang->action->objectTypes['kanban'] = '看板'; $lang->action->objectTypes['kanbanregion'] = '看板区域'; @@ -143,6 +144,7 @@ $lang->action->objectTypes['sonarqube'] = 'SonarQube服务器'; $lang->action->objectTypes['sonarqubeproject'] = 'SonarQube项目'; $lang->action->objectTypes['stage'] = '阶段'; $lang->action->objectTypes['patch'] = '补丁'; +$lang->action->objectTypes['repo'] = '代码库'; /* 用来描述操作历史记录。*/ $lang->action->desc = new stdclass(); diff --git a/module/action/lang/zh-tw.php b/module/action/lang/zh-tw.php index 9a66017d80..dbdac896d3 100755 --- a/module/action/lang/zh-tw.php +++ b/module/action/lang/zh-tw.php @@ -127,12 +127,14 @@ $lang->action->objectTypes['gitlabgroup'] = 'GitLab群組'; $lang->action->objectTypes['gitlabbranch'] = 'GitLab分支'; $lang->action->objectTypes['gitlabbranchpriv'] = 'GitLab保護分支'; $lang->action->objectTypes['gitlabtag'] = 'GitLab標籤'; +$lang->action->objectTypes['giteauser'] = 'Gitea用戶'; $lang->action->objectTypes['kanbanspace'] = '看板空間'; $lang->action->objectTypes['kanban'] = '看板'; $lang->action->objectTypes['kanbanregion'] = '看板區域'; $lang->action->objectTypes['kanbanlane'] = '看板泳道'; $lang->action->objectTypes['kanbancolumn'] = '看板列'; $lang->action->objectTypes['kanbancard'] = '看板卡片'; +$lang->action->objectTypes['repo'] = '代码库'; /* 用來描述操作歷史記錄。*/ $lang->action->desc = new stdclass(); diff --git a/module/action/model.php b/module/action/model.php index 56cbf24af1..8aec1a5db1 100755 --- a/module/action/model.php +++ b/module/action/model.php @@ -984,13 +984,16 @@ class actionModel extends model $executions = array(); if(!$this->app->user->admin) { - if($productID == 'all') $authedProducts = $this->app->user->view->products; - if($projectID == 'all') $authedProjects = $this->app->user->view->projects; - if($executionID == 'all') $authedExecutions = $this->app->user->view->sprints; + $aclViews = isset($this->app->user->rights['acls']['views']) ? $this->app->user->rights['acls']['views'] : array(); + if($productID == 'all') $authedProducts = (empty($aclViews) or (!empty($aclViews) and !empty($aclViews['product']))) ? $this->app->user->view->products : '0'; + if($projectID == 'all') $authedProjects = (empty($aclViews) or (!empty($aclViews) and !empty($aclViews['project']))) ? $this->app->user->view->projects : '0'; + if($executionID == 'all') $authedExecutions = (empty($aclViews) or (!empty($aclViews) and !empty($aclViews['execution']))) ? $this->app->user->view->sprints : '0'; if($productID == 'all' and $projectID == 'all') { - $productCondition = "product " . helper::dbIN($authedProducts); + $productCondition = ''; + foreach(explode(',', $authedProducts) as $product) $productCondition = empty($productCondition) ? "product LIKE '%,$product,%'" : "$productCondition OR product LIKE '%,$product,%'"; + $projectCondition = "project " . helper::dbIN($authedProjects); $executionCondition = isset($authedExecutions) ? "execution " . helper::dbIN($authedExecutions) : ''; } @@ -999,7 +1002,9 @@ class actionModel extends model $products = $this->loadModel('product')->getProductPairsByProject($projectID); $executions = $this->loadModel('execution')->getPairs($projectID) + array(0 => 0); - $productCondition = "product " . helper::dbIN(array_keys($products)); + $productCondition = ''; + foreach(array_keys($products) as $product) $productCondition = empty($productCondition) ? "product LIKE '%,$product,%'" : "$productCondition OR product LIKE '%,$product,%'"; + $projectCondition = "project = $projectID"; $executionCondition = "execution " . helper::dbIN(array_keys($executions)); } @@ -1014,7 +1019,7 @@ class actionModel extends model $executionCondition = "execution " . helper::dbIN(array_keys($executions)); } - $condition = "((product =',0,' or product=0) AND project = '0' AND execution = 0)"; + $condition = "((product =',0,' or product = '0') AND project = '0' AND execution = '0')"; if(!empty($productCondition)) $condition .= ' OR ' . $productCondition; if(!empty($projectCondition)) $condition .= ' OR ' . $projectCondition; if(!empty($executionCondition)) $condition .= ' OR ' . $executionCondition; @@ -1086,6 +1091,7 @@ class actionModel extends model foreach($this->app->user->rights['acls']['actions'] as $moduleName => $actions) { + if(isset($this->lang->mainNav->$moduleName) and !empty($this->app->user->rights['acls']['views']) and !isset($this->app->user->rights['acls']['views'][$moduleName])) continue; $actionCondition .= "(`objectType` = '$moduleName' and `action` " . helper::dbIN($actions) . ") or "; } $actionCondition = trim($actionCondition, 'or '); @@ -1254,15 +1260,11 @@ class actionModel extends model /* If action type is login or logout, needn't link. */ if($actionType == 'svncommited' or $actionType == 'gitcommited') $action->actor = zget($commiters, $action->actor); - /* Get gitlab objectname. */ - if(empty($action->objectName) and substr($objectType, 0, 6) == 'gitlab') $action->objectName = $action->extra; + /* Get gitlab or gitea objectname. */ + if(empty($action->objectName) and (substr($objectType, 0, 6) == 'gitlab' or substr($objectType, 0, 5) == 'gitea')) $action->objectName = $action->extra; /* Other actions, create a link. */ - if(!$this->setObjectLink($action, $deptUsers)) - { - unset($actions[$i]); - continue; - } + $this->setObjectLink($action, $deptUsers); /* Set merge request link. */ if(empty($action->objectName) and $action->objectType == 'mr') $action->objectLink = ''; @@ -1437,7 +1439,6 @@ class actionModel extends model /* Fix bug #2961. */ $isLoginOrLogout = $action->objectType == 'user' and ($action->action == 'login' or $action->action == 'logout'); - if(!common::hasPriv($moduleName, $methodName) and !$isLoginOrLogout) return false; $action->objectLabel = $objectLabel; $action->product = trim($action->product, ','); @@ -1536,6 +1537,7 @@ class actionModel extends model $action->objectLink = !isset($deptUsers[$action->objectID]) ? 'javascript:void(0)' : helper::createLink($moduleName, $methodName, sprintf($vars, $action->objectID)); } } + if(!common::hasPriv($moduleName, $methodName) and !$isLoginOrLogout) $action->objectLink = ''; } elseif($action->objectType == 'team') { diff --git a/module/block/view/dynamic.html.php b/module/block/view/dynamic.html.php index e2e44aac2d..6e0102b36c 100644 --- a/module/block/view/dynamic.html.php +++ b/module/block/view/dynamic.html.php @@ -25,7 +25,7 @@ $class = $action->major ? "class='active'" : ''; echo "