ostatussub.php 15.3 KB
Newer Older
1 2 3
<?php
/*
 * StatusNet - the distributed open-source microblogging tool
4
 * Copyright (C) 2009-2010, StatusNet, Inc.
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @package OStatusPlugin
22
 * @maintainer Brion Vibber <brion@status.net>
23 24 25 26
 */

if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }

27 28 29 30 31 32 33
/**
 * Key UI methods:
 *
 *  showInputForm() - form asking for a remote profile account or URL
 *                    We end up back here on errors
 *
 *  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 39
class OStatusSubAction extends Action
{
40 41
    protected $profile_uri; // provided acct: or URI of remote entity
    protected $oprofile; // Ostatus_profile of remote entity, if valid
42 43

    /**
44 45
     * Show the initial form, when we haven't yet been given a valid
     * remote profile.
46
     */
47
    function showInputForm()
48 49 50 51 52 53
    {
        $user = common_current_user();

        $profile = $user->getProfile();

        $this->elementStart('form', array('method' => 'post',
54
                                          'id' => 'form_ostatus_sub',
55
                                          'class' => 'form_settings',
56
                                          'action' => $this->selfLink()));
57 58 59 60 61 62

        $this->hidden('token', common_session_token());

        $this->elementStart('fieldset', array('id' => 'settings_feeds'));

        $this->elementStart('ul', 'form_data');
63
        $this->elementStart('li');
64
        $this->input('profile',
65
                     _m('Subscribe to'),
66
                     $this->profile_uri,
67
                     _m("OStatus user's address, like nickname@example.com or http://example.net/nickname"));
68 69 70
        $this->elementEnd('li');
        $this->elementEnd('ul');

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
        $this->submit('validate', _m('Continue'));

        $this->elementEnd('fieldset');

        $this->elementEnd('form');
    }

    /**
     * Show the preview-and-confirm form. We've got a valid remote
     * profile and are ready to poke it!
     *
     * This controls the wrapper form; actual profile display will
     * be in previewUser() or previewGroup() depending on the type.
     */
    function showPreviewForm()
    {
87
        $ok = $this->preview();
88 89 90
        if (!$ok) {
            // @fixme maybe provide a cancel button or link back?
            return;
91 92 93 94 95
        }

        $this->elementStart('div', 'entity_actions');
        $this->elementStart('ul');
        $this->elementStart('li', 'entity_subscribe');
96 97
        $this->elementStart('form', array('method' => 'post',
                                          'id' => 'form_ostatus_sub',
98
                                          'class' => 'form_remote_authorize',
99
                                          'action' =>
100
                                          $this->selfLink()));
101
        $this->elementStart('fieldset');
102 103 104
        $this->hidden('token', common_session_token());
        $this->hidden('profile', $this->profile_uri);
        if ($this->oprofile->isGroup()) {
105 106
            $this->submit('submit', _m('Join'), 'submit', null,
                         _m('Join this group'));
107
        } else {
108
            $this->submit('submit', _m('Confirm'), 'submit', null,
109
                         _m('Subscribe to this user'));
110
        }
111 112
        $this->elementEnd('fieldset');
        $this->elementEnd('form');
113 114 115
        $this->elementEnd('li');
        $this->elementEnd('ul');
        $this->elementEnd('div');
116 117 118 119
    }

    /**
     * Show a preview for a remote user's profile
120
     * @return boolean true if we're ok to try subscribing
121
     */
122
    function preview()
123 124 125 126
    {
        $oprofile = $this->oprofile;
        $profile = $oprofile->localProfile();

127 128 129 130 131 132 133 134 135
        $cur = common_current_user();
        if ($cur->isSubscribed($profile)) {
            $this->element('div', array('class' => 'error'),
                           _m("You are already subscribed to this user."));
            $ok = false;
        } else {
            $ok = true;
        }

136 137 138 139 140 141 142
        $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
        $avatarUrl = $avatar ? $avatar->displayUrl() : false;

        $this->showEntity($profile,
                          $profile->profileurl,
                          $avatarUrl,
                          $profile->bio);
143
        return $ok;
144 145
    }

146
    function showEntity($entity, $profile, $avatar, $note)
