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.

983 lines
28 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
8 years ago
10 years ago
10 years ago
10 years ago
10 years ago
8 years ago
3 years ago
11 months ago
  1. <?php
  2. use IndieWeb\DateFormatter;
  3. function require_login(&$app, $redirect=true) {
  4. $params = $app->request()->params();
  5. if(array_key_exists('token', $params)) {
  6. try {
  7. $data = JWT::decode($params['token'], Config::$jwtSecret, array('HS256'));
  8. $_SESSION['user_id'] = $data->user_id;
  9. $_SESSION['me'] = $data->me;
  10. } catch(DomainException $e) {
  11. if($redirect) {
  12. header('X-Error: DomainException');
  13. $app->redirect('/', 302);
  14. } else {
  15. return false;
  16. }
  17. } catch(UnexpectedValueException $e) {
  18. if($redirect) {
  19. header('X-Error: UnexpectedValueException');
  20. $app->redirect('/', 302);
  21. } else {
  22. return false;
  23. }
  24. }
  25. }
  26. if(!array_key_exists('user_id', $_SESSION)) {
  27. if($redirect)
  28. $app->redirect('/', 302);
  29. return false;
  30. } else {
  31. return ORM::for_table('users')->find_one($_SESSION['user_id']);
  32. }
  33. }
  34. function generate_login_token($opts=[]) {
  35. return JWT::encode(array_merge([
  36. 'user_id' => $_SESSION['user_id'],
  37. 'me' => $_SESSION['me'],
  38. 'created_at' => time()
  39. ], $opts), Config::$jwtSecret);
  40. }
  41. $app->get('/dashboard', function() use($app) {
  42. if($user=require_login($app)) {
  43. render('dashboard', array(
  44. 'title' => 'Dashboard',
  45. 'authorizing' => false,
  46. 'user' => $user,
  47. ));
  48. }
  49. });
  50. $app->get('/new', function() use($app) {
  51. if($user=require_login($app)) {
  52. $params = $app->request()->params();
  53. $entry = false;
  54. $in_reply_to = '';
  55. if(array_key_exists('reply', $params))
  56. $in_reply_to = $params['reply'];
  57. render('new-post', array(
  58. 'title' => 'New Post',
  59. 'in_reply_to' => $in_reply_to,
  60. 'micropub_endpoint' => $user->micropub_endpoint,
  61. 'media_endpoint' => $user->micropub_media_endpoint,
  62. 'micropub_scope' => $user->micropub_scope,
  63. 'micropub_access_token' => $user->micropub_access_token,
  64. 'response_date' => $user->last_micropub_response_date,
  65. 'syndication_targets' => json_decode($user->syndication_targets, true),
  66. 'supported_visibility' => json_decode($user->supported_visibility, true),
  67. 'location_enabled' => $user->location_enabled,
  68. 'user' => $user,
  69. 'authorizing' => false
  70. ));
  71. }
  72. });
  73. $app->get('/new/last-photo.json', function() use($app) {
  74. if($user=require_login($app)) {
  75. $item = null;
  76. if($user->micropub_media_endpoint) {
  77. // Request the last file uploaded from the media endpoint
  78. $response = micropub_get($user->micropub_media_endpoint, ['q'=>'source', 'limit'=>1], $user->micropub_access_token);
  79. if(isset($response['data']['items'])) {
  80. $items = $response['data']['items'];
  81. if(isset($items[0])) {
  82. // Only show the file if it was uploaded in the last 5 minutes or if no published date available
  83. $show = !isset($items[0]['published']) || (strtotime($items[0]['published']) >= (time()-900));
  84. if($show && isset($items[0]['url'])) {
  85. $item = $items[0];
  86. }
  87. }
  88. }
  89. }
  90. if($item && !empty($item['latitude'])) {
  91. $timezone = p3k\Timezone::timezone_for_location($item['latitude'], $item['longitude']);
  92. $tz = new DateTimeZone($timezone);
  93. $item['tz_offset'] = tz_seconds_to_offset($tz->getOffset(new DateTime($item['date_created'])));
  94. }
  95. $app->response()['Content-type'] = 'application/json';
  96. $app->response()->body(json_encode($item, JSON_PRETTY_PRINT+JSON_UNESCAPED_SLASHES));
  97. }
  98. });
  99. $app->get('/bookmark', function() use($app) {
  100. if($user=require_login($app)) {
  101. $params = $app->request()->params();
  102. $url = '';
  103. $name = '';
  104. $content = '';
  105. $tags = '';
  106. if(array_key_exists('url', $params))
  107. $url = $params['url'];
  108. if(array_key_exists('name', $params))
  109. $name = $params['name'];
  110. if(array_key_exists('content', $params))
  111. $content = $params['content'];
  112. render('new-bookmark', array(
  113. 'title' => 'New Bookmark',
  114. 'bookmark_url' => $url,
  115. 'bookmark_name' => $name,
  116. 'bookmark_content' => $content,
  117. 'bookmark_tags' => $tags,
  118. 'token' => generate_login_token(),
  119. 'syndication_targets' => json_decode($user->syndication_targets, true),
  120. 'user' => $user,
  121. 'authorizing' => false
  122. ));
  123. }
  124. });
  125. $app->get('/favorite', function() use($app) {
  126. if($user=require_login($app)) {
  127. $params = $app->request()->params();
  128. $like_of = '';
  129. if(array_key_exists('url', $params))
  130. $like_of = $params['url'];
  131. // Check if there was a login token in the query string and whether it has autosubmit=true
  132. $autosubmit = false;
  133. if(array_key_exists('token', $params)) {
  134. try {
  135. $data = JWT::decode($params['token'], Config::$jwtSecret, ['HS256']);
  136. if(isset($data->autosubmit) && $data->autosubmit) {
  137. // Only allow this token to be used for the user who created it
  138. if($data->user_id == $_SESSION['user_id']) {
  139. $autosubmit = true;
  140. }
  141. }
  142. } catch(Exception $e) {
  143. }
  144. }
  145. if(array_key_exists('edit', $params)) {
  146. $edit_data = get_micropub_source($user, $params['edit'], 'like-of');
  147. $url = $params['edit'];
  148. if(isset($edit_data['like-of'])) {
  149. $like_of = $edit_data['like-of'][0];
  150. }
  151. } else {
  152. $edit_data = false;
  153. $url = false;
  154. }
  155. render('new-favorite', array(
  156. 'title' => 'New Favorite',
  157. 'like_of' => $like_of,
  158. 'token' => generate_login_token(['autosubmit'=>true]),
  159. 'authorizing' => false,
  160. 'autosubmit' => $autosubmit,
  161. 'url' => $url
  162. ));
  163. }
  164. });
  165. $app->get('/event', function() use($app) {
  166. if($user=require_login($app)) {
  167. $params = $app->request()->params();
  168. $channels = $user->channels ? json_decode($user->channels, true) : [];
  169. render('event', array(
  170. 'title' => 'Event',
  171. 'channels' => $channels,
  172. 'authorizing' => false
  173. ));
  174. }
  175. });
  176. $app->get('/itinerary', function() use($app) {
  177. if($user=require_login($app)) {
  178. $params = $app->request()->params();
  179. render('new-itinerary', array(
  180. 'title' => 'Itinerary',
  181. 'authorizing' => false
  182. ));
  183. }
  184. });
  185. $app->get('/photo', function() use($app) {
  186. if($user=require_login($app)) {
  187. $params = $app->request()->params();
  188. render('photo', array(
  189. 'title' => 'New Photo',
  190. 'note_content' => '',
  191. 'authorizing' => false
  192. ));
  193. }
  194. });
  195. $app->get('/review', function() use($app) {
  196. if($user=require_login($app)) {
  197. $params = $app->request()->params();
  198. render('review', array(
  199. 'title' => 'Review',
  200. 'authorizing' => false
  201. ));
  202. }
  203. });
  204. $app->get('/repost', function() use($app) {
  205. if($user=require_login($app)) {
  206. $params = $app->request()->params();
  207. $repost_of = '';
  208. if(array_key_exists('url', $params))
  209. $repost_of = $params['url'];
  210. if(array_key_exists('edit', $params)) {
  211. $edit_data = get_micropub_source($user, $params['edit'], 'repost-of');
  212. $url = $params['edit'];
  213. if(isset($edit_data['repost-of'])) {
  214. $repost = $edit_data['repost-of'][0];
  215. if(is_string($edit_data['repost-of'][0])) {
  216. $repost_of = $repost;
  217. } elseif(is_array($repost)) {
  218. if(array_key_exists('type', $repost) && in_array('h-cite', $repost)) {
  219. if(array_key_exists('url', $repost['properties'])) {
  220. $repost_of = $repost['properties']['url'][0];
  221. }
  222. } else {
  223. // Error
  224. }
  225. } else {
  226. // Error: don't know what type of post this is
  227. }
  228. }
  229. } else {
  230. $edit_data = false;
  231. $url = false;
  232. }
  233. render('new-repost', array(
  234. 'title' => 'New Repost',
  235. 'repost_of' => $repost_of,
  236. 'token' => generate_login_token(),
  237. 'authorizing' => false,
  238. 'url' => $url,
  239. ));
  240. }
  241. });
  242. $app->post('/prefs', function() use($app) {
  243. if($user=require_login($app)) {
  244. $params = $app->request()->params();
  245. $user->location_enabled = $params['enabled'];
  246. $user->save();
  247. }
  248. $app->response()['Content-type'] = 'application/json';
  249. $app->response()->body(json_encode(array(
  250. 'result' => 'ok'
  251. )));
  252. });
  253. $app->post('/prefs/timezone', function() use($app) {
  254. // Called when the interface finds the user's location.
  255. // Look up the timezone for this location and store it as their default.
  256. $timezone = false;
  257. if($user=require_login($app)) {
  258. $params = $app->request()->params();
  259. $timezone = p3k\Timezone::timezone_for_location($params['latitude'], $params['longitude']);
  260. if($timezone) {
  261. $user->default_timezone = $timezone;
  262. $user->save();
  263. }
  264. }
  265. $app->response()['Content-type'] = 'application/json';
  266. $app->response()->body(json_encode(array(
  267. 'result' => 'ok',
  268. 'timezone' => $timezone,
  269. )));
  270. });
  271. $app->get('/add-to-home', function() use($app) {
  272. $params = $app->request()->params();
  273. header("Cache-Control: no-cache, must-revalidate");
  274. if(array_key_exists('token', $params) && !session('add-to-home-started')) {
  275. unset($_SESSION['add-to-home-started']);
  276. // Verify the token and sign the user in
  277. try {
  278. $data = JWT::decode($params['token'], Config::$jwtSecret, array('HS256'));
  279. $_SESSION['user_id'] = $data->user_id;
  280. $_SESSION['me'] = $data->me;
  281. $app->redirect('/new', 302);
  282. } catch(DomainException $e) {
  283. header('X-Error: DomainException');
  284. $app->redirect('/', 302);
  285. } catch(UnexpectedValueException $e) {
  286. header('X-Error: UnexpectedValueException');
  287. $app->redirect('/', 302);
  288. }
  289. } else {
  290. if($user=require_login($app)) {
  291. if(array_key_exists('start', $params)) {
  292. $_SESSION['add-to-home-started'] = true;
  293. $token = JWT::encode(array(
  294. 'user_id' => $_SESSION['user_id'],
  295. 'me' => $_SESSION['me'],
  296. 'created_at' => time()
  297. ), Config::$jwtSecret);
  298. $app->redirect('/add-to-home?token='.$token, 302);
  299. } else {
  300. unset($_SESSION['add-to-home-started']);
  301. render('add-to-home', array('title' => 'Quill'));
  302. }
  303. }
  304. }
  305. });
  306. $app->get('/email', function() use($app) {
  307. if($user=require_login($app)) {
  308. if(!$user->email_username) {
  309. $host = parse_url($user->url, PHP_URL_HOST);
  310. $user->email_username = $host . '.' . rand(100000,999999);
  311. $user->save();
  312. }
  313. render('email', array(
  314. 'title' => 'Post-by-Email',
  315. 'micropub_endpoint' => $user->micropub_endpoint,
  316. 'user' => $user
  317. ));
  318. }
  319. });
  320. $app->get('/settings', function() use($app) {
  321. if($user=require_login($app)) {
  322. render('settings', [
  323. 'title' => 'Settings',
  324. 'user' => $user,
  325. 'syndication_targets' => json_decode($user->syndication_targets, true),
  326. 'channels' => json_decode($user->channels, true),
  327. 'authorizing' => false
  328. ]);
  329. }
  330. });
  331. $app->post('/settings/save', function() use($app) {
  332. if($user=require_login($app)) {
  333. $params = $app->request()->params();
  334. if(array_key_exists('html_content', $params))
  335. $user->micropub_optin_html_content = $params['html_content'] ? 1 : 0;
  336. if(array_key_exists('slug_field', $params) && $params['slug_field'])
  337. $user->micropub_slug_field = $params['slug_field'];
  338. if(array_key_exists('syndicate_field', $params) && $params['syndicate_field']) {
  339. if(in_array($params['syndicate_field'], ['syndicate-to','mp-syndicate-to']))
  340. $user->micropub_syndicate_field = $params['syndicate_field'];
  341. }
  342. if(array_key_exists('weight_unit', $params) && $params['weight_unit'])
  343. $user->weight_unit = $params['weight_unit'];
  344. $user->save();
  345. $app->response()['Content-type'] = 'application/json';
  346. $app->response()->body(json_encode(array(
  347. 'result' => 'ok'
  348. )));
  349. }
  350. });
  351. $app->post('/settings/html-content', function() use($app) {
  352. if($user=require_login($app)) {
  353. $params = $app->request()->params();
  354. $user->micropub_optin_html_content = $params['html'] ? 1 : 0;
  355. $user->save();
  356. $app->response()['Content-type'] = 'application/json';
  357. $app->response()->body(json_encode(array(
  358. 'html' => $user->micropub_optin_html_content
  359. )));
  360. }
  361. });
  362. $app->get('/settings/html-content', function() use($app) {
  363. if($user=require_login($app)) {
  364. $app->response()['Content-type'] = 'application/json';
  365. $app->response()->body(json_encode(array(
  366. 'html' => $user->micropub_optin_html_content
  367. )));
  368. }
  369. });
  370. function create_favorite(&$user, $url) {
  371. $tweet_id = false;
  372. $micropub_request = array(
  373. 'like-of' => $url
  374. );
  375. $r = micropub_post_for_user($user, $micropub_request);
  376. return $r;
  377. }
  378. function edit_favorite(&$user, $post_url, $like_of) {
  379. $micropub_request = [
  380. 'action' => 'update',
  381. 'url' => $post_url,
  382. 'replace' => [
  383. 'like-of' => [$like_of]
  384. ]
  385. ];
  386. $r = micropub_post_for_user($user, $micropub_request, null, true);
  387. return $r;
  388. }
  389. function create_repost(&$user, $url) {
  390. $micropub_request = array(
  391. 'repost-of' => $url
  392. );
  393. $r = micropub_post_for_user($user, $micropub_request);
  394. return $r;
  395. }
  396. function edit_repost(&$user, $post_url, $repost_of) {
  397. $micropub_request = [
  398. 'action' => 'update',
  399. 'url' => $post_url,
  400. 'replace' => [
  401. 'repost-of' => [$repost_of]
  402. ]
  403. ];
  404. $r = micropub_post_for_user($user, $micropub_request, null, true);
  405. return $r;
  406. }
  407. $app->post('/favorite', function() use($app) {
  408. if($user=require_login($app)) {
  409. $params = $app->request()->params();
  410. $error = false;
  411. if(isset($params['edit']) && $params['edit']) {
  412. $r = edit_favorite($user, $params['edit'], $params['like_of']);
  413. if(isset($r['location']) && $r['location'])
  414. $location = $r['location'];
  415. elseif(in_array($r['code'], [200,201,204]))
  416. $location = $params['edit'];
  417. elseif(in_array($r['code'], [401,403])) {
  418. $location = false;
  419. $error = 'Your Micropub endpoint denied the request. Check that Quill is authorized to update posts.';
  420. } else {
  421. $location = false;
  422. $error = 'Your Micropub endpoint did not return a location header or a recognized response code';
  423. }
  424. } else {
  425. $r = create_favorite($user, $params['like_of']);
  426. $location = $r['location'];
  427. }
  428. $app->response()['Content-type'] = 'application/json';
  429. $app->response()->body(json_encode(array(
  430. 'location' => $location,
  431. 'error' => $r['error'],
  432. 'error_details' => $error,
  433. )));
  434. }
  435. });
  436. $app->post('/repost', function() use($app) {
  437. if($user=require_login($app)) {
  438. $params = $app->request()->params();
  439. $error = false;
  440. if(isset($params['edit']) && $params['edit']) {
  441. $r = edit_repost($user, $params['edit'], $params['repost_of']);
  442. if(isset($r['location']) && $r['location'])
  443. $location = $r['location'];
  444. elseif(in_array($r['code'], [200,201,204]))
  445. $location = $params['edit'];
  446. elseif(in_array($r['code'], [401,403])) {
  447. $location = false;
  448. $error = 'Your Micropub endpoint denied the request. Check that Quill is authorized to update posts.';
  449. } else {
  450. $location = false;
  451. $error = 'Your Micropub endpoint did not return a location header or a recognized response code';
  452. }
  453. } else {
  454. $r = create_repost($user, $params['repost_of']);
  455. $location = $r['location'];
  456. }
  457. $app->response()['Content-type'] = 'application/json';
  458. $app->response()->body(json_encode(array(
  459. 'location' => $location,
  460. 'error' => $r['error'],
  461. 'error_details' => $error,
  462. )));
  463. }
  464. });
  465. $app->get('/code', function() use($app) {
  466. if($user=require_login($app)) {
  467. $params = $app->request()->params();
  468. $edit_data = ['content'=>'','name'=>''];
  469. if(array_key_exists('edit', $params)) {
  470. $source = get_micropub_source($user, $params['edit'], ['content','name']);
  471. if(isset($source['content']) && is_array($source['content']) && isset($source['content'][0]))
  472. $edit_data['content'] = $source['content'][0];
  473. if(isset($source['name']) && is_array($source['name']) && isset($source['name'][0]))
  474. $edit_data['name'] = $source['name'][0];
  475. $url = $params['edit'];
  476. } else {
  477. $url = false;
  478. }
  479. $languages = [
  480. 'php' => ['php'],
  481. 'ruby' => ['rb'],
  482. 'python' => ['py'],
  483. 'perl' => ['pl'],
  484. 'javascript' => ['js'],
  485. 'html' => ['html','htm'],
  486. 'css' => ['css'],
  487. 'bash' => ['sh'],
  488. 'nginx' => ['conf'],
  489. 'apache' => [],
  490. 'text' => ['txt'],
  491. ];
  492. ksort($languages);
  493. $language_map = [];
  494. foreach($languages as $lang=>$exts) {
  495. foreach($exts as $ext)
  496. $language_map[$ext] = $lang;
  497. }
  498. render('new-code', array(
  499. 'title' => 'New Code Snippet',
  500. 'url' => $url,
  501. 'edit_data' => $edit_data,
  502. 'token' => generate_login_token(),
  503. 'languages' => $languages,
  504. 'language_map' => $language_map,
  505. 'my_hostname' => parse_url($user->url, PHP_URL_HOST),
  506. 'authorizing' => false,
  507. ));
  508. }
  509. });
  510. $app->post('/code', function() use($app) {
  511. if($user=require_login($app)) {
  512. $params = $app->request()->params();
  513. $error = false;
  514. if(isset($params['edit']) && $params['edit']) {
  515. $micropub_request = [
  516. 'action' => 'update',
  517. 'url' => $params['edit'],
  518. 'replace' => [
  519. 'content' => [$params['content']]
  520. ]
  521. ];
  522. if(isset($params['name']) && $params['name']) {
  523. $micropub_request['replace']['name'] = [$params['name']];
  524. }
  525. $r = micropub_post_for_user($user, $micropub_request, null, true);
  526. if(isset($r['location']) && $r['location'])
  527. $location = $r['location'];
  528. elseif(in_array($r['code'], [200,201,204]))
  529. $location = $params['edit'];
  530. elseif(in_array($r['code'], [401,403])) {
  531. $location = false;
  532. $error = 'Your Micropub endpoint denied the request. Check that Quill is authorized to update posts.';
  533. } else {
  534. $location = false;
  535. $error = 'Your Micropub endpoint did not return a location header or a recognized response code';
  536. }
  537. } else {
  538. $micropub_request = array(
  539. 'p3k-content-type' => 'code/' . $params['language'],
  540. 'content' => $params['content'],
  541. );
  542. if(isset($params['name']) && $params['name'])
  543. $micropub_request['name'] = $params['name'];
  544. $r = micropub_post_for_user($user, $micropub_request);
  545. $location = $r['location'];
  546. }
  547. $app->response()['Content-type'] = 'application/json';
  548. $app->response()->body(json_encode(array(
  549. 'location' => $location,
  550. 'error' => $r['error'],
  551. 'error_details' => $error,
  552. )));
  553. }
  554. });
  555. $app->get('/reply/preview', function() use($app) {
  556. if($user=require_login($app)) {
  557. $params = $app->request()->params();
  558. if(!isset($params['url']) || !$params['url']) {
  559. return '';
  560. }
  561. $reply_url = trim($params['url']);
  562. $entry = false;
  563. $xray_opts = [];
  564. // Pass to X-Ray to see if it can expand the entry
  565. $xray = new p3k\XRay();
  566. $xray->http = new p3k\HTTP('Quill ('.Config::$base_url.')');
  567. $data = $xray->parse($reply_url, $xray_opts);
  568. if($data && isset($data['data'])) {
  569. if($data['data']['type'] == 'entry') {
  570. $entry = $data['data'];
  571. } elseif($data['data']['type'] == 'event') {
  572. $entry = $data['data'];
  573. $content = '';
  574. if(isset($entry['start']) && isset($entry['end'])) {
  575. $formatted = DateFormatter::format($entry['start'], $entry['end'], false);
  576. if($formatted)
  577. $content .= $formatted;
  578. else {
  579. $start = new DateTime($entry['start']);
  580. $end = new DateTime($entry['end']);
  581. if($start && $end)
  582. $content .= 'from '.$start->format('Y-m-d g:ia').' to '.$end->format('Y-m-d g:ia');
  583. }
  584. } elseif(isset($entry['start'])) {
  585. $formatted = DateFormatter::format($entry['start'], false, false);
  586. if($formatted)
  587. $content .= $formatted;
  588. else {
  589. $start = new DateTime($entry['start']);
  590. if($start)
  591. $content .= $start->format('Y-m-d g:ia');
  592. }
  593. }
  594. $entry['content']['text'] = $content;
  595. }
  596. // Create a nickname based on the author URL
  597. if($entry && array_key_exists('author', $entry)) {
  598. if($entry['author']['url']) {
  599. if(!isset($entry['author']['nickname']) || !$entry['author']['nickname'])
  600. $entry['author']['nickname'] = display_url($entry['author']['url']);
  601. }
  602. }
  603. }
  604. $mentions = [];
  605. if($entry) {
  606. if(array_key_exists('author', $entry) && isset($entry['author']['nickname'])) {
  607. // Find all @-names in the post, as well as the author name
  608. $mentions[] = strtolower($entry['author']['nickname']);
  609. }
  610. if(isset($entry['content']) && $entry['content'] && isset($entry['content']['text'])) {
  611. if(preg_match_all('/(^|(?<=[\s\/]))@([a-z0-9_]+([a-z0-9_\.]*)?)/i', $entry['content']['text'], $matches)) {
  612. foreach($matches[0] as $nick) {
  613. if(trim($nick,'@') != display_url($user->url))
  614. $mentions[] = strtolower(trim($nick,'@'));
  615. }
  616. }
  617. }
  618. $mentions = array_values(array_unique($mentions));
  619. }
  620. $syndications = [];
  621. if($entry && isset($entry['syndication'])) {
  622. foreach($entry['syndication'] as $s) {
  623. $host = parse_url($s, PHP_URL_HOST);
  624. switch($host) {
  625. case 'github.com':
  626. case 'www.github.com':
  627. $icon = 'github.ico'; break;
  628. default:
  629. $icon = 'default.png'; break;
  630. }
  631. $syndications[] = [
  632. 'url' => $s,
  633. 'icon' => $icon
  634. ];
  635. }
  636. }
  637. $app->response()['Content-type'] = 'application/json';
  638. $app->response()->body(json_encode([
  639. 'canonical_reply_url' => $reply_url,
  640. 'entry' => $entry,
  641. 'mentions' => $mentions,
  642. 'syndications' => $syndications,
  643. ]));
  644. }
  645. });
  646. $app->get('/edit', function() use($app) {
  647. if($user=require_login($app)) {
  648. $params = $app->request()->params();
  649. if(!isset($params['url']) || !$params['url']) {
  650. $app->response()->body('no URL specified');
  651. }
  652. // Query the micropub endpoint for the source properties
  653. $source = micropub_get($user->micropub_endpoint, [
  654. 'q' => 'source',
  655. 'url' => $params['url']
  656. ], $user->micropub_access_token);
  657. $data = $source['data'];
  658. if(array_key_exists('error', $data)) {
  659. render('edit/error', [
  660. 'title' => 'Error',
  661. 'summary' => 'Your Micropub endpoint returned an error:',
  662. 'error' => $data['error'],
  663. 'error_description' => $data['error_description']
  664. ]);
  665. return;
  666. }
  667. if(!array_key_exists('properties', $data) || !array_key_exists('type', $data)) {
  668. render('edit/error', [
  669. 'title' => 'Error',
  670. 'summary' => '',
  671. 'error' => 'Invalid Response',
  672. 'error_description' => 'Your endpoint did not return "properties" and "type" in the response.'
  673. ]);
  674. return;
  675. }
  676. // Start checking for content types
  677. $type = $data['type'][0];
  678. $error = false;
  679. $url = false;
  680. if($type == 'h-review') {
  681. $url = '/review';
  682. } elseif($type == 'h-event') {
  683. $url = '/event';
  684. } elseif($type != 'h-entry') {
  685. $error = 'This type of post is not supported by any of Quill\'s editing interfaces. Type: '.$type;
  686. } else {
  687. if(array_key_exists('bookmark-of', $data['properties'])) {
  688. $url = '/bookmark';
  689. } elseif(array_key_exists('like-of', $data['properties'])) {
  690. $url = '/favorite';
  691. } elseif(array_key_exists('repost-of', $data['properties'])) {
  692. $url = '/repost';
  693. }
  694. }
  695. if($error) {
  696. render('edit/error', [
  697. 'title' => 'Error',
  698. 'summary' => '',
  699. 'error' => 'There was a problem!',
  700. 'error_description' => $error
  701. ]);
  702. return;
  703. }
  704. // Until all interfaces are complete, show an error here for unsupported ones
  705. if(!in_array($url, ['/favorite','/repost','/code'])) {
  706. render('edit/error', [
  707. 'title' => 'Not Yet Supported',
  708. 'summary' => '',
  709. 'error' => 'Not Yet Supported',
  710. 'error_description' => 'Editing is not yet supported for this type of post.'
  711. ]);
  712. return;
  713. }
  714. $app->redirect($url . '?edit=' . $params['url'], 302);
  715. }
  716. });
  717. $app->get('/airport-info', function() use($app){
  718. if($user=require_login($app)) {
  719. $params = $app->request()->params();
  720. if(!isset($params['code'])) return;
  721. $ch = curl_init('https://atlas.p3k.io/api/timezone?'.http_build_query([
  722. 'airport' => $params['code'],
  723. 'date' => $params['date'],
  724. ]));
  725. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  726. $data = json_decode(curl_exec($ch), true);
  727. if(!$data)
  728. $response = ['error' => 'unknown'];
  729. else {
  730. $response = $data;
  731. }
  732. $app->response()['Content-type'] = 'application/json';
  733. $app->response()->body(json_encode($response));
  734. }
  735. });
  736. $app->get('/map-img', function() use($app) {
  737. $params = $app->request()->params();
  738. $app->response()['Content-type'] = 'image/png';
  739. $params = [
  740. 'marker[]' => 'lat:'.$params['lat'].';lng:'.$params['lng'].';icon:small-blue-cutout',
  741. 'basemap' => 'custom',
  742. 'width' => $params['w'],
  743. 'height' => $params['h'],
  744. 'zoom' => $params['z'],
  745. 'attribution' => 'mapbox',
  746. 'tileurl' => Config::$mapTileURL,
  747. 'token' => Config::$atlasToken,
  748. ];
  749. $ch = curl_init('https://atlas.p3k.io/map/img');
  750. curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
  751. curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
  752. curl_exec($ch);
  753. });
  754. function create_weight(&$user, $weight_num, $weight_unit, $published) {
  755. $micropub_request = array(
  756. 'type' => ['h-entry'],
  757. 'properties' => [
  758. 'weight' => [[
  759. 'type' => ['h-measure'],
  760. 'properties' => [
  761. 'num' => [$weight_num],
  762. 'unit' => [$weight_unit]
  763. ]
  764. ]]
  765. ]
  766. );
  767. try {
  768. $date = new DateTime($published);
  769. $micropub_request['properties']['published'] = [$date->format('c')];
  770. } catch(Exception $e) {
  771. }
  772. $r = micropub_post_for_user($user, $micropub_request, null, true);
  773. return $r;
  774. }
  775. $app->get('/weight', function() use($app){
  776. if($user=require_login($app)) {
  777. render('new-weight', array(
  778. 'title' => 'New Weight',
  779. 'unit' => $user->weight_unit
  780. ));
  781. }
  782. });
  783. $app->post('/weight', function() use($app) {
  784. if($user=require_login($app)) {
  785. $params = $app->request()->params();
  786. $r = create_weight($user, $params['weight_num'], $user->weight_unit, $params['published']);
  787. $location = $r['location'];
  788. $app->response()['Content-type'] = 'application/json';
  789. $app->response()->body(json_encode(array(
  790. 'location' => $location,
  791. 'error' => $r['error']
  792. )));
  793. }
  794. });
  795. function create_exercise(&$user, $activity, $minutes, $heartrate, $published) {
  796. $micropub_request = array(
  797. 'type' => ['h-entry'],
  798. 'properties' => [
  799. 'workout' => [[
  800. 'type' => ['h-workout'],
  801. 'properties' => [
  802. 'activity' => [$activity],
  803. 'duration' => [[
  804. 'type' => ['h-measure'],
  805. 'properties' => [
  806. 'num' => [($minutes*60)],
  807. 'unit' => ['second']
  808. ],
  809. ]],
  810. 'heartrate' => [[
  811. 'type' => 'h-measure',
  812. 'properties' => [
  813. 'num' => [$heartrate],
  814. 'unit' => ['bpm'],
  815. ]
  816. ]]
  817. ]
  818. ]]
  819. ]
  820. );
  821. try {
  822. $date = new DateTime($published);
  823. $micropub_request['properties']['published'] = [$date->format('c')];
  824. } catch(Exception $e) {
  825. }
  826. $r = micropub_post_for_user($user, $micropub_request, null, true);
  827. return $r;
  828. }
  829. $app->get('/exercise', function() use($app){
  830. if($user=require_login($app)) {
  831. render('new-exercise', array(
  832. 'title' => 'New Exercise',
  833. ));
  834. }
  835. });
  836. $app->post('/exercise', function() use($app) {
  837. if($user=require_login($app)) {
  838. $params = $app->request()->params();
  839. $r = create_exercise($user, $params['activity'], $params['minutes'], $params['heartrate'], $params['published']);
  840. $location = $r['location'];
  841. $app->response()['Content-type'] = 'application/json';
  842. $app->response()->body(json_encode(array(
  843. 'location' => $location,
  844. 'error' => $r['error']
  845. )));
  846. }
  847. });