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.

376 lines
11 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. <?php
  2. function require_login(&$app) {
  3. $params = $app->request()->params();
  4. if(array_key_exists('token', $params)) {
  5. try {
  6. $data = JWT::decode($params['token'], Config::$jwtSecret);
  7. $_SESSION['user_id'] = $data->user_id;
  8. $_SESSION['me'] = $data->me;
  9. } catch(DomainException $e) {
  10. header('X-Error: DomainException');
  11. $app->redirect('/', 301);
  12. } catch(UnexpectedValueException $e) {
  13. header('X-Error: UnexpectedValueException');
  14. $app->redirect('/', 301);
  15. }
  16. }
  17. if(!array_key_exists('user_id', $_SESSION)) {
  18. $app->redirect('/');
  19. return false;
  20. } else {
  21. return ORM::for_table('users')->find_one($_SESSION['user_id']);
  22. }
  23. }
  24. function get_login(&$app) {
  25. if(array_key_exists('user_id', $_SESSION)) {
  26. return ORM::for_table('users')->find_one($_SESSION['user_id']);
  27. } else {
  28. return false;
  29. }
  30. }
  31. function generate_login_token() {
  32. return JWT::encode(array(
  33. 'user_id' => $_SESSION['user_id'],
  34. 'me' => $_SESSION['me'],
  35. 'created_at' => time()
  36. ), Config::$jwtSecret);
  37. }
  38. $app->get('/new', function() use($app) {
  39. if($user=require_login($app)) {
  40. // Get the last post and set the timezone offset to match
  41. $date_str = date('Y-m-d');
  42. $time_str = date('H:i:s');
  43. $tz_offset = '+0000';
  44. $last = ORM::for_table('entries')->where('user_id', $user->id)
  45. ->order_by_desc('published')->find_one();
  46. if(false && $last) {
  47. $seconds = $last->tz_offset;
  48. $tz_offset = tz_seconds_to_offset($seconds);
  49. // Create a date object in the local timezone given the offset
  50. $date = new DateTime();
  51. if($seconds > 0)
  52. $date->add(new DateInterval('PT'.$seconds.'S'));
  53. elseif($seconds < 0)
  54. $date->sub(new DateInterval('PT'.abs($seconds).'S'));
  55. $date_str = $date->format('Y-m-d');
  56. $time_str = $date->format('H:i:s');
  57. }
  58. // Initially populate the page with the list of options without considering location.
  59. // This way if browser location is disabled or not available, or JS is disabled, there
  60. // will still be a list of options presented on the page by the time it loads.
  61. // Javascript will replace the options after location is available.
  62. $html = render('new-post', array(
  63. 'title' => 'New Post',
  64. 'micropub_endpoint' => $user->micropub_endpoint,
  65. 'token_scope' => $user->token_scope,
  66. 'access_token' => $user->access_token,
  67. 'response_date' => $user->last_micropub_response_date,
  68. 'location_enabled' => $user->location_enabled,
  69. 'default_options' => get_entry_options($user->id),
  70. 'tz_offset' => $tz_offset,
  71. 'date_str' => $date_str,
  72. 'time_str' => $time_str,
  73. 'enable_appcache' => true
  74. ));
  75. $app->response()->body($html);
  76. }
  77. });
  78. $app->post('/prefs', function() use($app) {
  79. if($user=require_login($app)) {
  80. $params = $app->request()->params();
  81. $user->location_enabled = $params['enabled'];
  82. $user->save();
  83. }
  84. $app->response()->body(json_encode(array(
  85. 'result' => 'ok'
  86. )));
  87. });
  88. $app->get('/creating-a-token-endpoint', function() use($app) {
  89. $app->redirect('http://indiewebcamp.com/token-endpoint', 301);
  90. });
  91. $app->get('/creating-a-micropub-endpoint', function() use($app) {
  92. $html = render('creating-a-micropub-endpoint', array('title' => 'Creating a Micropub Endpoint'));
  93. $app->response()->body($html);
  94. });
  95. $app->get('/docs', function() use($app) {
  96. $html = render('docs', array('title' => 'Documentation'));
  97. $app->response()->body($html);
  98. });
  99. $app->get('/add-to-home', function() use($app) {
  100. $params = $app->request()->params();
  101. header("Cache-Control: no-cache, must-revalidate");
  102. if(array_key_exists('token', $params) && !session('add-to-home-started')) {
  103. unset($_SESSION['add-to-home-started']);
  104. // Verify the token and sign the user in
  105. try {
  106. $data = JWT::decode($params['token'], Config::$jwtSecret);
  107. $_SESSION['user_id'] = $data->user_id;
  108. $_SESSION['me'] = $data->me;
  109. $app->redirect('/new', 301);
  110. } catch(DomainException $e) {
  111. header('X-Error: DomainException');
  112. $app->redirect('/', 301);
  113. } catch(UnexpectedValueException $e) {
  114. header('X-Error: UnexpectedValueException');
  115. $app->redirect('/', 301);
  116. }
  117. } else {
  118. if($user=require_login($app)) {
  119. if(array_key_exists('start', $params)) {
  120. $_SESSION['add-to-home-started'] = true;
  121. $token = JWT::encode(array(
  122. 'user_id' => $_SESSION['user_id'],
  123. 'me' => $_SESSION['me'],
  124. 'created_at' => time()
  125. ), Config::$jwtSecret);
  126. $app->redirect('/add-to-home?token='.$token, 301);
  127. } else {
  128. unset($_SESSION['add-to-home-started']);
  129. $html = render('add-to-home', array('title' => 'Teacup'));
  130. $app->response()->body($html);
  131. }
  132. }
  133. }
  134. });
  135. $app->post('/post', function() use($app) {
  136. if($user=require_login($app)) {
  137. $params = $app->request()->params();
  138. // Remove any blank params
  139. $params = array_filter($params, function($v){
  140. return $v !== '';
  141. });
  142. // Store the post in the database
  143. $entry = ORM::for_table('entries')->create();
  144. $entry->user_id = $user->id;
  145. $location = false;
  146. if(k($params, 'location') && $location=parse_geo_uri($params['location'])) {
  147. $entry->latitude = $location['latitude'];
  148. $entry->longitude = $location['longitude'];
  149. }
  150. if(k($params,'note_date')) {
  151. // The post request is always going to have a date now
  152. $date_string = $params['note_date'] . 'T' . $params['note_time'] . $params['note_tzoffset'];
  153. $entry->published = date('Y-m-d H:i:s', strtotime($date_string));
  154. $entry->tz_offset = tz_offset_to_seconds($params['note_tzoffset']);
  155. $published = $date_string;
  156. } else {
  157. // Pebble doesn't send the date/time/timezone
  158. $entry->published = date('Y-m-d H:i:s');
  159. $published = date('c'); // for the micropub post
  160. if($location && ($timezone=get_timezone($location['latitude'], $location['longitude']))) {
  161. $entry->timezone = $timezone->getName();
  162. $entry->tz_offset = $timezone->getOffset(new DateTime());
  163. $now = new DateTime();
  164. $now->setTimeZone(new DateTimeZone($entry->timezone));
  165. $published = $now->format('c');
  166. }
  167. }
  168. if(k($params, 'drank')) {
  169. $entry->content = trim($params['drank']);
  170. $type = 'drink';
  171. $verb = 'drank';
  172. } elseif(k($params, 'drink')) {
  173. $entry->content = trim($params['drink']);
  174. $type = 'drink';
  175. $verb = 'drank';
  176. } elseif(k($params, 'eat')) {
  177. $entry->content = trim($params['eat']);
  178. $type = 'eat';
  179. $verb = 'ate';
  180. } elseif(k($params, 'custom_drink')) {
  181. $entry->content = trim($params['custom_drink']);
  182. $type = 'drink';
  183. $verb = 'drank';
  184. } elseif(k($params, 'custom_eat')) {
  185. $entry->content = trim($params['custom_eat']);
  186. $type = 'eat';
  187. $verb = 'ate';
  188. }
  189. $entry->type = $type;
  190. $entry->save();
  191. // Send to the micropub endpoint if one is defined, and store the result
  192. $url = false;
  193. if($user->micropub_endpoint) {
  194. $text_content = 'Just ' . $verb . ': ' . $entry->content;
  195. $mp_request = array(
  196. 'h' => 'entry',
  197. 'published' => $published,
  198. 'p3k-food' => $entry->content,
  199. 'p3k-type' => $type,
  200. 'location' => k($params, 'location'),
  201. 'summary' => $text_content
  202. );
  203. $r = micropub_post($user->micropub_endpoint, $mp_request, $user->access_token);
  204. $request = $r['request'];
  205. $response = $r['response'];
  206. $entry->micropub_response = $response;
  207. // Check the response and look for a "Location" header containing the URL
  208. if($response && preg_match('/Location: (.+)/', $response, $match)) {
  209. $url = $match[1];
  210. $user->micropub_success = 1;
  211. $entry->micropub_success = 1;
  212. $entry->canonical_url = $url;
  213. } else {
  214. $entry->micropub_success = 0;
  215. }
  216. $entry->save();
  217. }
  218. if($url) {
  219. $app->redirect($url);
  220. } else {
  221. // TODO: Redirect to an error page or show an error on the teacup post page
  222. $url = Config::$base_url . $user->url . '/' . $entry->id;
  223. $app->redirect($url);
  224. }
  225. }
  226. });
  227. $app->get('/options.json', function() use($app) {
  228. if($user=require_login($app)) {
  229. $params = $app->request()->params();
  230. $options = get_entry_options($user->id, k($params,'latitude'), k($params,'longitude'));
  231. $html = partial('partials/entry-buttons', ['options'=>$options]);
  232. $app->response()['Content-type'] = 'application/json';
  233. $app->response()->body(json_encode([
  234. 'buttons'=>$html
  235. ]));
  236. }
  237. });
  238. $app->get('/map.png', function() use($app) {
  239. $url = static_map_service($_SERVER['QUERY_STRING']);
  240. $ch = curl_init($url);
  241. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  242. $img = curl_exec($ch);
  243. header('Expires: ' . gmdate('D, d M Y H:i:s', strtotime('+30 days')) . ' GMT');
  244. header('Pragma: cache');
  245. header('Cache-Control: private');
  246. $app->response()['Content-type'] = 'image/png';
  247. $app->response()->body($img);
  248. });
  249. $app->get('/teacup.appcache', function() use($app) {
  250. $content = partial('appcache');
  251. $app->response()['Content-type'] = 'text/cache-manifest';
  252. $app->response()->body($content);
  253. });
  254. $app->get('/:domain', function($domain) use($app) {
  255. $params = $app->request()->params();
  256. $user = ORM::for_table('users')->where('url', $domain)->find_one();
  257. if(!$user) {
  258. $app->notFound();
  259. return;
  260. }
  261. $per_page = 10;
  262. $entries = ORM::for_table('entries')->where('user_id', $user->id);
  263. if(array_key_exists('before', $params)) {
  264. $entries->where_lte('id', $params['before']);
  265. }
  266. $entries = $entries->limit($per_page)->order_by_desc('published')->find_many();
  267. if(count($entries) > 1) {
  268. $older = ORM::for_table('entries')->where('user_id', $user->id)
  269. ->where_lt('id', $entries[count($entries)-1]->id)->order_by_desc('published')->find_one();
  270. } else {
  271. $older = null;
  272. }
  273. if(count($entries) > 1) {
  274. $newer = ORM::for_table('entries')->where('user_id', $user->id)
  275. ->where_gte('id', $entries[0]->id)->order_by_asc('published')->offset($per_page)->find_one();
  276. } else {
  277. $newer = null;
  278. }
  279. if(!$newer) {
  280. // no new entry was found at the specific offset, so find the newest post to link to instead
  281. $newer = ORM::for_table('entries')->where('user_id', $user->id)
  282. ->order_by_desc('published')->limit(1)->find_one();
  283. if($newer && $newer->id == $entries[0]->id)
  284. $newer = false;
  285. }
  286. $html = render('entries', array(
  287. 'title' => 'Teacup',
  288. 'entries' => $entries,
  289. 'user' => $user,
  290. 'older' => ($older ? $older->id : false),
  291. 'newer' => ($newer ? $newer->id : false)
  292. ));
  293. $app->response()->body($html);
  294. })->conditions(array(
  295. 'domain' => '[a-zA-Z0-9\.-]+\.[a-z]+'
  296. ));
  297. $app->get('/:domain/:entry', function($domain, $entry_id) use($app) {
  298. $user = ORM::for_table('users')->where('url', $domain)->find_one();
  299. if(!$user) {
  300. $app->notFound();
  301. return;
  302. }
  303. $entry = ORM::for_table('entries')->where('user_id', $user->id)->where('id', $entry_id)->find_one();
  304. if(!$entry) {
  305. $app->notFound();
  306. return;
  307. }
  308. $html = render('entry', array(
  309. 'title' => 'Teacup',
  310. 'entry' => $entry,
  311. 'user' => $user
  312. ));
  313. $app->response()->body($html);
  314. })->conditions(array(
  315. 'domain' => '[a-zA-Z0-9\.-]+\.[a-z]+',
  316. 'entry' => '\d+'
  317. ));