github: fix notices
[pharext/pharext.org] / app / Github / API.php
1 <?php
2
3 namespace app\Github;
4
5 use app\Github\API;
6 use app\Github\Storage;
7 use app\Github\Exception;
8 use app\Pharext;
9
10 use merry\Config;
11
12 use http\Client;
13 use http\QueryString;
14 use http\Url;
15
16 use Psr\Log\LoggerInterface;
17
18 class API
19 {
20 /**
21 * @var Client
22 */
23 private $client;
24
25 /**
26 * @var Storage
27 */
28 private $tokens;
29
30 /**
31 * @var Storage
32 */
33 private $cache;
34
35 /**
36 * @var merry\Config
37 */
38 private $config;
39
40 /**
41 * @var int
42 */
43 private $maxAge;
44
45 /**
46 * @var \Psr\Log\LoggerInterface;
47 */
48 private $logger;
49
50 /**
51 * Queued promises
52 * @var array
53 */
54 private $queue;
55
56 function __construct(Config $config, LoggerInterface $logger, Storage $tokens = null, Storage $cache = null) {
57 $this->logger = $logger;
58 $this->config = $config;
59 $this->client = new Client("curl", "github");
60 $this->client->configure($config->http->configure->toArray());
61 $this->client->attach(new ClientObserver($logger));
62 $this->tokens = $tokens ?: new Storage\Session;
63 $this->cache = $cache;
64 }
65
66 /**
67 * Set maximum acceptable age of cache items
68 * @param int $seconds
69 */
70 function setMaxAge($seconds) {
71 $this->maxAge = $seconds;
72 return $this;
73 }
74
75 function getMaxAge() {
76 return $this->maxAge;
77 }
78
79 function getLogger() {
80 return $this->logger;
81 }
82
83 function getConfig() {
84 return $this->config;
85 }
86
87 function getClient() {
88 return $this->client;
89 }
90
91 function getTokenStorage() {
92 return $this->tokens;
93 }
94
95 function getCacheStorage() {
96 return $this->cache;
97 }
98
99 function getCacheKey($ident, $page = null) {
100 return sprintf("%s:%s:%s", $this->getToken(), $ident, $page ?: 1);
101 }
102
103 function hasToken() {
104 if ($this->tokens->get("access_token", $token)) {
105 $access_token = $token->getValue();
106 if (isset($access_token)) {
107 return true;
108 }
109 $this->dropToken();
110 }
111 return false;
112 }
113
114 function setToken($token) {
115 $this->tokens->set("access_token", new Storage\Item(
116 $token,
117 $this->config->storage->token->ttl
118 ));
119 }
120
121 function getToken() {
122 if ($this->tokens->get("access_token", $token, true)) {
123 return $token->getValue();
124 }
125 if (isset($token)) {
126 $this->logger->notice("Token expired", $token);
127 throw new Exception\TokenExpired($token->getLTL());
128 }
129 throw new Exception\TokenNotSet;
130 }
131
132 function dropToken() {
133 $this->tokens->del("access_token");
134 }
135
136 function getAuthUrl($callback_url) {
137 $state = base64_encode(openssl_random_pseudo_bytes(24));
138 $this->tokens->set("state", new Storage\Item($state, 5*60));
139 $param = [
140 "state" => $state,
141 "client_id" => $this->config->client->id,
142 "scope" => $this->config->client->scope,
143 "redirect_uri" => (string) $callback_url,
144 ];
145 return new Url("https://github.com/login/oauth/authorize", [
146 "query" => new QueryString($param)
147 ], 0);
148 }
149
150 function fetchToken($code, $state) {
151 if (!$this->tokens->get("state", $orig_state, true)) {
152 if (isset($orig_state)) {
153 $this->logger->notice("State expired", compact("state", "orig_state"));
154 throw new Exception\StateExpired($orig_state->getLTL());
155 }
156 throw new Exception\StateNotSet;
157 }
158 if ($state !== $orig_state->getValue()) {
159 $this->logger->warning("State mismatch", compact("state", "orig_state"));
160 throw new Exception\StateMismatch($orig_state->getValue(), $state);
161 }
162
163 return $this->queue(new API\Users\ReadAuthToken($this, [
164 "code" => $code,
165 "client_id" => $this->config->client->id,
166 "client_secret" => $this->config->client->secret,
167 ]));
168 }
169
170 function queue(API\Call $call) {
171 return $this->queue[] = $call();
172 }
173
174 function drain() {
175 $queue = $this->queue;
176 $this->queue = array();
177 $this->client->send();
178 return $queue;
179 }
180
181 function readAuthUser() {
182 return $this->queue(new API\Users\ReadAuthUser($this));
183 }
184
185 function listRepos($page) {
186 return $this->queue(new API\Repos\ListRepos($this, compact("page")));
187 }
188
189 function readRepo($repo) {
190 return $this->queue(new API\Repos\ReadRepo($this, compact("repo")));
191 }
192
193 /**
194 * Check if the pharext webhook is set and return it
195 * @param array $hooks
196 * @return stdClass hook
197 */
198 function checkHook($hooks) {
199 if (!empty($hooks)) {
200 foreach ($hooks as $hook) {
201 if ($hook->name === "web" && $hook->config->url === $this->config->hook->url) {
202 return $hook;
203 }
204 }
205 }
206 return null;
207 }
208
209 /**
210 * Check if the pharext webhook is set for the repo and return it
211 * @param object $repo
212 * @return stdClass hook
213 */
214 function checkRepoHook($repo) {
215 if (!empty($repo->hooks)) {
216 return $this->checkHook($repo->hooks);
217 }
218 return null;
219 }
220
221 function listHooks($repo) {
222 return $this->queue(new API\Hooks\ListHooks($this, compact("repo")));
223 }
224
225 function listReleases($repo, $page) {
226 return $this->queue(new API\Releases\ListReleases($this, compact("repo", "page")));
227 }
228
229 function listTags($repo, $page) {
230 return $this->queue(new API\Tags\ListTags($this, compact("repo", "page")));
231 }
232
233 function readContents($repo, $path = null) {
234 return $this->queue(new API\Repos\ReadContents($this, compact("repo", "path")));
235 }
236
237 function createRepoHook($repo, $conf) {
238 return $this->queue(new API\Hooks\CreateHook($this, compact("repo", "conf")));
239 }
240
241 function updateRepoHook($repo, $id, $conf) {
242 return $this->queue(new API\Hooks\UpdateHook($this, compact("repo", "id", "conf")));
243 }
244
245 function deleteRepoHook($repo, $id) {
246 return $this->queue(new API\Hooks\DeleteHook($this, compact("repo", "id")));
247 }
248
249 function createRelease($repo, $tag) {
250 return $this->queue(new API\Releases\CreateRelease($this, compact("repo", "tag")));
251 }
252
253 function publishRelease($repo, $id, $tag) {
254 return $this->queue(new API\Releases\PublishRelease($this, compact("repo", "id", "tag")));
255 }
256
257 function createReleaseAsset($url, $asset, $type) {
258 return $this->queue(new API\Releases\CreateReleaseAsset($this, compact("url", "asset", "type")));
259 }
260
261 function listReleaseAssets($repo, $id) {
262 return $this->queue(new API\Releases\ListReleaseAssets($this, compact("repo", "id")));
263 }
264
265 function uploadAssetForRelease($repo, $release, $config = null) {
266 return $this->listHooks($repo->full_name)->then(function($result) use($release, $repo, $config) {
267 list($repo->hooks) = $result;
268 $phar = new Pharext\Package(
269 $repo->clone_url,
270 $release->tag_name,
271 $repo->name,
272 $config ?: (array) $this->checkRepoHook($repo)->config
273 );
274 $name = $phar->build();
275 $url = uri_template($release->upload_url, compact("name"));
276 $promise = $this->createReleaseAsset($url, $phar, "application/phar");
277 if ($release->draft) {
278 return $promise->then(function($result) use($release, $repo) {
279 return $this->publishRelease($repo->full_name, $release->id, $release->tag_name);
280 });
281 }
282 return $promise;
283 });
284 }
285
286 function createReleaseFromTag($repo, $tag_name, $config = null) {
287 return $this->createRelease($repo->full_name, $tag_name)->then(function($result) use($repo, $config) {
288 list($release) = $result;
289 return $this->uploadAssetForRelease($repo, $release, $config);
290 });
291 }
292
293 }