ostatussub.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  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->text($nickname);
  175. $this->elementEnd('a');
  176. if (!is_null($fullname)) {
  177. $this->elementStart('div', 'fn entity_fn');
  178. $this->text($fullname);
  179. $this->elementEnd('div');
  180. }
  181. if (!is_null($location)) {
  182. $this->elementStart('div', 'label entity_location');
  183. $this->text($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->text($homepage);
  190. $this->elementEnd('a');
  191. }
  192. if (!is_null($note)) {
  193. $this->elementStart('div', 'note entity_note');
  194. $this->text($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. $validate = new Validate();
  217. $this->profile_uri = $this->trimmed('profile');
  218. try {
  219. if ($validate->email($this->profile_uri)) {
  220. $this->oprofile = Ostatus_profile::ensureWebfinger($this->profile_uri);
  221. } else if ($validate->uri($this->profile_uri)) {
  222. $this->oprofile = Ostatus_profile::ensureProfileURL($this->profile_uri);
  223. } else {
  224. // TRANS: Error message in OStatus plugin. Do not translate the domain names example.com
  225. // TRANS: and example.net, as these are official standard domain names for use in examples.
  226. $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.");
  227. common_debug('Invalid address format.', __FILE__);
  228. return false;
  229. }
  230. return true;
  231. } catch (FeedSubBadURLException $e) {
  232. // TRANS: Error message in OStatus plugin. Do not translate the domain names example.com
  233. // TRANS: and example.net, as these are official standard domain names for use in examples.
  234. $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.');
  235. common_debug('Invalid URL or could not reach server.', __FILE__);
  236. } catch (FeedSubBadResponseException $e) {
  237. // TRANS: Error text.
  238. $this->error = _m('Sorry, we could not reach that feed. Please try that OStatus address again later.');
  239. common_debug('Cannot read feed; server returned error.', __FILE__);
  240. } catch (FeedSubEmptyException $e) {
  241. // TRANS: Error text.
  242. $this->error = _m('Sorry, we could not reach that feed. Please try that OStatus address again later.');
  243. common_debug('Cannot read feed; server returned an empty page.', __FILE__);
  244. } catch (FeedSubBadHTMLException $e) {
  245. // TRANS: Error text.
  246. $this->error = _m('Sorry, we could not reach that feed. Please try that OStatus address again later.');
  247. common_debug('Bad HTML, could not find feed link.', __FILE__);
  248. } catch (FeedSubNoFeedException $e) {
  249. // TRANS: Error text.
  250. $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
  251. common_debug('Could not find a feed linked from this URL.', __FILE__);
  252. } catch (FeedSubUnrecognizedTypeException $e) {
  253. // TRANS: Error text.
  254. $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
  255. common_debug('Not a recognized feed type.', __FILE__);
  256. } catch (FeedSubNoHubException $e) {
  257. // TRANS: Error text.
  258. $this->error = _m("Sorry, that feed is not Pubsubhubub enabled.");
  259. common_debug('No hub found.', __FILE__);
  260. } catch (Exception $e) {
  261. // Any new ones we forgot about
  262. // TRANS: Error message in OStatus plugin. Do not translate the domain names example.com
  263. // TRANS: and example.net, as these are official standard domain names for use in examples.
  264. $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.");
  265. common_debug(sprintf('Bad feed URL: %s %s', get_class($e), $e->getMessage()), __FILE__);
  266. }
  267. return false;
  268. }
  269. function validateRemoteProfile()
  270. {
  271. // Send us to the respective subscription form for conf
  272. if ($this->oprofile->isGroup()) {
  273. $target = common_local_url('ostatusgroup', array(), array('profile' => $this->profile_uri));
  274. common_redirect($target, 303);
  275. } else if ($this->oprofile->isPeopletag()) {
  276. $target = common_local_url('ostatuspeopletag', array(), array('profile' => $this->profile_uri));
  277. common_redirect($target, 303);
  278. }
  279. }
  280. /**
  281. * Attempt to finalize subscription.
  282. * validateFeed must have been run first.
  283. *
  284. * Calls showForm on failure or success on success.
  285. */
  286. function saveFeed()
  287. {
  288. // And subscribe the current user to the local profile
  289. $local = $this->oprofile->localProfile();
  290. if ($this->scoped->isSubscribed($local)) {
  291. // TRANS: OStatus remote subscription dialog error.
  292. $this->showForm(_m('Already subscribed!'));
  293. } elseif (Subscription::start($this->scoped, $local)) {
  294. $this->success();
  295. } else {
  296. // TRANS: OStatus remote subscription dialog error.
  297. $this->showForm(_m('Remote subscription failed!'));
  298. }
  299. }
  300. /**
  301. * Handle posts to this form
  302. *
  303. * @return void
  304. */
  305. function handlePost()
  306. {
  307. // CSRF protection
  308. $token = $this->trimmed('token');
  309. if (!$token || $token != common_session_token()) {
  310. // TRANS: Client error displayed when the session token does not match or is not given.
  311. $this->showForm(_m('There was a problem with your session token. '.
  312. 'Try again, please.'));
  313. return;
  314. }
  315. if ($this->oprofile) {
  316. if ($this->arg('submit')) {
  317. $this->saveFeed();
  318. return;
  319. }
  320. }
  321. $this->showForm();
  322. }
  323. /**
  324. * Show the appropriate form based on our input state.
  325. */
  326. function showForm($err=null)
  327. {
  328. if ($err) {
  329. $this->error = $err;
  330. }
  331. if ($this->boolean('ajax')) {
  332. $this->startHTML('text/xml;charset=utf-8');
  333. $this->elementStart('head');
  334. // TRANS: Form title.
  335. $this->element('title', null, _m('Subscribe to user'));
  336. $this->elementEnd('head');
  337. $this->elementStart('body');
  338. $this->showContent();
  339. $this->elementEnd('body');
  340. $this->endHTML();
  341. } else {
  342. $this->showPage();
  343. }
  344. }
  345. /**
  346. * Title of the page
  347. *
  348. * @return string Title of the page
  349. */
  350. function title()
  351. {
  352. // TRANS: Page title for OStatus remote subscription form.
  353. return _m('Confirm');
  354. }
  355. /**
  356. * Instructions for use
  357. *
  358. * @return instructions for use
  359. */
  360. function getInstructions()
  361. {
  362. // TRANS: Instructions.
  363. return _m('You can subscribe to users from other supported sites. Paste their address or profile URI below:');
  364. }
  365. function showPageNotice()
  366. {
  367. if (!empty($this->error)) {
  368. $this->element('p', 'error', $this->error);
  369. }
  370. }
  371. /**
  372. * Content area of the page
  373. *
  374. * Shows a form for associating a remote OStatus account with this
  375. * StatusNet account.
  376. *
  377. * @return void
  378. */
  379. function showContent()
  380. {
  381. if ($this->oprofile) {
  382. $this->showPreviewForm();
  383. } else {
  384. $this->showInputForm();
  385. }
  386. }
  387. function showScripts()
  388. {
  389. parent::showScripts();
  390. $this->autofocus('feedurl');
  391. }
  392. function selfLink()
  393. {
  394. return common_local_url('ostatussub');
  395. }
  396. /**
  397. * Disable the send-notice form at the top of the page.
  398. * This is really just a hack for the broken CSS in the Cloudy theme,
  399. * I think; copying from other non-notice-navigation pages that do this
  400. * as well. There will be plenty of others also broken.
  401. *
  402. * @fixme fix the cloudy theme
  403. * @fixme do this in a more general way
  404. */
  405. function showNoticeForm() {
  406. // nop
  407. }
  408. }