ostatussub.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. <?php
  2. /*
  3. * StatusNet - the distributed open-source microblogging tool
  4. * Copyright (C) 2009-2010, StatusNet, Inc.
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. /**
  20. * @package OStatusPlugin
  21. * @maintainer Brion Vibber <brion@status.net>
  22. */
  23. if (!defined('GNUSOCIAL') && !defined('STATUSNET')) { exit(1); }
  24. /**
  25. * Key UI methods:
  26. *
  27. * showInputForm() - form asking for a remote profile account or URL
  28. * We end up back here on errors
  29. *
  30. * showPreviewForm() - surrounding form for preview-and-confirm
  31. * preview() - display profile for a remote user
  32. *
  33. * success() - redirects to subscriptions page on subscribe
  34. */
  35. class OStatusSubAction extends Action
  36. {
  37. protected $profile_uri; // provided acct: or URI of remote entity
  38. protected $oprofile; // Ostatus_profile of remote entity, if valid
  39. protected function prepare(array $args=array())
  40. {
  41. parent::prepare($args);
  42. if (!common_logged_in()) {
  43. // XXX: selfURL() didn't work. :<
  44. common_set_returnto($_SERVER['REQUEST_URI']);
  45. if (Event::handle('RedirectToLogin', array($this, null))) {
  46. common_redirect(common_local_url('login'), 303);
  47. }
  48. return false;
  49. }
  50. if ($this->pullRemoteProfile()) {
  51. $this->validateRemoteProfile();
  52. }
  53. return true;
  54. }
  55. /**
  56. * Handle the submission.
  57. */
  58. protected function handle()
  59. {
  60. parent::handle();
  61. if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  62. $this->handlePost();
  63. } else {
  64. $this->showForm();
  65. }
  66. }
  67. /**
  68. * Show the initial form, when we haven't yet been given a valid
  69. * remote profile.
  70. */
  71. function showInputForm()
  72. {
  73. $this->elementStart('form', array('method' => 'post',
  74. 'id' => 'form_ostatus_sub',
  75. 'class' => 'form_settings',
  76. 'action' => $this->selfLink()));
  77. $this->hidden('token', common_session_token());
  78. $this->elementStart('fieldset', array('id' => 'settings_feeds'));
  79. $this->elementStart('ul', 'form_data');
  80. $this->elementStart('li');
  81. $this->input('profile',
  82. // TRANS: Field label for a field that takes an OStatus user address.
  83. _m('Subscribe to'),
  84. $this->profile_uri,
  85. // TRANS: Tooltip for field label "Subscribe to".
  86. _m('OStatus user\'s address, like nickname@example.com or http://example.net/nickname.'));
  87. $this->elementEnd('li');
  88. $this->elementEnd('ul');
  89. // TRANS: Button text.
  90. $this->submit('validate', _m('BUTTON','Continue'));
  91. $this->elementEnd('fieldset');
  92. $this->elementEnd('form');
  93. }
  94. /**
  95. * Show the preview-and-confirm form. We've got a valid remote
  96. * profile and are ready to poke it!
  97. *
  98. * This controls the wrapper form; actual profile display will
  99. * be in previewUser() or previewGroup() depending on the type.
  100. */
  101. function showPreviewForm()
  102. {
  103. $ok = $this->preview();
  104. if (!$ok) {
  105. // @todo FIXME maybe provide a cancel button or link back?
  106. return;
  107. }
  108. $this->elementStart('div', 'entity_actions');
  109. $this->elementStart('ul');
  110. $this->elementStart('li', 'entity_subscribe');
  111. $this->elementStart('form', array('method' => 'post',
  112. 'id' => 'form_ostatus_sub',
  113. 'class' => 'form_remote_authorize',
  114. 'action' =>
  115. $this->selfLink()));
  116. $this->elementStart('fieldset');
  117. $this->hidden('token', common_session_token());
  118. $this->hidden('profile', $this->profile_uri);
  119. if ($this->oprofile->isGroup()) {
  120. // TRANS: Button text.
  121. $this->submit('submit', _m('Join'), 'submit', null,
  122. // TRANS: Tooltip for button "Join".
  123. _m('BUTTON','Join this group'));
  124. } else {
  125. // TRANS: Button text.
  126. $this->submit('submit', _m('BUTTON','Confirm'), 'submit', null,
  127. // TRANS: Tooltip for button "Confirm".
  128. _m('Subscribe to this user'));
  129. }
  130. $this->elementEnd('fieldset');
  131. $this->elementEnd('form');
  132. $this->elementEnd('li');
  133. $this->elementEnd('ul');
  134. $this->elementEnd('div');
  135. }
  136. /**
  137. * Show a preview for a remote user's profile
  138. * @return boolean true if we're ok to try subscribing
  139. */
  140. function preview()
  141. {
  142. // Throws NoProfileException on localProfile when remote user's Profile not found
  143. $profile = $this->oprofile->localProfile();
  144. if ($this->scoped->isSubscribed($profile)) {
  145. $this->element('div', array('class' => 'error'),
  146. // TRANS: Extra paragraph in remote profile view when already subscribed.
  147. _m('You are already subscribed to this user.'));
  148. $ok = false;
  149. } else {
  150. $ok = true;
  151. }
  152. $avatarUrl = $profile->avatarUrl(AVATAR_PROFILE_SIZE);
  153. $this->showEntity($profile,
  154. $profile->profileurl,
  155. $avatarUrl,
  156. $profile->bio);
  157. return $ok;
  158. }
  159. function showEntity($entity, $profile, $avatar, $note)
  160. {
  161. $nickname = $entity->nickname;
  162. $fullname = $entity->fullname;
  163. $homepage = $entity->homepage;
  164. $location = $entity->location;
  165. $this->elementStart('div', 'entity_profile vcard');
  166. $this->element('img', array('src' => $avatar,
  167. 'class' => 'photo avatar entity_depiction',
  168. 'width' => AVATAR_PROFILE_SIZE,
  169. 'height' => AVATAR_PROFILE_SIZE,
  170. 'alt' => $nickname));
  171. $hasFN = ($fullname !== '') ? 'nickname' : 'fn nickname entity_nickname';
  172. $this->elementStart('a', array('href' => $profile,
  173. 'class' => 'url '.$hasFN));
  174. $this->raw($nickname);
  175. $this->elementEnd('a');
  176. if (!is_null($fullname)) {
  177. $this->elementStart('div', 'fn entity_fn');
  178. $this->raw($fullname);
  179. $this->elementEnd('div');
  180. }
  181. if (!is_null($location)) {
  182. $this->elementStart('div', 'label entity_location');
  183. $this->raw($location);
  184. $this->elementEnd('div');
  185. }
  186. if (!is_null($homepage)) {
  187. $this->elementStart('a', array('href' => $homepage,
  188. 'class' => 'url entity_url'));
  189. $this->raw($homepage);
  190. $this->elementEnd('a');
  191. }
  192. if (!is_null($note)) {
  193. $this->elementStart('div', 'note entity_note');
  194. $this->raw($note);
  195. $this->elementEnd('div');
  196. }
  197. $this->elementEnd('div');
  198. }
  199. /**
  200. * Redirect on successful remote user subscription
  201. */
  202. function success()
  203. {
  204. $url = common_local_url('subscriptions', array('nickname' => $this->scoped->nickname));
  205. common_redirect($url, 303);
  206. }
  207. /**
  208. * Pull data for a remote profile and check if it's valid.
  209. * Fills out error UI string in $this->error
  210. * Fills out $this->oprofile on success.
  211. *
  212. * @return boolean
  213. */
  214. function pullRemoteProfile()
  215. {
  216. $this->profile_uri = $this->trimmed('profile');
  217. try {
  218. if (Validate::email($this->profile_uri)) {
  219. $this->oprofile = Ostatus_profile::ensureWebfinger($this->profile_uri);
  220. } else if (Validate::uri($this->profile_uri)) {
  221. $this->oprofile = Ostatus_profile::ensureProfileURL($this->profile_uri);
  222. } else {
  223. // TRANS: Error message in OStatus plugin. Do not translate the domain names example.com
  224. // TRANS: and example.net, as these are official standard domain names for use in examples.
  225. $this->error = _m("Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname.");
  226. common_debug('Invalid address format.', __FILE__);
  227. return false;
  228. }
  229. return true;
  230. } catch (FeedSubBadURLException $e) {
  231. // TRANS: Error message in OStatus plugin. Do not translate the domain names example.com
  232. // TRANS: and example.net, as these are official standard domain names for use in examples.
  233. $this->error = _m('Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname.');
  234. common_debug('Invalid URL or could not reach server.', __FILE__);
  235. } catch (FeedSubBadResponseException $e) {
  236. // TRANS: Error text.
  237. $this->error = _m('Sorry, we could not reach that feed. Please try that OStatus address again later.');
  238. common_debug('Cannot read feed; server returned error.', __FILE__);
  239. } catch (FeedSubEmptyException $e) {
  240. // TRANS: Error text.
  241. $this->error = _m('Sorry, we could not reach that feed. Please try that OStatus address again later.');
  242. common_debug('Cannot read feed; server returned an empty page.', __FILE__);
  243. } catch (FeedSubBadHTMLException $e) {
  244. // TRANS: Error text.
  245. $this->error = _m('Sorry, we could not reach that feed. Please try that OStatus address again later.');
  246. common_debug('Bad HTML, could not find feed link.', __FILE__);
  247. } catch (FeedSubNoFeedException $e) {
  248. // TRANS: Error text.
  249. $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
  250. common_debug('Could not find a feed linked from this URL.', __FILE__);
  251. } catch (FeedSubUnrecognizedTypeException $e) {
  252. // TRANS: Error text.
  253. $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
  254. common_debug('Not a recognized feed type.', __FILE__);
  255. } catch (Exception $e) {
  256. // Any new ones we forgot about
  257. // TRANS: Error message in OStatus plugin. Do not translate the domain names example.com
  258. // TRANS: and example.net, as these are official standard domain names for use in examples.
  259. $this->error = _m("Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname.");
  260. common_debug(sprintf('Bad feed URL: %s %s', get_class($e), $e->getMessage()), __FILE__);
  261. }
  262. return false;
  263. }
  264. function validateRemoteProfile()
  265. {
  266. // Send us to the respective subscription form for conf
  267. if ($this->oprofile->isGroup()) {
  268. $target = common_local_url('ostatusgroup', array(), array('profile' => $this->profile_uri));
  269. common_redirect($target, 303);
  270. } else if ($this->oprofile->isPeopletag()) {
  271. $target = common_local_url('ostatuspeopletag', array(), array('profile' => $this->profile_uri));
  272. common_redirect($target, 303);
  273. }
  274. }
  275. /**
  276. * Attempt to finalize subscription.
  277. * validateFeed must have been run first.
  278. *
  279. * Calls showForm on failure or success on success.
  280. */
  281. function saveFeed()
  282. {
  283. // And subscribe the current user to the local profile
  284. $local = $this->oprofile->localProfile();
  285. if ($this->scoped->isSubscribed($local)) {
  286. // TRANS: OStatus remote subscription dialog error.
  287. $this->showForm(_m('Already subscribed!'));
  288. } elseif (Subscription::start($this->scoped, $local)) {
  289. $this->success();
  290. } else {
  291. // TRANS: OStatus remote subscription dialog error.
  292. $this->showForm(_m('Remote subscription failed!'));
  293. }
  294. }
  295. /**
  296. * Handle posts to this form
  297. *
  298. * @return void
  299. */
  300. function handlePost()
  301. {
  302. // CSRF protection
  303. $token = $this->trimmed('token');
  304. if (!$token || $token != common_session_token()) {
  305. // TRANS: Client error displayed when the session token does not match or is not given.
  306. $this->showForm(_m('There was a problem with your session token. '.
  307. 'Try again, please.'));
  308. return;
  309. }
  310. if ($this->oprofile) {
  311. if ($this->arg('submit')) {
  312. $this->saveFeed();
  313. return;
  314. }
  315. }
  316. $this->showForm();
  317. }
  318. /**
  319. * Show the appropriate form based on our input state.
  320. */
  321. function showForm($err=null)
  322. {
  323. if ($err) {
  324. $this->error = $err;
  325. }
  326. if ($this->boolean('ajax')) {
  327. $this->startHTML('text/xml;charset=utf-8');
  328. $this->elementStart('head');
  329. // TRANS: Form title.
  330. $this->element('title', null, _m('Subscribe to user'));
  331. $this->elementEnd('head');
  332. $this->elementStart('body');
  333. $this->showContent();
  334. $this->elementEnd('body');
  335. $this->endHTML();
  336. } else {
  337. $this->showPage();
  338. }
  339. }
  340. /**
  341. * Title of the page
  342. *
  343. * @return string Title of the page
  344. */
  345. function title()
  346. {
  347. // TRANS: Page title for OStatus remote subscription form.
  348. return _m('Confirm');
  349. }
  350. /**
  351. * Instructions for use
  352. *
  353. * @return instructions for use
  354. */
  355. function getInstructions()
  356. {
  357. // TRANS: Instructions.
  358. return _m('You can subscribe to users from other supported sites. Paste their address or profile URI below:');
  359. }
  360. function showPageNotice()
  361. {
  362. if (!empty($this->error)) {
  363. $this->element('p', 'error', $this->error);
  364. }
  365. }
  366. /**
  367. * Content area of the page
  368. *
  369. * Shows a form for associating a remote OStatus account with this
  370. * StatusNet account.
  371. *
  372. * @return void
  373. */
  374. function showContent()
  375. {
  376. if ($this->oprofile) {
  377. $this->showPreviewForm();
  378. } else {
  379. $this->showInputForm();
  380. }
  381. }
  382. function showScripts()
  383. {
  384. parent::showScripts();
  385. $this->autofocus('feedurl');
  386. }
  387. function selfLink()
  388. {
  389. return common_local_url('ostatussub');
  390. }
  391. /**
  392. * Disable the send-notice form at the top of the page.
  393. * This is really just a hack for the broken CSS in the Cloudy theme,
  394. * I think; copying from other non-notice-navigation pages that do this
  395. * as well. There will be plenty of others also broken.
  396. *
  397. * @fixme fix the cloudy theme
  398. * @fixme do this in a more general way
  399. */
  400. function showNoticeForm() {
  401. // nop
  402. }
  403. }