diff --git a/build.xml b/build.xml
index 4ba366c..ffd852c 100644
--- a/build.xml
+++ b/build.xml
@@ -25,6 +25,9 @@
]]>
+
+
+
@@ -450,6 +453,9 @@
--updateSetName '${updateSetName}'
--targetVersion '${targetVersion}'
--targetVersionCode '${targetVersionCode}'
+ --issuesRepository '${issuesRepository}'
+ --resolvedIssuesMilestone '${resolvedIssuesMilestone}'
+ --resolvedIssuesLink '${resolvedIssuesLink}'
--distVersionDataFile '${distVersionDataFile}'" />
diff --git a/php/version-data.php b/php/version-data.php
index 878f622..3bd54b7 100644
--- a/php/version-data.php
+++ b/php/version-data.php
@@ -11,9 +11,20 @@
'updateSetName:',
'targetVersion:',
'targetVersionCode:',
+ 'issuesRepository:',
+ 'resolvedIssuesMilestone:',
+ 'resolvedIssuesLink:',
'distVersionDataFile:',
]);
+define('MY_USERAGENT', 'mybb/mybb-build');
+define('DEFAULT_CURLOPTS', [
+ CURLOPT_USERAGENT => MY_USERAGENT,
+ CURLOPT_TIMEOUT => 10,
+ CURLOPT_RETURNTRANSFER => 1,
+ CURLOPT_SSL_VERIFYPEER => 1,
+]);
+
function arrayToYml($array, $level = 1)
{
$output = null;
@@ -60,6 +71,144 @@ function directoryStructureSort($a, $b) {
}
}
+function fetchJson($ch, $path) {
+ $curlopt = [
+ CURLOPT_URL => $path,
+ ] + DEFAULT_CURLOPTS;
+
+ curl_setopt_array($ch, $curlopt);
+
+ $response = curl_exec($ch);
+
+ $data = json_decode($response, true);
+
+ if ($data === null) {
+ return null;
+ }
+
+ return $data;
+}
+
+function fetchJsonPaged($ch, $path, $sep, $callback, $perPage=100) {
+ $results = [];
+ $page = 1;
+
+ $curlopt = [
+ CURLOPT_HEADER => 1,
+ ] + DEFAULT_CURLOPTS;
+
+ while ($page !== null) {
+ $curlopt[CURLOPT_URL] = $path . $sep . 'per_page=' . $perPage . '&page=' . $page;
+
+ curl_setopt_array($ch, $curlopt);
+
+ $response = curl_exec($ch);
+
+ $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
+ $header = substr($response, 0, $header_size);
+ $body = substr($response, $header_size);
+
+ $data = json_decode($body, true);
+
+ if ($data === null) {
+ break;
+ }
+
+ foreach ($data as $item) {
+ $result = $callback($item);
+ if ($result === null) {
+ continue;
+ }
+
+ $results[] = $result;
+ }
+
+ preg_match('/^Link:(.*?)&page=([0-9]+)>; rel="next"/im', $header, $matches);
+
+ if ($matches) {
+ $page = $matches[2];
+ } else {
+ $page = null;
+ }
+ }
+
+ return $results;
+}
+
+function getGithubIssueAges($ch, $repo, $resolvedIssuesMilestone) {
+ $githubApiPath = 'https://api.github.com/repos/' . $repo . '/';
+
+ $milestones = fetchJsonPaged($ch, $githubApiPath . 'milestones?state=all', '&', function($item) use ($resolvedIssuesMilestone) {
+ if ($item['title'] === $resolvedIssuesMilestone) {
+ return $item;
+ }
+
+ return null;
+ });
+
+ if (empty($milestones[0])) {
+ return null;
+ }
+
+ $path = $githubApiPath . 'issues?milestone=' . $milestones[0]['number'] . '&state=closed';
+
+ $issueAges = fetchJsonPaged($ch, $path, '&', function($item) {
+ if (empty($item['closed_at']) || !empty($item['pull_request'])) {
+ return null;
+ }
+
+ $created = DateTime::createFromFormat(DateTime::ISO8601, $item['created_at']);
+ $closed = DateTime::createFromFormat(DateTime::ISO8601, $item['closed_at']);
+
+ if ($created === false || $closed === false) {
+ return null;
+ }
+
+ $interval = $created->diff($closed);
+ return (int) $interval->format('%a');
+ });
+
+ return $issueAges;
+}
+
+function getIssuesData($ch, $issuesRepository, $resolvedIssuesMilestone) {
+ $parsed = parse_url($issuesRepository);
+
+ if ($parsed === false) {
+ return null;
+ }
+
+ $repo = trim(preg_replace('/\.git$/', '', $parsed['path']), '/');
+
+ switch ($parsed['host']) {
+ case 'github.com':
+ $issueAges = getGithubIssueAges($ch, $repo, $resolvedIssuesMilestone);
+ break;
+ default:
+ return null;
+ }
+
+ sort($issueAges);
+
+ $numIssues = count($issueAges);
+
+ if ($numIssues == 0) {
+ return null;
+ }
+
+ if ($numIssues % 2 === 0) {
+ $median = ($issueAges[($numIssues/2)-1] + $issueAges[$numIssues/2]) / 2;
+ } else {
+ $median = $issueAges[intdiv($numIssues, 2)];
+ }
+
+ return [
+ 'medianAge' => $median,
+ 'meanAge' => round(array_sum($issueAges) / $numIssues, 1),
+ 'numIssues' => $numIssues,
+ ];
+}
+
// changed files YML
$file = $args['distChangedFilesFile'];
@@ -175,6 +324,30 @@ function directoryStructureSort($a, $b) {
}
}
+// resolved issue YML
+$resolvedIssuesYml = '';
+
+$ch = curl_init();
+
+$issuesData = getIssuesData($ch, $args['issuesRepository'], $args['resolvedIssuesMilestone']);
+
+if ($issuesData !== null) {
+ $resolvedIssuesYml .= <<