147 148 149 150 151
    {
        $nickname = $entity->nickname;
        $fullname = $entity->fullname;
        $homepage = $entity->homepage;
        $location = $entity->location;
152

153 154 155
        if (!$avatar) {
            $avatar = Avatar::defaultImage(AVATAR_PROFILE_SIZE);
        }
156 157 158 159 160

        $this->elementStart('div', 'entity_profile vcard');
        $this->elementStart('dl', 'entity_depiction');
        $this->element('dt', null, _('Photo'));
        $this->elementStart('dd');
161 162 163 164 165
        $this->element('img', array('src' => $avatar,
                                    'class' => 'photo avatar',
                                    'width' => AVATAR_PROFILE_SIZE,
                                    'height' => AVATAR_PROFILE_SIZE,
                                    'alt' => $nickname));
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
        $this->elementEnd('dd');
        $this->elementEnd('dl');

        $this->elementStart('dl', 'entity_nickname');
        $this->element('dt', null, _('Nickname'));
        $this->elementStart('dd');
        $hasFN = ($fullname !== '') ? 'nickname' : 'fn nickname';
        $this->elementStart('a', array('href' => $profile,
                                       'class' => 'url '.$hasFN));
        $this->raw($nickname);
        $this->elementEnd('a');
        $this->elementEnd('dd');
        $this->elementEnd('dl');

        if (!is_null($fullname)) {
            $this->elementStart('dl', 'entity_fn');
            $this->elementStart('dd');
            $this->elementStart('span', 'fn');
            $this->raw($fullname);
            $this->elementEnd('span');
            $this->elementEnd('dd');
            $this->elementEnd('dl');
        }
        if (!is_null($location)) {
            $this->elementStart('dl', 'entity_location');
            $this->element('dt', null, _('Location'));
            $this->elementStart('dd', 'label');
            $this->raw($location);
            $this->elementEnd('dd');
            $this->elementEnd('dl');
        }

        if (!is_null($homepage)) {
            $this->elementStart('dl', 'entity_url');
            $this->element('dt', null, _('URL'));
            $this->elementStart('dd');
            $this->elementStart('a', array('href' => $homepage,
                                                'class' => 'url'));
            $this->raw($homepage);
            $this->elementEnd('a');
            $this->elementEnd('dd');
            $this->elementEnd('dl');
        }

210
        if (!is_null($note)) {
211 212 213
            $this->elementStart('dl', 'entity_note');
            $this->element('dt', null, _('Note'));
            $this->elementStart('dd', 'note');
214
            $this->raw($note);
215 216 217 218
            $this->elementEnd('dd');
            $this->elementEnd('dl');
        }
        $this->elementEnd('div');
219 220 221 222 223
    }

    /**
     * Redirect on successful remote user subscription
     */
224
    function success()
225 226 227 228 229 230 231 232 233 234 235 236 237
    {
        $cur = common_current_user();
        $url = common_local_url('subscriptions', array('nickname' => $cur->nickname));
        common_redirect($url, 303);
    }

    /**
     * Pull data for a remote profile and check if it's valid.
     * Fills out error UI string in $this->error
     * Fills out $this->oprofile on success.
     *
     * @return boolean
     */
238
    function pullRemoteProfile()
239
    {
240
        $this->profile_uri = $this->trimmed('profile');
241
        try {
242 243 244
            if (Validate::email($this->profile_uri)) {
                $this->oprofile = Ostatus_profile::ensureWebfinger($this->profile_uri);
            } else if (Validate::uri($this->profile_uri)) {
245
                $this->oprofile = Ostatus_profile::ensureProfileURL($this->profile_uri);
246
            } else {
247 248
                $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");
                common_debug('Invalid address format.', __FILE__);
249 250
                return false;
            }
251 252
            return true;
        } catch (FeedSubBadURLException $e) {
253 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");
            common_debug('Invalid URL or could not reach server.', __FILE__);
255
        } catch (FeedSubBadResponseException $e) {
256 257
            $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
            common_debug('Cannot read feed; server returned error.', __FILE__);
258
        } catch (FeedSubEmptyException $e) {
259 260
            $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
            common_debug('Cannot read feed; server returned an empty page.', __FILE__);
261
        } catch (FeedSubBadHTMLException $e) {
262 263
            $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
            common_debug('Bad HTML, could not find feed link.', __FILE__);
264
        } catch (FeedSubNoFeedException $e) {
265 266
            $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
            common_debug('Could not find a feed linked from this URL.', __FILE__);
267
        } catch (FeedSubUnrecognizedTypeException $e) {
268 269
            $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
            common_debug('Not a recognized feed type.', __FILE__);
270
        } catch (Exception $e) {
271
            // Any new ones we forgot about
272 273
            $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");
            common_debug(sprintf('Bad feed URL: %s %s', get_class($e), $e->getMessage()), __FILE__);
274 275 276 277 278
        }

        return false;
    }

279 280 281 282 283 284 285 286 287
    function validateRemoteProfile()
    {
        if ($this->oprofile->isGroup()) {
            // Send us to the group subscription form for conf
            $target = common_local_url('ostatusgroup', array(), array('profile' => $this->profile_uri));
            common_redirect($target, 303);
        }
    }

