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.

618 lines
18 KiB

8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
  1. <?php
  2. use Firebase\JWT\JWT;
  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);
  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('/', 301);
  14. } else {
  15. return false;
  16. }
  17. } catch(UnexpectedValueException $e) {
  18. if($redirect) {
  19. header('X-Error: UnexpectedValueException');
  20. $app->redirect('/', 301);
  21. } else {
  22. return false;
  23. }
  24. }
  25. }
  26. if(!array_key_exists('user_id', $_SESSION)) {
  27. if($redirect)
  28. $app->redirect('/');
  29. return false;
  30. } else {
  31. return ORM::for_table('users')->find_one($_SESSION['user_id']);
  32. }
  33. }
  34. function generate_login_token() {
  35. return JWT::encode(array(
  36. 'user_id' => $_SESSION['user_id'],
  37. 'me' => $_SESSION['me'],
  38. 'created_at' => time()
  39. ), Config::$jwtSecret);
  40. }
  41. $app->get('/new', function() use($app) {
  42. if($user=require_login($app)) {
  43. $params = $app->request()->params();
  44. $entry = false;
  45. $photo_url = false;
  46. $in_reply_to = '';
  47. if(array_key_exists('reply', $params))
  48. $in_reply_to = $params['reply'];
  49. $test_response = '';
  50. if($user->last_micropub_response) {
  51. try {
  52. if(@json_decode($user->last_micropub_response)) {
  53. $d = json_decode($user->last_micropub_response);
  54. $test_response = $d->response;
  55. }
  56. } catch(Exception $e) {
  57. }
  58. }
  59. $html = render('new-post', array(
  60. 'title' => 'New Post',
  61. 'in_reply_to' => $in_reply_to,
  62. 'micropub_endpoint' => $user->micropub_endpoint,
  63. 'micropub_scope' => $user->micropub_scope,
  64. 'micropub_access_token' => $user->micropub_access_token,
  65. 'response_date' => $user->last_micropub_response_date,
  66. 'syndication_targets' => json_decode($user->syndication_targets, true),
  67. 'test_response' => $test_response,
  68. 'location_enabled' => $user->location_enabled,
  69. 'authorizing' => false
  70. ));
  71. $app->response()->body($html);
  72. }
  73. });
  74. $app->get('/bookmark', function() use($app) {
  75. if($user=require_login($app)) {
  76. $params = $app->request()->params();
  77. $url = '';
  78. $name = '';
  79. $content = '';
  80. $tags = '';
  81. if(array_key_exists('url', $params))
  82. $url = $params['url'];
  83. if(array_key_exists('name', $params))
  84. $name = $params['name'];
  85. if(array_key_exists('content', $params))
  86. $content = $params['content'];
  87. $html = render('new-bookmark', array(
  88. 'title' => 'New Bookmark',
  89. 'bookmark_url' => $url,
  90. 'bookmark_name' => $name,
  91. 'bookmark_content' => $content,
  92. 'bookmark_tags' => $tags,
  93. 'token' => generate_login_token(),
  94. 'syndication_targets' => json_decode($user->syndication_targets, true),
  95. 'authorizing' => false
  96. ));
  97. $app->response()->body($html);
  98. }
  99. });
  100. $app->get('/favorite', function() use($app) {
  101. if($user=require_login($app)) {
  102. $params = $app->request()->params();
  103. $url = '';
  104. if(array_key_exists('url', $params))
  105. $url = $params['url'];
  106. $html = render('new-favorite', array(
  107. 'title' => 'New Favorite',
  108. 'url' => $url,
  109. 'token' => generate_login_token(),
  110. 'authorizing' => false
  111. ));
  112. $app->response()->body($html);
  113. }
  114. });
  115. $app->get('/photo', function() use($app) {
  116. if($user=require_login($app)) {
  117. $params = $app->request()->params();
  118. $html = render('photo', array(
  119. 'title' => 'New Photo',
  120. 'note_content' => '',
  121. 'authorizing' => false
  122. ));
  123. $app->response()->body($html);
  124. }
  125. });
  126. $app->get('/repost', function() use($app) {
  127. if($user=require_login($app)) {
  128. $params = $app->request()->params();
  129. $url = '';
  130. if(array_key_exists('url', $params))
  131. $url = $params['url'];
  132. $html = render('new-repost', array(
  133. 'title' => 'New Repost',
  134. 'url' => $url,
  135. 'token' => generate_login_token(),
  136. 'authorizing' => false
  137. ));
  138. $app->response()->body($html);
  139. }
  140. });
  141. $app->post('/prefs', function() use($app) {
  142. if($user=require_login($app)) {
  143. $params = $app->request()->params();
  144. $user->location_enabled = $params['enabled'];
  145. $user->save();
  146. }
  147. $app->response()->body(json_encode(array(
  148. 'result' => 'ok'
  149. )));
  150. });
  151. $app->get('/creating-a-token-endpoint', function() use($app) {
  152. $app->redirect('http://indiewebcamp.com/token-endpoint', 301);
  153. });
  154. $app->get('/creating-a-micropub-endpoint', function() use($app) {
  155. $html = render('creating-a-micropub-endpoint', array('title' => 'Creating a Micropub Endpoint', 'authorizing' => false));
  156. $app->response()->body($html);
  157. });
  158. $app->get('/docs', function() use($app) {
  159. $html = render('docs', array('title' => 'Documentation', 'authorizing' => false));
  160. $app->response()->body($html);
  161. });
  162. $app->get('/privacy', function() use($app) {
  163. $html = render('privacy', array('title' => 'Quill Privacy Policy', 'authorizing' => false));
  164. $app->response()->body($html);
  165. });
  166. $app->get('/add-to-home', function() use($app) {
  167. $params = $app->request()->params();
  168. if(array_key_exists('token', $params) && !session('add-to-home-started')) {
  169. // Verify the token and sign the user in
  170. try {
  171. $data = JWT::decode($params['token'], Config::$jwtSecret);
  172. $_SESSION['user_id'] = $data->user_id;
  173. $_SESSION['me'] = $data->me;
  174. $app->redirect('/new', 301);
  175. } catch(DomainException $e) {
  176. header('X-Error: DomainException');
  177. $app->redirect('/', 301);
  178. } catch(UnexpectedValueException $e) {
  179. header('X-Error: UnexpectedValueException');
  180. $app->redirect('/', 301);
  181. }
  182. } else {
  183. if($user=require_login($app)) {
  184. if(array_key_exists('start', $params)) {
  185. $_SESSION['add-to-home-started'] = true;
  186. $token = JWT::encode(array(
  187. 'user_id' => $_SESSION['user_id'],
  188. 'me' => $_SESSION['me'],
  189. 'created_at' => time()
  190. ), Config::$jwtSecret);
  191. $app->redirect('/add-to-home?token='.$token, 301);
  192. } else {
  193. unset($_SESSION['add-to-home-started']);
  194. $html = render('add-to-home', array('title' => 'Quill'));
  195. $app->response()->body($html);
  196. }
  197. }
  198. }
  199. });
  200. $app->get('/email', function() use($app) {
  201. if($user=require_login($app)) {
  202. $test_response = '';
  203. if($user->last_micropub_response) {
  204. try {
  205. if(@json_decode($user->last_micropub_response)) {
  206. $d = json_decode($user->last_micropub_response);
  207. $test_response = $d->response;
  208. }
  209. } catch(Exception $e) {
  210. }
  211. }
  212. if(!$user->email_username) {
  213. $host = parse_url($user->url, PHP_URL_HOST);
  214. $user->email_username = $host . '.' . rand(100000,999999);
  215. $user->save();
  216. }
  217. $html = render('email', array(
  218. 'title' => 'Post-by-Email',
  219. 'micropub_endpoint' => $user->micropub_endpoint,
  220. 'test_response' => $test_response,
  221. 'user' => $user
  222. ));
  223. $app->response()->body($html);
  224. }
  225. });
  226. $app->get('/settings', function() use($app) {
  227. if($user=require_login($app)) {
  228. $html = render('settings', array('title' => 'Settings', 'include_facebook' => true, 'authorizing' => false));
  229. $app->response()->body($html);
  230. }
  231. });
  232. $app->get('/favorite-popup', function() use($app) {
  233. if($user=require_login($app)) {
  234. $params = $app->request()->params();
  235. $html = $app->render('favorite-popup.php', array(
  236. 'url' => $params['url'],
  237. 'token' => $params['token']
  238. ));
  239. $app->response()->body($html);
  240. }
  241. });
  242. function create_favorite(&$user, $url) {
  243. $micropub_request = array(
  244. 'like-of' => $url
  245. );
  246. $r = micropub_post_for_user($user, $micropub_request);
  247. $facebook_id = false;
  248. $instagram_id = false;
  249. $tweet_id = false;
  250. /*
  251. // Facebook likes are posted via Javascript, so pass the FB ID to the javascript code
  252. if(preg_match('/https?:\/\/(?:www\.)?facebook\.com\/(?:[^\/]+)\/posts\/(\d+)/', $url, $match)) {
  253. $facebook_id = $match[1];
  254. }
  255. if(preg_match('/https?:\/\/(?:www\.)?facebook\.com\/photo\.php\?fbid=(\d+)/', $url, $match)) {
  256. $facebook_id = $match[1];
  257. }
  258. */
  259. if(preg_match('/https?:\/\/(?:www\.)?instagram\.com\/p\/([^\/]+)/', $url, $match)) {
  260. $instagram_id = $match[1];
  261. if($user->instagram_access_token) {
  262. $instagram = instagram_client();
  263. $instagram->setAccessToken($user->instagram_access_token);
  264. $ch = curl_init('https://api.instagram.com/v1/media/shortcode/' . $instagram_id . '?access_token=' . $user->instagram_access_token);
  265. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  266. $result = json_decode(curl_exec($ch));
  267. $result = $instagram->likeMedia($result->data->id);
  268. } else {
  269. // TODO: indicate that the instagram post couldn't be liked because no access token was available
  270. }
  271. }
  272. if($user->twitter_access_token && preg_match('/https?:\/\/(?:www\.)?twitter\.com\/[^\/]+\/status(?:es)?\/(\d+)/', $url, $match)) {
  273. $tweet_id = $match[1];
  274. $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret,
  275. $user->twitter_access_token, $user->twitter_token_secret);
  276. $result = $twitter->post('favorites/create', array(
  277. 'id' => $tweet_id
  278. ));
  279. }
  280. return $r;
  281. }
  282. function create_photo(&$user, $params, $file) {
  283. $error = validate_photo($file);
  284. if(!$error) {
  285. $file_path = $file['tmp_name'];
  286. $micropub_request = array('content' => $params['note_content']);
  287. $r = micropub_post_for_user($user, $micropub_request, $file_path);
  288. } else {
  289. $r = array('error' => $error);
  290. }
  291. return $r;
  292. }
  293. function create_repost(&$user, $url) {
  294. $micropub_request = array(
  295. 'repost-of' => $url
  296. );
  297. $r = micropub_post_for_user($user, $micropub_request);
  298. $tweet_id = false;
  299. if($user->twitter_access_token && preg_match('/https?:\/\/(?:www\.)?twitter\.com\/[^\/]+\/status(?:es)?\/(\d+)/', $url, $match)) {
  300. $tweet_id = $match[1];
  301. $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret,
  302. $user->twitter_access_token, $user->twitter_token_secret);
  303. $result = $twitter->post('statuses/retweet/'.$tweet_id);
  304. }
  305. return $r;
  306. }
  307. $app->get('/favorite.js', function() use($app) {
  308. $app->response()->header("Content-type", "text/javascript");
  309. if($user=require_login($app, false)) {
  310. $params = $app->request()->params();
  311. if(array_key_exists('url', $params)) {
  312. $r = create_favorite($user, $params['url']);
  313. $app->response()->body($app->render('favorite-js.php', array(
  314. 'url' => $params['url'],
  315. 'like_url' => $r['location'],
  316. 'error' => $r['error'],
  317. // 'facebook_id' => $facebook_id
  318. )));
  319. } else {
  320. $app->response()->body('alert("no url");');
  321. }
  322. } else {
  323. $app->response()->body('alert("invalid token");');
  324. }
  325. });
  326. $app->post('/favorite', function() use($app) {
  327. if($user=require_login($app)) {
  328. $params = $app->request()->params();
  329. $r = create_favorite($user, $params['url']);
  330. $app->response()->body(json_encode(array(
  331. 'location' => $r['location'],
  332. 'error' => $r['error']
  333. )));
  334. }
  335. });
  336. $app->post('/photo', function() use($app) {
  337. if($user=require_login($app)) {
  338. // var_dump($app->request()->post());
  339. //
  340. // Since $app->request()->post() with multipart is always
  341. // empty (bug in Slim?) We're using the raw $_POST here
  342. // until this gets fixed.
  343. // PHP empties everything in $_POST if the file upload size exceeds
  344. // that is why we have to test if the variables exist first.
  345. $note_content = isset($_POST['note_content']) ? $_POST['note_content'] : null;
  346. $params = array('note_content' => $note_content);
  347. $file = isset($_FILES['note_photo']) ? $_FILES['note_photo'] : null;
  348. $r = create_photo($user, $params, $file);
  349. // Populate the error if there was no location header.
  350. if(empty($r['location']) && empty($r['error'])) {
  351. $r['error'] = "No 'Location' header in response.";
  352. }
  353. $html = render('photo', array(
  354. 'title' => 'Photo posted',
  355. 'note_content' => $params['note_content'],
  356. 'location' => (isset($r['location']) ? $r['location'] : null),
  357. 'error' => (isset($r['error']) ? $r['error'] : null),
  358. 'response' => (isset($r['response']) ? htmlspecialchars($r['response']) : null),
  359. 'authorizing' => false
  360. ));
  361. $app->response()->body($html);
  362. }
  363. });
  364. $app->post('/repost', function() use($app) {
  365. if($user=require_login($app)) {
  366. $params = $app->request()->params();
  367. $r = create_repost($user, $params['url']);
  368. $app->response()->body(json_encode(array(
  369. 'location' => $r['location'],
  370. 'error' => $r['error']
  371. )));
  372. }
  373. });
  374. $app->get('/micropub/syndications', function() use($app) {
  375. if($user=require_login($app)) {
  376. $data = get_syndication_targets($user);
  377. $app->response()->body(json_encode(array(
  378. 'targets' => $data['targets'],
  379. 'response' => $data['response']
  380. )));
  381. }
  382. });
  383. $app->post('/micropub/post', function() use($app) {
  384. if($user=require_login($app)) {
  385. $params = $app->request()->params();
  386. // Remove any blank params
  387. $params = array_filter($params, function($v){
  388. return $v !== '';
  389. });
  390. $r = micropub_post_for_user($user, $params);
  391. $app->response()->body(json_encode(array(
  392. 'request' => htmlspecialchars($r['request']),
  393. 'response' => htmlspecialchars($r['response']),
  394. 'location' => $r['location'],
  395. 'error' => $r['error'],
  396. 'curlinfo' => $r['curlinfo']
  397. )));
  398. }
  399. });
  400. /*
  401. $app->post('/auth/facebook', function() use($app) {
  402. if($user=require_login($app, false)) {
  403. $params = $app->request()->params();
  404. // User just auth'd with facebook, store the access token
  405. $user->facebook_access_token = $params['fb_token'];
  406. $user->save();
  407. $app->response()->body(json_encode(array(
  408. 'result' => 'ok'
  409. )));
  410. } else {
  411. $app->response()->body(json_encode(array(
  412. 'result' => 'error'
  413. )));
  414. }
  415. });
  416. */
  417. $app->post('/auth/twitter', function() use($app) {
  418. if($user=require_login($app, false)) {
  419. $params = $app->request()->params();
  420. // User just auth'd with twitter, store the access token
  421. $user->twitter_access_token = $params['twitter_token'];
  422. $user->twitter_token_secret = $params['twitter_secret'];
  423. $user->save();
  424. $app->response()->body(json_encode(array(
  425. 'result' => 'ok'
  426. )));
  427. } else {
  428. $app->response()->body(json_encode(array(
  429. 'result' => 'error'
  430. )));
  431. }
  432. });
  433. function getTwitterLoginURL(&$twitter) {
  434. $request_token = $twitter->getRequestToken(Config::$base_url . 'auth/twitter/callback');
  435. $_SESSION['twitter_auth'] = $request_token;
  436. return $twitter->getAuthorizeURL($request_token['oauth_token']);
  437. }
  438. $app->get('/auth/twitter', function() use($app) {
  439. $params = $app->request()->params();
  440. if($user=require_login($app, false)) {
  441. // If there is an existing Twitter token, check if it is valid
  442. // Otherwise, generate a Twitter login link
  443. $twitter_login_url = false;
  444. $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret,
  445. $user->twitter_access_token, $user->twitter_token_secret);
  446. if(array_key_exists('login', $params)) {
  447. $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret);
  448. $twitter_login_url = getTwitterLoginURL($twitter);
  449. } else {
  450. if($user->twitter_access_token) {
  451. if ($twitter->get('account/verify_credentials')) {
  452. $app->response()->body(json_encode(array(
  453. 'result' => 'ok'
  454. )));
  455. return;
  456. } else {
  457. // If the existing twitter token is not valid, generate a login link
  458. $twitter_login_url = getTwitterLoginURL($twitter);
  459. }
  460. } else {
  461. $twitter_login_url = getTwitterLoginURL($twitter);
  462. }
  463. }
  464. $app->response()->body(json_encode(array(
  465. 'url' => $twitter_login_url
  466. )));
  467. } else {
  468. $app->response()->body(json_encode(array(
  469. 'result' => 'error'
  470. )));
  471. }
  472. });
  473. $app->get('/auth/twitter/callback', function() use($app) {
  474. if($user=require_login($app)) {
  475. $params = $app->request()->params();
  476. $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret,
  477. $_SESSION['twitter_auth']['oauth_token'], $_SESSION['twitter_auth']['oauth_token_secret']);
  478. $credentials = $twitter->getAccessToken($params['oauth_verifier']);
  479. $user->twitter_access_token = $credentials['oauth_token'];
  480. $user->twitter_token_secret = $credentials['oauth_token_secret'];
  481. $user->twitter_username = $credentials['screen_name'];
  482. $user->save();
  483. $app->redirect('/settings');
  484. }
  485. });
  486. $app->get('/auth/instagram', function() use($app) {
  487. if($user=require_login($app, false)) {
  488. $instagram = instagram_client();
  489. // If there is an existing Instagram auth token, check if it's valid
  490. if($user->instagram_access_token) {
  491. $instagram->setAccessToken($user->instagram_access_token);
  492. $igUser = $instagram->getUser();
  493. if($igUser && $igUser->meta->code == 200) {
  494. $app->response()->body(json_encode(array(
  495. 'result' => 'ok',
  496. 'username' => $igUser->data->username,
  497. 'url' => $instagram->getLoginUrl(array('basic','likes'))
  498. )));
  499. return;
  500. }
  501. }
  502. $app->response()->body(json_encode(array(
  503. 'result' => 'error',
  504. 'url' => $instagram->getLoginUrl(array('basic','likes'))
  505. )));
  506. }
  507. });
  508. $app->get('/auth/instagram/callback', function() use($app) {
  509. if($user=require_login($app)) {
  510. $params = $app->request()->params();
  511. $instagram = instagram_client();
  512. $data = $instagram->getOAuthToken($params['code']);
  513. $user->instagram_access_token = $data->access_token;
  514. $user->save();
  515. $app->redirect('/settings');
  516. }
  517. });