]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/OStatus/actions/ostatussub.php
Merge branch '0.9.x' into 1.0.x
[quix0rs-gnu-social.git] / plugins / OStatus / actions / ostatussub.php
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 /**
21  * @package OStatusPlugin
22  * @maintainer Brion Vibber <brion@status.net>
23  */
24
25 if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
26
27 /**
28  * Key UI methods:
29  *
30  *  showInputForm() - form asking for a remote profile account or URL
31  *                    We end up back here on errors
32  *
33  *  showPreviewForm() - surrounding form for preview-and-confirm
34  *    preview() - display profile for a remote user
35  *
36  *  success() - redirects to subscriptions page on subscribe
37  */
38 class OStatusSubAction extends Action
39 {
40     protected $profile_uri; // provided acct: or URI of remote entity
41     protected $oprofile; // Ostatus_profile of remote entity, if valid
42
43     /**
44      * Show the initial form, when we haven't yet been given a valid
45      * remote profile.
46      */
47     function showInputForm()
48     {
49         $user = common_current_user();
50
51         $profile = $user->getProfile();
52
53         $this->elementStart('form', array('method' => 'post',
54                                           'id' => 'form_ostatus_sub',
55                                           'class' => 'form_settings',
56                                           'action' => $this->selfLink()));
57
58         $this->hidden('token', common_session_token());
59
60         $this->elementStart('fieldset', array('id' => 'settings_feeds'));
61
62         $this->elementStart('ul', 'form_data');
63         $this->elementStart('li');
64         $this->input('profile',
65                      // TRANS: Field label for a field that takes an OStatus user address.
66                      _m('Subscribe to'),
67                      $this->profile_uri,
68                      // TRANS: Tooltip for field label "Subscribe to".
69                      _m('OStatus user\'s address, like nickname@example.com or http://example.net/nickname'));
70         $this->elementEnd('li');
71         $this->elementEnd('ul');
72         // TRANS: Button text.
73         $this->submit('validate', _m('BUTTON','Continue'));
74
75         $this->elementEnd('fieldset');
76
77         $this->elementEnd('form');
78     }
79
80     /**
81      * Show the preview-and-confirm form. We've got a valid remote
82      * profile and are ready to poke it!
83      *
84      * This controls the wrapper form; actual profile display will
85      * be in previewUser() or previewGroup() depending on the type.
86      */
87     function showPreviewForm()
88     {
89         $ok = $this->preview();
90         if (!$ok) {
91             // @fixme maybe provide a cancel button or link back?
92             return;
93         }
94
95         $this->elementStart('div', 'entity_actions');
96         $this->elementStart('ul');
97         $this->elementStart('li', 'entity_subscribe');
98         $this->elementStart('form', array('method' => 'post',
99                                           'id' => 'form_ostatus_sub',
100                                           'class' => 'form_remote_authorize',
101                                           'action' =>
102                                           $this->selfLink()));
103         $this->elementStart('fieldset');
104         $this->hidden('token', common_session_token());
105         $this->hidden('profile', $this->profile_uri);
106         if ($this->oprofile->isGroup()) {
107             $this->submit('submit', _m('Join'), 'submit', null,
108                          // TRANS: Button text.
109                          // TRANS: Tooltip for button "Join".
110                          _m('BUTTON','Join this group'));
111         } else {
112             // TRANS: Button text.
113             $this->submit('submit', _m('BUTTON','Confirm'), 'submit', null,
114                          // TRANS: Tooltip for button "Confirm".
115                          _m('Subscribe to this user'));
116         }
117         $this->elementEnd('fieldset');
118         $this->elementEnd('form');
119         $this->elementEnd('li');
120         $this->elementEnd('ul');
121         $this->elementEnd('div');
122     }
123
124     /**
125      * Show a preview for a remote user's profile
126      * @return boolean true if we're ok to try subscribing
127      */
128     function preview()
129     {
130         $oprofile = $this->oprofile;
131         $profile = $oprofile->localProfile();
132
133         $cur = common_current_user();
134         if ($cur->isSubscribed($profile)) {
135             $this->element('div', array('class' => 'error'),
136                            _m("You are already subscribed to this user."));
137             $ok = false;
138         } else {
139             $ok = true;
140         }
141
142         $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
143         $avatarUrl = $avatar ? $avatar->displayUrl() : false;
144
145         $this->showEntity($profile,
146                           $profile->profileurl,
147                           $avatarUrl,
148                           $profile->bio);
149         return $ok;
150     }
151
152     function showEntity($entity, $profile, $avatar, $note)
153     {
154         $nickname = $entity->nickname;
155         $fullname = $entity->fullname;
156         $homepage = $entity->homepage;
157         $location = $entity->location;
158
159         if (!$avatar) {
160             $avatar = Avatar::defaultImage(AVATAR_PROFILE_SIZE);
161         }
162
163         $this->elementStart('div', 'entity_profile vcard');
164         $this->elementStart('dl', 'entity_depiction');
165         $this->element('dt', null, _m('Photo'));
166         $this->elementStart('dd');
167         $this->element('img', array('src' => $avatar,
168                                     'class' => 'photo avatar',
169                                     'width' => AVATAR_PROFILE_SIZE,
170                                     'height' => AVATAR_PROFILE_SIZE,
171                                     'alt' => $nickname));
172         $this->elementEnd('dd');
173         $this->elementEnd('dl');
174
175         $this->elementStart('dl', 'entity_nickname');
176         $this->element('dt', null, _m('Nickname'));
177         $this->elementStart('dd');
178         $hasFN = ($fullname !== '') ? 'nickname' : 'fn nickname';
179         $this->elementStart('a', array('href' => $profile,
180                                        'class' => 'url '.$hasFN));
181         $this->raw($nickname);
182         $this->elementEnd('a');
183         $this->elementEnd('dd');
184         $this->elementEnd('dl');
185
186         if (!is_null($fullname)) {
187             $this->elementStart('dl', 'entity_fn');
188             $this->elementStart('dd');
189             $this->elementStart('span', 'fn');
190             $this->raw($fullname);
191             $this->elementEnd('span');
192             $this->elementEnd('dd');
193             $this->elementEnd('dl');
194         }
195         if (!is_null($location)) {
196             $this->elementStart('dl', 'entity_location');
197             $this->element('dt', null, _m('Location'));
198             $this->elementStart('dd', 'label');
199             $this->raw($location);
200             $this->elementEnd('dd');
201             $this->elementEnd('dl');
202         }
203
204         if (!is_null($homepage)) {
205             $this->elementStart('dl', 'entity_url');
206             $this->element('dt', null, _m('URL'));
207             $this->elementStart('dd');
208             $this->elementStart('a', array('href' => $homepage,
209                                                 'class' => 'url'));
210             $this->raw($homepage);
211             $this->elementEnd('a');
212             $this->elementEnd('dd');
213             $this->elementEnd('dl');
214         }
215
216         if (!is_null($note)) {
217             $this->elementStart('dl', 'entity_note');
218             $this->element('dt', null, _m('Note'));
219             $this->elementStart('dd', 'note');
220             $this->raw($note);
221             $this->elementEnd('dd');
222             $this->elementEnd('dl');
223         }
224         $this->elementEnd('div');
225     }
226
227     /**
228      * Redirect on successful remote user subscription
229      */
230     function success()
231     {
232         $cur = common_current_user();
233         $url = common_local_url('subscriptions', array('nickname' => $cur->nickname));
234         common_redirect($url, 303);
235     }
236
237     /**
238      * Pull data for a remote profile and check if it's valid.
239      * Fills out error UI string in $this->error
240      * Fills out $this->oprofile on success.
241      *
242      * @return boolean
243      */
244     function pullRemoteProfile()
245     {
246         $this->profile_uri = $this->trimmed('profile');
247         try {
248             if (Validate::email($this->profile_uri)) {
249                 $this->oprofile = Ostatus_profile::ensureWebfinger($this->profile_uri);
250             } else if (Validate::uri($this->profile_uri)) {
251                 $this->oprofile = Ostatus_profile::ensureProfileURL($this->profile_uri);
252             } else {
253                 // TRANS: Error text.
254                 $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.");
255                 common_debug('Invalid address format.', __FILE__);
256                 return false;
257             }
258             return true;
259         } catch (FeedSubBadURLException $e) {
260             // TRANS: Error text.
261             $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.");
262             common_debug('Invalid URL or could not reach server.', __FILE__);
263         } catch (FeedSubBadResponseException $e) {
264             // TRANS: Error text.
265             $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
266             common_debug('Cannot read feed; server returned error.', __FILE__);
267         } catch (FeedSubEmptyException $e) {
268             // TRANS: Error text.
269             $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
270             common_debug('Cannot read feed; server returned an empty page.', __FILE__);
271         } catch (FeedSubBadHTMLException $e) {
272             // TRANS: Error text.
273             $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
274             common_debug('Bad HTML, could not find feed link.', __FILE__);
275         } catch (FeedSubNoFeedException $e) {
276             // TRANS: Error text.
277             $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
278             common_debug('Could not find a feed linked from this URL.', __FILE__);
279         } catch (FeedSubUnrecognizedTypeException $e) {
280             // TRANS: Error text.
281             $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
282             common_debug('Not a recognized feed type.', __FILE__);
283         } catch (Exception $e) {
284             // Any new ones we forgot about
285             // TRANS: Error text.
286             $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.");
287             common_debug(sprintf('Bad feed URL: %s %s', get_class($e), $e->getMessage()), __FILE__);
288         }
289
290         return false;
291     }
292
293     function validateRemoteProfile()
294     {
295         if ($this->oprofile->isGroup()) {
296             // Send us to the group subscription form for conf
297             $target = common_local_url('ostatusgroup', array(), array('profile' => $this->profile_uri));
298             common_redirect($target, 303);
299         }
300     }
301
302     /**
303      * Attempt to finalize subscription.
304      * validateFeed must have been run first.
305      *
306      * Calls showForm on failure or success on success.
307      */
308     function saveFeed()
309     {
310         // And subscribe the current user to the local profile
311         $user = common_current_user();
312         $local = $this->oprofile->localProfile();
313         if ($user->isSubscribed($local)) {
314             // TRANS: OStatus remote subscription dialog error.
315             $this->showForm(_m('Already subscribed!'));
316         } elseif (Subscription::start($user, $local)) {
317             $this->success();
318         } else {
319             // TRANS: OStatus remote subscription dialog error.
320             $this->showForm(_m('Remote subscription failed!'));
321         }
322     }
323
324     function prepare($args)
325     {
326         parent::prepare($args);
327
328         if (!common_logged_in()) {
329             // XXX: selfURL() didn't work. :<
330             common_set_returnto($_SERVER['REQUEST_URI']);
331             if (Event::handle('RedirectToLogin', array($this, null))) {
332                 common_redirect(common_local_url('login'), 303);
333             }
334             return false;
335         }
336
337         if ($this->pullRemoteProfile()) {
338             $this->validateRemoteProfile();
339         }
340         return true;
341     }
342
343     /**
344      * Handle the submission.
345      */
346     function handle($args)
347     {
348         parent::handle($args);
349         if ($_SERVER['REQUEST_METHOD'] == 'POST') {
350             $this->handlePost();
351         } else {
352             $this->showForm();
353         }
354     }
355
356     /**
357      * Handle posts to this form
358      *
359      * @return void
360      */
361
362     function handlePost()
363     {
364         // CSRF protection
365         $token = $this->trimmed('token');
366         if (!$token || $token != common_session_token()) {
367             $this->showForm(_m('There was a problem with your session token. '.
368                               'Try again, please.'));
369             return;
370         }
371
372         if ($this->oprofile) {
373             if ($this->arg('submit')) {
374                 $this->saveFeed();
375                 return;
376             }
377         }
378         $this->showForm();
379     }
380
381     /**
382      * Show the appropriate form based on our input state.
383      */
384     function showForm($err=null)
385     {
386         if ($err) {
387             $this->error = $err;
388         }
389         if ($this->boolean('ajax')) {
390             header('Content-Type: text/xml;charset=utf-8');
391             $this->xw->startDocument('1.0', 'UTF-8');
392             $this->elementStart('html');
393             $this->elementStart('head');
394             // TRANS: Form title.
395             $this->element('title', null, _m('Subscribe to user'));
396             $this->elementEnd('head');
397             $this->elementStart('body');
398             $this->showContent();
399             $this->elementEnd('body');
400             $this->elementEnd('html');
401         } else {
402             $this->showPage();
403         }
404     }
405
406     /**
407      * Title of the page
408      *
409      * @return string Title of the page
410      */
411
412     function title()
413     {
414         // TRANS: Page title for OStatus remote subscription form
415         return _m('Confirm');
416     }
417
418     /**
419      * Instructions for use
420      *
421      * @return instructions for use
422      */
423
424     function getInstructions()
425     {
426         // TRANS: Instructions.
427         return _m('You can subscribe to users from other supported sites. Paste their address or profile URI below:');
428     }
429
430     function showPageNotice()
431     {
432         if (!empty($this->error)) {
433             $this->element('p', 'error', $this->error);
434         }
435     }
436
437     /**
438      * Content area of the page
439      *
440      * Shows a form for associating a remote OStatus account with this
441      * StatusNet account.
442      *
443      * @return void
444      */
445     function showContent()
446     {
447         if ($this->oprofile) {
448             $this->showPreviewForm();
449         } else {
450             $this->showInputForm();
451         }
452     }
453
454     function showScripts()
455     {
456         parent::showScripts();
457         $this->autofocus('feedurl');
458     }
459
460     function selfLink()
461     {
462         return common_local_url('ostatussub');
463     }
464
465     /**
466      * Disable the send-notice form at the top of the page.
467      * This is really just a hack for the broken CSS in the Cloudy theme,
468      * I think; copying from other non-notice-navigation pages that do this
469      * as well. There will be plenty of others also broken.
470      *
471      * @fixme fix the cloudy theme
472      * @fixme do this in a more general way
473      */
474     function showNoticeForm() {
475         // nop
476     }
477 }