288 289 290 291
    /**
     * Attempt to finalize subscription.
     * validateFeed must have been run first.
     *
292
     * Calls showForm on failure or success on success.
293 294 295 296 297
     */
    function saveFeed()
    {
        // And subscribe the current user to the local profile
        $user = common_current_user();
298 299 300 301
        $local = $this->oprofile->localProfile();
        if ($user->isSubscribed($local)) {
            // TRANS: OStatus remote subscription dialog error.
            $this->showForm(_m('Already subscribed!'));
302
        } elseif (Subscription::start($user, $local)) {
303
            $this->success();
304
        } else {
305 306
            // TRANS: OStatus remote subscription dialog error.
            $this->showForm(_m('Remote subscription failed!'));
307 308 309 310 311 312
        }
    }

    function prepare($args)
    {
        parent::prepare($args);
313 314 315 316 317 318 319 320 321 322

        if (!common_logged_in()) {
            // XXX: selfURL() didn't work. :<
            common_set_returnto($_SERVER['REQUEST_URI']);
            if (Event::handle('RedirectToLogin', array($this, null))) {
                common_redirect(common_local_url('login'), 303);
            }
            return false;
        }

323 324 325
        if ($this->pullRemoteProfile()) {
            $this->validateRemoteProfile();
        }
326 327 328
        return true;
    }

329 330 331
    /**
     * Handle the submission.
     */
332 333 334 335 336 337
    function handle($args)
    {
        parent::handle($args);
        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
            $this->handlePost();
        } else {
338
            $this->showForm();
339
        }
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
    }

    /**
     * Handle posts to this form
     *
     * @return void
     */

    function handlePost()
    {
        // CSRF protection
        $token = $this->trimmed('token');
        if (!$token || $token != common_session_token()) {
            $this->showForm(_('There was a problem with your session token. '.
                              'Try again, please.'));
            return;
        }

358
        if ($this->oprofile) {
359
            if ($this->arg('submit')) {
360 361 362
                $this->saveFeed();
                return;
            }
363
        }
364
        $this->showForm();
365 366 367
    }

    /**
368
     * Show the appropriate form based on our input state.
369
     */
370
    function showForm($err=null)
371
    {
372 373
        if ($err) {
            $this->error = $err;
374
        }
375 376 377 378 379 380 381 382 383 384 385 386 387
        if ($this->boolean('ajax')) {
            header('Content-Type: text/xml;charset=utf-8');
            $this->xw->startDocument('1.0', 'UTF-8');
            $this->elementStart('html');
            $this->elementStart('head');
            $this->element('title', null, _m('Subscribe to user'));
            $this->elementEnd('head');
            $this->elementStart('body');
            $this->showContent();
            $this->elementEnd('body');
            $this->elementEnd('html');
        } else {
            $this->showPage();
388 389 390
        }
    }

391 392 393 394 395
    /**
     * Title of the page
     *
     * @return string Title of the page
     */
396

397 398
    function title()
    {
399
        // TRANS: Page title for OStatus remote subscription form
400
        return _m('Confirm');
401
    }
402

403 404 405 406 407
    /**
     * Instructions for use
     *
     * @return instructions for use
     */
408

409 410 411
    function getInstructions()
    {
        return _m('You can subscribe to users from other supported sites. Paste their address or profile URI below:');
412 413
    }

414
    function showPageNotice()
415
    {
416
        if (!empty($this->error)) {
417
            $this->element('p', 'error', $this->error);
418 419 420
        }
    }

421 422 423 424 425 426 427 428 429 430
    /**
     * Content area of the page
     *
     * Shows a form for associating a remote OStatus account with this
     * StatusNet account.
     *
     * @return void
     */

    function showContent()
431
    {
432 433 434 435 436
        if ($this->oprofile) {
            $this->showPreviewForm();
        } else {
            $this->showInputForm();
        }
437
    }
438

439 440 441 442 443
    function showScripts()
    {
        parent::showScripts();
        $this->autofocus('feedurl');
    }
444 445 446 447 448

    function selfLink()
    {
        return common_local_url('ostatussub');
    }
449 450 451 452 453 454 455 456 457 458 459 460 461 462

    /**
     * Disable the send-notice form at the top of the page.
     * This is really just a hack for the broken CSS in the Cloudy theme,
     * I think; copying from other non-notice-navigation pages that do this
     * as well. There will be plenty of others also broken.
     *
     * @fixme fix the cloudy theme
     * @fixme do this in a more general way
     */
    function showNoticeForm() {
        // nop
    }

463
}