An updated theme (based on Atticus Finch) for ClassicPress
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

288 lines
7.8 KiB

3 years ago
  1. <?php
  2. if ( !class_exists('Puc_v4p1_Vcs_GitHubApi', false) ):
  3. class Puc_v4p1_Vcs_GitHubApi extends Puc_v4p1_Vcs_Api {
  4. /**
  5. * @var string GitHub username.
  6. */
  7. protected $userName;
  8. /**
  9. * @var string GitHub repository name.
  10. */
  11. protected $repositoryName;
  12. /**
  13. * @var string Either a fully qualified repository URL, or just "user/repo-name".
  14. */
  15. protected $repositoryUrl;
  16. /**
  17. * @var string GitHub authentication token. Optional.
  18. */
  19. protected $accessToken;
  20. public function __construct($repositoryUrl, $accessToken = null) {
  21. $path = @parse_url($repositoryUrl, PHP_URL_PATH);
  22. if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) {
  23. $this->userName = $matches['username'];
  24. $this->repositoryName = $matches['repository'];
  25. } else {
  26. throw new InvalidArgumentException('Invalid GitHub repository URL: "' . $repositoryUrl . '"');
  27. }
  28. parent::__construct($repositoryUrl, $accessToken);
  29. }
  30. /**
  31. * Get the latest release from GitHub.
  32. *
  33. * @return Puc_v4p1_Vcs_Reference|null
  34. */
  35. public function getLatestRelease() {
  36. $release = $this->api('/repos/:user/:repo/releases/latest');
  37. if ( is_wp_error($release) || !is_object($release) || !isset($release->tag_name) ) {
  38. return null;
  39. }
  40. $reference = new Puc_v4p1_Vcs_Reference(array(
  41. 'name' => $release->tag_name,
  42. 'version' => ltrim($release->tag_name, 'v'), //Remove the "v" prefix from "v1.2.3".
  43. 'downloadUrl' => $this->signDownloadUrl($release->zipball_url),
  44. 'updated' => $release->created_at,
  45. 'apiResponse' => $release,
  46. ));
  47. if ( !empty($release->body) ) {
  48. /** @noinspection PhpUndefinedClassInspection */
  49. $reference->changelog = Parsedown::instance()->text($release->body);
  50. }
  51. if ( isset($release->assets[0]) ) {
  52. $reference->downloadCount = $release->assets[0]->download_count;
  53. }
  54. return $reference;
  55. }
  56. /**
  57. * Get the tag that looks like the highest version number.
  58. *
  59. * @return Puc_v4p1_Vcs_Reference|null
  60. */
  61. public function getLatestTag() {
  62. $tags = $this->api('/repos/:user/:repo/tags');
  63. if ( is_wp_error($tags) || empty($tags) || !is_array($tags) ) {
  64. return null;
  65. }
  66. $versionTags = $this->sortTagsByVersion($tags);
  67. if ( empty($versionTags) ) {
  68. return null;
  69. }
  70. $tag = $versionTags[0];
  71. return new Puc_v4p1_Vcs_Reference(array(
  72. 'name' => $tag->name,
  73. 'version' => ltrim($tag->name, 'v'),
  74. 'downloadUrl' => $this->signDownloadUrl($tag->zipball_url),
  75. 'apiResponse' => $tag,
  76. ));
  77. }
  78. /**
  79. * Get a branch by name.
  80. *
  81. * @param string $branchName
  82. * @return null|Puc_v4p1_Vcs_Reference
  83. */
  84. public function getBranch($branchName) {
  85. $branch = $this->api('/repos/:user/:repo/branches/' . $branchName);
  86. if ( is_wp_error($branch) || empty($branch) ) {
  87. return null;
  88. }
  89. $reference = new Puc_v4p1_Vcs_Reference(array(
  90. 'name' => $branch->name,
  91. 'downloadUrl' => $this->buildArchiveDownloadUrl($branch->name),
  92. 'apiResponse' => $branch,
  93. ));
  94. if ( isset($branch->commit, $branch->commit->commit, $branch->commit->commit->author->date) ) {
  95. $reference->updated = $branch->commit->commit->author->date;
  96. }
  97. return $reference;
  98. }
  99. /**
  100. * Get the latest commit that changed the specified file.
  101. *
  102. * @param string $filename
  103. * @param string $ref Reference name (e.g. branch or tag).
  104. * @return StdClass|null
  105. */
  106. public function getLatestCommit($filename, $ref = 'master') {
  107. $commits = $this->api(
  108. '/repos/:user/:repo/commits',
  109. array(
  110. 'path' => $filename,
  111. 'sha' => $ref,
  112. )
  113. );
  114. if ( !is_wp_error($commits) && is_array($commits) && isset($commits[0]) ) {
  115. return $commits[0];
  116. }
  117. return null;
  118. }
  119. /**
  120. * Get the timestamp of the latest commit that changed the specified branch or tag.
  121. *
  122. * @param string $ref Reference name (e.g. branch or tag).
  123. * @return string|null
  124. */
  125. public function getLatestCommitTime($ref) {
  126. $commits = $this->api('/repos/:user/:repo/commits', array('sha' => $ref));
  127. if ( !is_wp_error($commits) && is_array($commits) && isset($commits[0]) ) {
  128. return $commits[0]->commit->author->date;
  129. }
  130. return null;
  131. }
  132. /**
  133. * Perform a GitHub API request.
  134. *
  135. * @param string $url
  136. * @param array $queryParams
  137. * @return mixed|WP_Error
  138. */
  139. protected function api($url, $queryParams = array()) {
  140. $variables = array(
  141. 'user' => $this->userName,
  142. 'repo' => $this->repositoryName,
  143. );
  144. foreach ($variables as $name => $value) {
  145. $url = str_replace('/:' . $name, '/' . urlencode($value), $url);
  146. }
  147. $url = 'https://api.github.com' . $url;
  148. if ( !empty($this->accessToken) ) {
  149. $queryParams['access_token'] = $this->accessToken;
  150. }
  151. if ( !empty($queryParams) ) {
  152. $url = add_query_arg($queryParams, $url);
  153. }
  154. $options = array('timeout' => 10);
  155. if ( !empty($this->httpFilterName) ) {
  156. $options = apply_filters($this->httpFilterName, $options);
  157. }
  158. $response = wp_remote_get($url, $options);
  159. if ( is_wp_error($response) ) {
  160. return $response;
  161. }
  162. $code = wp_remote_retrieve_response_code($response);
  163. $body = wp_remote_retrieve_body($response);
  164. if ( $code === 200 ) {
  165. $document = json_decode($body);
  166. return $document;
  167. }
  168. return new WP_Error(
  169. 'puc-github-http-error',
  170. 'GitHub API error. HTTP status: ' . $code
  171. );
  172. }
  173. /**
  174. * Get the contents of a file from a specific branch or tag.
  175. *
  176. * @param string $path File name.
  177. * @param string $ref
  178. * @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error.
  179. */
  180. public function getRemoteFile($path, $ref = 'master') {
  181. $apiUrl = '/repos/:user/:repo/contents/' . $path;
  182. $response = $this->api($apiUrl, array('ref' => $ref));
  183. if ( is_wp_error($response) || !isset($response->content) || ($response->encoding !== 'base64') ) {
  184. return null;
  185. }
  186. return base64_decode($response->content);
  187. }
  188. /**
  189. * Generate a URL to download a ZIP archive of the specified branch/tag/etc.
  190. *
  191. * @param string $ref
  192. * @return string
  193. */
  194. public function buildArchiveDownloadUrl($ref = 'master') {
  195. $url = sprintf(
  196. 'https://api.github.com/repos/%1$s/%2$s/zipball/%3$s',
  197. urlencode($this->userName),
  198. urlencode($this->repositoryName),
  199. urlencode($ref)
  200. );
  201. if ( !empty($this->accessToken) ) {
  202. $url = $this->signDownloadUrl($url);
  203. }
  204. return $url;
  205. }
  206. /**
  207. * Get a specific tag.
  208. *
  209. * @param string $tagName
  210. * @return Puc_v4p1_Vcs_Reference|null
  211. */
  212. public function getTag($tagName) {
  213. //The current GitHub update checker doesn't use getTag, so I didn't bother to implement it.
  214. throw new LogicException('The ' . __METHOD__ . ' method is not implemented and should not be used.');
  215. }
  216. public function setAuthentication($credentials) {
  217. parent::setAuthentication($credentials);
  218. $this->accessToken = is_string($credentials) ? $credentials : null;
  219. }
  220. /**
  221. * Figure out which reference (i.e tag or branch) contains the latest version.
  222. *
  223. * @param string $configBranch Start looking in this branch.
  224. * @return null|Puc_v4p1_Vcs_Reference
  225. */
  226. public function chooseReference($configBranch) {
  227. $updateSource = null;
  228. if ( $configBranch === 'master' ) {
  229. //Use the latest release.
  230. $updateSource = $this->getLatestRelease();
  231. if ( $updateSource === null ) {
  232. //Failing that, use the tag with the highest version number.
  233. $updateSource = $this->getLatestTag();
  234. }
  235. }
  236. //Alternatively, just use the branch itself.
  237. if ( empty($updateSource) ) {
  238. $updateSource = $this->getBranch($configBranch);
  239. }
  240. return $updateSource;
  241. }
  242. /**
  243. * @param string $url
  244. * @return string
  245. */
  246. public function signDownloadUrl($url) {
  247. if ( empty($this->credentials) ) {
  248. return $url;
  249. }
  250. return add_query_arg('access_token', $this->credentials, $url);
  251. }
  252. }
  253. endif;