router.php 47.8 KB
Newer Older
1 2
<?php
/**
3
 * StatusNet, the distributed open-source microblogging tool
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 * URL routing utilities
 *
 * PHP version 5
 *
 * LICENCE: 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/>.
 *
 * @category  URL
23
 * @package   StatusNet
24
 * @author    Evan Prodromou <evan@status.net>
25
 * @copyright 2009 StatusNet, Inc.
26
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
27
 * @link      http://status.net/
28 29
 */

30
if (!defined('GNUSOCIAL')) { exit(1); }
31 32 33 34 35 36 37

/**
 * URL Router
 *
 * Cheap wrapper around Net_URL_Mapper
 *
 * @category URL
38
 * @package  StatusNet
39
 * @author   Evan Prodromou <evan@status.net>
40
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
41
 * @link     http://status.net/
42 43 44
 */
class Router
{
45
    var $m = null;
Evan Prodromou's avatar
Evan Prodromou committed
46 47
    static $inst = null;

48 49
    const REGEX_TAG = '[^\/]+'; // [\pL\pN_\-\.]{1,64} better if we can do unicode regexes

Evan Prodromou's avatar
Evan Prodromou committed
50 51 52 53 54 55 56
    static function get()
    {
        if (!Router::$inst) {
            Router::$inst = new Router();
        }
        return Router::$inst;
    }
57

58 59 60 61 62 63 64 65 66
    /**
     * Clear the global singleton instance for this class.
     * Needed to ensure reset when switching site configurations.
     */
    static function clear()
    {
        Router::$inst = null;
    }

67 68
    function __construct()
    {
69
        if (empty($this->m)) {
70
            $this->m = $this->initialize();
71 72 73
        }
    }

74 75
    /**
     * Create a unique hashkey for the router.
Siebrand Mazeland's avatar
Siebrand Mazeland committed
76
     *
77 78 79 80
     * The router's url map can change based on the version of the software
     * you're running and the plugins that are enabled. To avoid having bad routes
     * get stuck in the cache, the key includes a list of plugins and the software
     * version.
81 82
     * 
    * There can still be problems with a) differences in versions of the plugins and
83 84
     * b) people running code between official versions, but these tend to be more
     * sophisticated users who can grok what's going on and clear their caches.
Siebrand Mazeland's avatar
Siebrand Mazeland committed
85
     *
86 87
     * @return string cache key string that should uniquely identify a router
     */
Siebrand Mazeland's avatar
Siebrand Mazeland committed
88

89 90
    static function cacheKey()
    {
91 92 93 94 95 96 97 98 99 100
        $parts = array('router');

        // Many router paths depend on this setting.
        if (common_config('singleuser', 'enabled')) {
            $parts[] = '1user';
        } else {
            $parts[] = 'multi';
        }

        return Cache::codeKey(implode(':', $parts));
101
    }
Siebrand Mazeland's avatar
Siebrand Mazeland committed
102

103 104
    function initialize()
    {
105
        $m = new URLMapper();
106

107
        if (Event::handle('StartInitializeRouter', array(&$m))) {
108

109 110 111 112 113
            // top of the menu hierarchy, sometimes "Home"
            $m->connect('', array('action' => 'top'));

            // public endpoints

114 115
            $m->connect('robots.txt', array('action' => 'robotstxt'));

116 117 118 119
            $m->connect('opensearch/people', array('action' => 'opensearch',
                                                   'type' => 'people'));
            $m->connect('opensearch/notice', array('action' => 'opensearch',
                                                   'type' => 'notice'));
120

121
            // docs
122

123
            $m->connect('doc/:title', array('action' => 'doc'));
124

125 126 127 128
            $m->connect('main/otp/:user_id/:token',
                        array('action' => 'otp'),
                        array('user_id' => '[0-9]+',
                              'token' => '.+'));
129

130 131 132 133 134 135 136 137 138 139 140 141 142 143
            // these take a code; before the main part

            foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) {
                $m->connect('main/'.$c.'/:code', array('action' => $c));
            }

            // Also need a block variant accepting ID on URL for mail links
            $m->connect('main/block/:profileid',
                        array('action' => 'block'),
                        array('profileid' => '[0-9]+'));

            $m->connect('main/sup/:seconds', array('action' => 'sup'),
                        array('seconds' => '[0-9]+'));

144
            // main stuff is repetitive
145

146
            $main = array('login', 'logout', 'register', 'subscribe',
147
                          'unsubscribe', 'cancelsubscription', 'approvesub',
148
                          'confirmaddress', 'recoverpassword',
149
                          'invite', 'sup',
150
                          'block', 'unblock', 'subedit',
151 152 153
                          'groupblock', 'groupunblock',
                          'sandbox', 'unsandbox',
                          'silence', 'unsilence',
154
                          'grantrole', 'revokerole',
155
                          'deleteuser',
156 157
                          'geocode',
                          'version',
158
                          'backupaccount',
159
                          'deleteaccount',
160
                          'restoreaccount',
161
                          'top',
162
                          'public',
163
            );
164

165 166 167
            foreach ($main as $a) {
                $m->connect('main/'.$a, array('action' => $a));
            }
Evan Prodromou's avatar
Evan Prodromou committed
168

169 170
            $m->connect('main/all', array('action' => 'networkpublic'));

Shashi Gowda's avatar
Shashi Gowda committed
171 172
            $m->connect('main/tagprofile/:id', array('action' => 'tagprofile'),
                                               array('id' => '[0-9]+'));
173

174 175
            $m->connect('main/tagprofile', array('action' => 'tagprofile'));

176 177 178
            $m->connect('main/xrds',
                        array('action' => 'publicxrds'));

179 180
            // settings

181
            foreach (array('profile', 'avatar', 'password', 'im', 'oauthconnections',
182
                           'oauthapps', 'email', 'sms', 'url') as $s) {
183 184
                $m->connect('settings/'.$s, array('action' => $s.'settings'));
            }
185

186 187 188 189
            if (common_config('oldschool', 'enabled')) {
                $m->connect('settings/oldschool', array('action' => 'oldschoolsettings'));
            }

Evan Prodromou's avatar
Evan Prodromou committed
190
            $m->connect('settings/oauthapps/show/:id',
191 192
                        array('action' => 'showapplication'),
                        array('id' => '[0-9]+')
Evan Prodromou's avatar
Evan Prodromou committed
193 194
            );
            $m->connect('settings/oauthapps/new',
195
                        array('action' => 'newapplication')
Evan Prodromou's avatar
Evan Prodromou committed
196 197
            );
            $m->connect('settings/oauthapps/edit/:id',
198 199
                        array('action' => 'editapplication'),
                        array('id' => '[0-9]+')
Evan Prodromou's avatar
Evan Prodromou committed
200
            );
201
            $m->connect('settings/oauthapps/delete/:id',
202 203
                        array('action' => 'deleteapplication'),
                        array('id' => '[0-9]+')
204
            );
Evan Prodromou's avatar
Evan Prodromou committed
205

206
            // search
207

208 209 210 211
            foreach (array('group', 'people', 'notice') as $s) {
                $m->connect('search/'.$s.'?q=:q',
                            array('action' => $s.'search'),
                            array('q' => '.+'));
212
                $m->connect('search/'.$s, array('action' => $s.'search'));
213 214 215 216 217 218
            }

            // The second of these is needed to make the link work correctly
            // when inserted into the page. The first is needed to match the
            // route on the way in. Seems to be another Net_URL_Mapper bug to me.
            $m->connect('search/notice/rss?q=:q', array('action' => 'noticesearchrss'),
219
                        array('q' => '.+'));
220
            $m->connect('search/notice/rss', array('action' => 'noticesearchrss'));
221

222 223 224 225
            $m->connect('attachment/:attachment',
                        array('action' => 'attachment'),
                        array('attachment' => '[0-9]+'));

226 227 228 229
            $m->connect('attachment/:attachment/download',
                        array('action' => 'attachment_download'),
                        array('attachment' => '[0-9]+'));

230 231 232 233 234 235
            $m->connect('attachment/:attachment/thumbnail',
                        array('action' => 'attachment_thumbnail'),
                        array('attachment' => '[0-9]+'));

            $m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
                        array('action' => 'newnotice'),
236
                        array('replyto' => Nickname::DISPLAY_FMT),
237 238
                        array('inreplyto' => '[0-9]+'));

239 240 241 242 243 244
            $m->connect('notice/new?replyto=:replyto',
                        array('action' => 'newnotice'),
                        array('replyto' => Nickname::DISPLAY_FMT));

            $m->connect('notice/new', array('action' => 'newnotice'));

245 246 247
            $m->connect('notice/:notice',
                        array('action' => 'shownotice'),
                        array('notice' => '[0-9]+'));
248

249
            $m->connect('notice/:notice/delete',
250 251 252 253 254 255 256 257 258 259 260 261 262
                        array('action' => 'deletenotice'),
                        array('notice' => '[0-9]+'));

            // conversation

            $m->connect('conversation/:id',
                        array('action' => 'conversation'),
                        array('id' => '[0-9]+'));

            $m->connect('user/:id',
                        array('action' => 'userbyid'),
                        array('id' => '[0-9]+'));

263 264 265 266 267 268
            if (!common_config('performance', 'high')) {
                $m->connect('tags/', array('action' => 'publictagcloud'));
                $m->connect('tag/', array('action' => 'publictagcloud'));
                $m->connect('tags', array('action' => 'publictagcloud'));
                $m->connect('tag', array('action' => 'publictagcloud'));
            }
269 270
            $m->connect('tag/:tag/rss',
                        array('action' => 'tagrss'),
271
                        array('tag' => self::REGEX_TAG));
272 273
            $m->connect('tag/:tag',
                        array('action' => 'tag'),
274
                        array('tag' => self::REGEX_TAG));
275 276 277 278 279

            // groups

            $m->connect('group/new', array('action' => 'newgroup'));

280
            foreach (array('edit', 'join', 'leave', 'delete', 'cancel', 'approve') as $v) {
281 282
                $m->connect('group/:nickname/'.$v,
                            array('action' => $v.'group'),
283
                            array('nickname' => Nickname::DISPLAY_FMT));
284 285 286
                $m->connect('group/:id/id/'.$v,
                            array('action' => $v.'group'),
                            array('id' => '[0-9]+'));
287 288
            }

289
            foreach (array('members', 'logo', 'rss') as $n) {
290 291
                $m->connect('group/:nickname/'.$n,
                            array('action' => 'group'.$n),
292
                            array('nickname' => Nickname::DISPLAY_FMT));
293 294 295 296
            }

            $m->connect('group/:nickname/foaf',
                        array('action' => 'foafgroup'),
297
                        array('nickname' => Nickname::DISPLAY_FMT));
298

299 300
            $m->connect('group/:nickname/blocked',
                        array('action' => 'blockedfromgroup'),
301
                        array('nickname' => Nickname::DISPLAY_FMT));
302

303 304
            $m->connect('group/:nickname/makeadmin',
                        array('action' => 'makeadmin'),
305
                        array('nickname' => Nickname::DISPLAY_FMT));
306

307 308 309
            $m->connect('group/:nickname/members/pending',
                        array('action' => 'groupqueue'),
                        array('nickname' => Nickname::DISPLAY_FMT));
310

311 312 313
            $m->connect('group/:id/id',
                        array('action' => 'groupbyid'),
                        array('id' => '[0-9]+'));
314

315 316
            $m->connect('group/:nickname',
                        array('action' => 'showgroup'),
317
                        array('nickname' => Nickname::DISPLAY_FMT));
318

319 320 321 322
            $m->connect('group/:nickname/',
                        array('action' => 'showgroup'),
                        array('nickname' => Nickname::DISPLAY_FMT));

323 324 325 326 327 328 329 330 331
            $m->connect('group/', array('action' => 'groups'));
            $m->connect('group', array('action' => 'groups'));
            $m->connect('groups/', array('action' => 'groups'));
            $m->connect('groups', array('action' => 'groups'));

            // Twitter-compatible API

            // statuses API

332 333 334 335 336
            $m->connect('api',
                        array('action' => 'Redirect',
                              'nextAction' => 'doc',
                              'args' => array('title' => 'api')));

337 338
            $m->connect('api/statuses/public_timeline.:format',
                        array('action' => 'ApiTimelinePublic',
339
                              'format' => '(xml|json|rss|atom|as)'));
340

341 342 343 344 345
            // this is not part of the Twitter API. Also may require authentication depending on server config!
            $m->connect('api/statuses/networkpublic_timeline.:format',
                        array('action' => 'ApiTimelineNetworkPublic',
                              'format' => '(xml|json|rss|atom|as)'));

346 347
            $m->connect('api/statuses/friends_timeline/:id.:format',
                        array('action' => 'ApiTimelineFriends',
348
                              'id' => Nickname::INPUT_FMT,
349
                              'format' => '(xml|json|rss|atom|as)'));
350

351 352
            $m->connect('api/statuses/friends_timeline.:format',
                        array('action' => 'ApiTimelineFriends',
353
                              'format' => '(xml|json|rss|atom|as)'));
354 355

            $m->connect('api/statuses/home_timeline/:id.:format',
356
                        array('action' => 'ApiTimelineHome',
357
                              'id' => Nickname::INPUT_FMT,
358
                              'format' => '(xml|json|rss|atom|as)'));
359

360 361
            $m->connect('api/statuses/home_timeline.:format',
                        array('action' => 'ApiTimelineHome',
362
                              'format' => '(xml|json|rss|atom|as)'));
363

364 365
            $m->connect('api/statuses/user_timeline/:id.:format',
                        array('action' => 'ApiTimelineUser',
366
                              'id' => Nickname::INPUT_FMT,
367
                              'format' => '(xml|json|rss|atom|as)'));
368

369 370
            $m->connect('api/statuses/user_timeline.:format',
                        array('action' => 'ApiTimelineUser',
371
                              'format' => '(xml|json|rss|atom|as)'));
372 373 374

            $m->connect('api/statuses/mentions/:id.:format',
                        array('action' => 'ApiTimelineMentions',
375
                              'id' => Nickname::INPUT_FMT,
376
                              'format' => '(xml|json|rss|atom|as)'));
377

378
            $m->connect('api/statuses/mentions.:format',
379
                        array('action' => 'ApiTimelineMentions',
380
                              'format' => '(xml|json|rss|atom|as)'));
381 382 383

            $m->connect('api/statuses/replies/:id.:format',
                        array('action' => 'ApiTimelineMentions',
384
                              'id' => Nickname::INPUT_FMT,
385
                              'format' => '(xml|json|rss|atom|as)'));
386

387 388 389
            $m->connect('api/statuses/replies.:format',
                        array('action' => 'ApiTimelineMentions',
                              'format' => '(xml|json|rss|atom|as)'));
390 391 392 393 394 395 396 397 398
 
            $m->connect('api/statuses/mentions_timeline/:id.:format',
                        array('action' => 'ApiTimelineMentions',
                              'id' => Nickname::INPUT_FMT,
                              'format' => '(xml|json|rss|atom|as)'));

            $m->connect('api/statuses/mentions_timeline.:format',
                        array('action' => 'ApiTimelineMentions',
                              'format' => '(xml|json|rss|atom|as)'));
399

400 401
            $m->connect('api/statuses/friends/:id.:format',
                        array('action' => 'ApiUserFriends',
402
                              'id' => Nickname::INPUT_FMT,
403
                              'format' => '(xml|json)'));
404

405 406
            $m->connect('api/statuses/friends.:format',
                        array('action' => 'ApiUserFriends',
407
                              'format' => '(xml|json)'));
408

409 410
            $m->connect('api/statuses/followers/:id.:format',
                        array('action' => 'ApiUserFollowers',
411
                              'id' => Nickname::INPUT_FMT,
412
                              'format' => '(xml|json)'));
413

414 415 416
            $m->connect('api/statuses/followers.:format',
                        array('action' => 'ApiUserFollowers',
                              'format' => '(xml|json)'));
417

418 419 420
            $m->connect('api/statuses/show/:id.:format',
                        array('action' => 'ApiStatusesShow',
                              'id' => '[0-9]+',
421
                              'format' => '(xml|json|atom)'));
422

423 424 425 426
            $m->connect('api/statuses/show.:format',
                        array('action' => 'ApiStatusesShow',
                              'format' => '(xml|json|atom)'));

427 428 429
            $m->connect('api/statuses/update.:format',
                        array('action' => 'ApiStatusesUpdate',
                              'format' => '(xml|json)'));
430

431
            $m->connect('api/statuses/destroy/:id.:format',
432
                        array('action' => 'ApiStatusesDestroy',
433
                              'id' => '[0-9]+',
434
                              'format' => '(xml|json)'));
435

436
            $m->connect('api/statuses/destroy.:format',
437
                        array('action' => 'ApiStatusesDestroy',
438 439
                              'format' => '(xml|json)'));

440 441
            // START qvitter API additions
            
442 443 444 445
            $m->connect('api/attachment/:id.:format',
                        array('action' => 'ApiAttachment',
                              'id' => '[0-9]+',
                              'format' => '(xml|json)'));
446
            
447 448 449
            $m->connect('api/checkhub.:format',
                        array('action' => 'ApiCheckHub',
                              'format' => '(xml|json)'));
450
            
451 452 453
            $m->connect('api/externalprofile/show.:format',
                        array('action' => 'ApiExternalProfileShow',
                              'format' => '(xml|json)'));
454 455

            $m->connect('api/statusnet/groups/admins/:id.:format',
456 457 458
                        array('action' => 'ApiGroupAdmins',
                              'id' => Nickname::INPUT_FMT,
                              'format' => '(xml|json)'));
459
            
460 461 462
            $m->connect('api/account/update_link_color.:format',
                        array('action' => 'ApiAccountUpdateLinkColor',
                              'format' => '(xml|json)'));
463
                
464 465 466
            $m->connect('api/account/update_background_color.:format',
                        array('action' => 'ApiAccountUpdateBackgroundColor',
                              'format' => '(xml|json)'));
467

468 469 470
            $m->connect('api/account/register.:format',
                        array('action' => 'ApiAccountRegister',
                              'format' => '(xml|json)'));
471
            
472 473 474
            $m->connect('api/check_nickname.:format',
                        array('action' => 'ApiCheckNickname',
                              'format' => '(xml|json)'));
475 476 477

            // END qvitter API additions

478
            // users
479

480
            $m->connect('api/users/show/:id.:format',
481
                        array('action' => 'ApiUserShow',
482
                              'id' => Nickname::INPUT_FMT,
483 484
                              'format' => '(xml|json)'));

485
            $m->connect('api/users/show.:format',
486 487
                        array('action' => 'ApiUserShow',
                              'format' => '(xml|json)'));
488

489 490 491 492 493
            $m->connect('api/users/profile_image/:screen_name.:format',
                        array('action' => 'ApiUserProfileImage',
                              'screen_name' => Nickname::DISPLAY_FMT,
                              'format' => '(xml|json)'));

494
            // friendships
495

496 497 498
            $m->connect('api/friendships/show.:format',
                        array('action' => 'ApiFriendshipsShow',
                              'format' => '(xml|json)'));
499

500 501 502
            $m->connect('api/friendships/exists.:format',
                        array('action' => 'ApiFriendshipsExists',
                              'format' => '(xml|json)'));
503

504
            $m->connect('api/friendships/create/:id.:format',
505
                        array('action' => 'ApiFriendshipsCreate',
506
                              'id' => Nickname::INPUT_FMT,
507
                              'format' => '(xml|json)'));
508

509
            $m->connect('api/friendships/create.:format',
510 511
                        array('action' => 'ApiFriendshipsCreate',
                              'format' => '(xml|json)'));
512

513 514
            $m->connect('api/friendships/destroy/:id.:format',
                        array('action' => 'ApiFriendshipsDestroy',
515
                              'id' => Nickname::INPUT_FMT,
516
                              'format' => '(xml|json)'));
517

518 519 520 521
            $m->connect('api/friendships/destroy.:format',
                        array('action' => 'ApiFriendshipsDestroy',
                              'format' => '(xml|json)'));

522
            // Social graph
523

524
            $m->connect('api/friends/ids/:id.:format',
525
                        array('action' => 'ApiUserFriends',
526
                              'ids_only' => true));
527

528
            $m->connect('api/followers/ids/:id.:format',
529
                        array('action' => 'ApiUserFollowers',
530
                              'ids_only' => true));
531

532
            $m->connect('api/friends/ids.:format',
533
                        array('action' => 'ApiUserFriends',
534
                              'ids_only' => true));
535

536
            $m->connect('api/followers/ids.:format',
537
                        array('action' => 'ApiUserFollowers',
538
                              'ids_only' => true));
539

540
            // account
541

542 543
            $m->connect('api/account/verify_credentials.:format',
                        array('action' => 'ApiAccountVerifyCredentials'));
544

545 546 547
            $m->connect('api/account/update_profile.:format',
                        array('action' => 'ApiAccountUpdateProfile'));

548 549 550
            $m->connect('api/account/update_profile_image.:format',
                        array('action' => 'ApiAccountUpdateProfileImage'));

551 552 553
            $m->connect('api/account/update_delivery_device.:format',
                        array('action' => 'ApiAccountUpdateDeliveryDevice'));

554
            // special case where verify_credentials is called w/out a format
555

556 557
            $m->connect('api/account/verify_credentials',
                        array('action' => 'ApiAccountVerifyCredentials'));
558

559 560
            $m->connect('api/account/rate_limit_status.:format',
                        array('action' => 'ApiAccountRateLimitStatus'));
561

562
            // blocks
563

564 565
            $m->connect('api/blocks/create/:id.:format',
                        array('action' => 'ApiBlockCreate',
566
                              'id' => Nickname::INPUT_FMT,
567
                              'format' => '(xml|json)'));
568

569 570
            $m->connect('api/blocks/create.:format',
                        array('action' => 'ApiBlockCreate',
571 572
                              'format' => '(xml|json)'));

573 574
            $m->connect('api/blocks/destroy/:id.:format',
                        array('action' => 'ApiBlockDestroy',
575
                              'id' => Nickname::INPUT_FMT,
576
                              'format' => '(xml|json)'));
577 578 579 580 581

            $m->connect('api/blocks/destroy.:format',
                        array('action' => 'ApiBlockDestroy',
                              'format' => '(xml|json)'));

582
            // help
583

584 585 586
            $m->connect('api/help/test.:format',
                        array('action' => 'ApiHelpTest',
                              'format' => '(xml|json)'));
587

588
            // statusnet
589

590
            $m->connect('api/statusnet/version.:format',
591
                        array('action' => 'ApiGNUsocialVersion',
592
                              'format' => '(xml|json)'));
593

594
            $m->connect('api/statusnet/config.:format',
595
                        array('action' => 'ApiGNUsocialConfig',
596
                              'format' => '(xml|json)'));
597

598
            // For our current software name, we provide "gnusocial" base action
599

600 601
            $m->connect('api/gnusocial/version.:format',
                        array('action' => 'ApiGNUsocialVersion',
602 603
                              'format' => '(xml|json)'));

604 605
            $m->connect('api/gnusocial/config.:format',
                        array('action' => 'ApiGNUsocialConfig',
606
                              'format' => '(xml|json)'));
607

608 609 610 611 612 613 614 615
            // Groups and tags are newer than 0.8.1 so no backward-compatibility
            // necessary

            // Groups
            //'list' has to be handled differently, as php will not allow a method to be named 'list'

            $m->connect('api/statusnet/groups/timeline/:id.:format',
                        array('action' => 'ApiTimelineGroup',
616
                              'id' => Nickname::INPUT_FMT,
617
                              'format' => '(xml|json|rss|atom|as)'));
618 619 620

            $m->connect('api/statusnet/groups/show/:id.:format',
                        array('action' => 'ApiGroupShow',
621
                              'id' => Nickname::INPUT_FMT,
622 623
                              'format' => '(xml|json)'));

624 625
            $m->connect('api/statusnet/groups/show.:format',
                        array('action' => 'ApiGroupShow',
626 627 628 629
                              'format' => '(xml|json)'));

            $m->connect('api/statusnet/groups/join/:id.:format',
                        array('action' => 'ApiGroupJoin',
mmn's avatar
mmn committed
630
                              'id' => Nickname::INPUT_FMT,
631 632
                              'format' => '(xml|json)'));

633 634
            $m->connect('api/statusnet/groups/join.:format',
                        array('action' => 'ApiGroupJoin',
635 636 637 638 639 640
                              'format' => '(xml|json)'));

            $m->connect('api/statusnet/groups/leave/:id.:format',
                        array('action' => 'ApiGroupLeave',
                              'format' => '(xml|json)'));

641 642 643 644 645
            $m->connect('api/statusnet/groups/leave.:format',
                        array('action' => 'ApiGroupLeave',
                              'id' => Nickname::INPUT_FMT,
                              'format' => '(xml|json)'));

646 647 648 649
            $m->connect('api/statusnet/groups/is_member.:format',
                        array('action' => 'ApiGroupIsMember',
                              'format' => '(xml|json)'));

650
            $m->connect('api/statusnet/groups/list/:id.:format',
651
                        array('action' => 'ApiGroupList',
652
                              'id' => Nickname::INPUT_FMT,
653 654
                              'format' => '(xml|json|rss|atom)'));

655
            $m->connect('api/statusnet/groups/list.:format',
656 657 658 659 660 661 662
                        array('action' => 'ApiGroupList',
                              'format' => '(xml|json|rss|atom)'));

            $m->connect('api/statusnet/groups/list_all.:format',
                        array('action' => 'ApiGroupListAll',
                              'format' => '(xml|json|rss|atom)'));

663
            $m->connect('api/statusnet/groups/membership/:id.:format',
664
                        array('action' => 'ApiGroupMembership',
665
                              'id' => Nickname::INPUT_FMT,
666 667
                              'format' => '(xml|json)'));

668
            $m->connect('api/statusnet/groups/membership.:format',
669 670 671 672 673 674
                        array('action' => 'ApiGroupMembership',
                              'format' => '(xml|json)'));

            $m->connect('api/statusnet/groups/create.:format',
                        array('action' => 'ApiGroupCreate',
                              'format' => '(xml|json)'));
675 676 677 678 679

            $m->connect('api/statusnet/groups/update/:id.:format',
                        array('action' => 'ApiGroupProfileUpdate',
                              'id' => '[a-zA-Z0-9]+',
                              'format' => '(xml|json)'));
Evan Prodromou's avatar
Evan Prodromou committed
680 681 682 683 684
                              
            $m->connect('api/statusnet/conversation/:id.:format',
                        array('action' => 'apiconversation',
                              'id' => '[0-9]+',
                              'format' => '(xml|json|rss|atom|as)'));
685

686
            // Lists (people tags)
687 688 689
            $m->connect('api/lists/list.:format',
                        array('action' => 'ApiListSubscriptions',
                              'format' => '(xml|json)'));
690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708

            $m->connect('api/lists/memberships.:format',
                        array('action' => 'ApiListMemberships',
                              'format' => '(xml|json)'));

            $m->connect('api/:user/lists/memberships.:format',
                        array('action' => 'ApiListMemberships',
                              'user' => '[a-zA-Z0-9]+',
                              'format' => '(xml|json)'));

            $m->connect('api/lists/subscriptions.:format',
                        array('action' => 'ApiListSubscriptions',
                              'format' => '(xml|json)'));

            $m->connect('api/:user/lists/subscriptions.:format',
                        array('action' => 'ApiListSubscriptions',
                              'user' => '[a-zA-Z0-9]+',
                              'format' => '(xml|json)'));

709
            $m->connect('api/lists.:format',
710 711 712 713 714 715 716 717 718
                        array('action' => 'ApiLists',
                              'format' => '(xml|json)'));

            $m->connect('api/:user/lists/:id.:format',
                        array('action' => 'ApiList',
                              'user' => '[a-zA-Z0-9]+',
                              'id' => '[a-zA-Z0-9]+',
                              'format' => '(xml|json)'));

719 720 721 722 723
            $m->connect('api/:user/lists.:format',
                        array('action' => 'ApiLists',
                              'user' => '[a-zA-Z0-9]+',
                              'format' => '(xml|json)'));

724 725 726 727 728 729
            $m->connect('api/:user/lists/:id/statuses.:format',
                        array('action' => 'ApiTimelineList',
                              'user' => '[a-zA-Z0-9]+',
                              'id' => '[a-zA-Z0-9]+',
                              'format' => '(xml|json|rss|atom)'));

730 731
            $m->connect('api/:user/:list_id/members/:id.:format',
                        array('action' => 'ApiListMember',
732 733
                              'user' => '[a-zA-Z0-9]+',
                              'list_id' => '[a-zA-Z0-9]+',
734
                              'id' => '[a-zA-Z0-9]+',
735 736
                              'format' => '(xml|json)'));

737 738
            $m->connect('api/:user/:list_id/members.:format',
                        array('action' => 'ApiListMembers',
739 740 741 742
                              'user' => '[a-zA-Z0-9]+',
                              'list_id' => '[a-zA-Z0-9]+',
                              'format' => '(xml|json)'));

743 744
            $m->connect('api/:user/:list_id/subscribers/:id.:format',
                        array('action' => 'ApiListSubscriber',
745 746 747 748 749
                              'user' => '[a-zA-Z0-9]+',
                              'list_id' => '[a-zA-Z0-9]+',
                              'id' => '[a-zA-Z0-9]+',
                              'format' => '(xml|json)'));

750 751
            $m->connect('api/:user/:list_id/subscribers.:format',
                        array('action' => 'ApiListSubscribers',
752 753 754 755
                              'user' => '[a-zA-Z0-9]+',
                              'list_id' => '[a-zA-Z0-9]+',
                              'format' => '(xml|json)'));

756 757 758
            // Tags
            $m->connect('api/statusnet/tags/timeline/:tag.:format',
                        array('action' => 'ApiTimelineTag',
759
                              'tag'    => self::REGEX_TAG,
760
                              'format' => '(xml|json|rss|atom|as)'));
761

762 763 764 765 766
            // media related
            $m->connect(
                'api/statusnet/media/upload',
                array('action' => 'ApiMediaUpload')
            );
767 768 769 770
            $m->connect(
                'api/statuses/update_with_media.json',
                array('action' => 'ApiMediaUpload')
            );
771 772 773 774 775 776 777
            // Twitter Media upload API v1.1
            $m->connect(
                'api/media/upload.:format',
                array('action' => 'ApiMediaUpload',
                      'format' => '(xml|json)',
                      )
            );
778

779
            // search
780 781 782
            $m->connect('api/search.atom', array('action' => 'ApiSearchAtom'));
            $m->connect('api/search.json', array('action' => 'ApiSearchJSON'));
            $m->connect('api/trends.json', array('action' => 'ApiTrends'));
783

Evan Prodromou's avatar
Evan Prodromou committed
784
            $m->connect('api/oauth/request_token',
785
                        array('action' => 'ApiOAuthRequestToken'));
Evan Prodromou's avatar
Evan Prodromou committed
786 787

            $m->connect('api/oauth/access_token',
788
                        array('action' => 'ApiOAuthAccessToken'));
Evan Prodromou's avatar
Evan Prodromou committed
789 790

            $m->connect('api/oauth/authorize',
791
                        array('action' => 'ApiOAuthAuthorize'));
Evan Prodromou's avatar
Evan Prodromou committed
792 793 794

            // Admin

795 796
            $m->connect('panel/site', array('action' => 'siteadminpanel'));
            $m->connect('panel/user', array('action' => 'useradminpanel'));
797
            $m->connect('panel/access', array('action' => 'accessadminpanel'));
798 799 800 801 802 803 804
            $m->connect('panel/paths', array('action' => 'pathsadminpanel'));
            $m->connect('panel/sessions', array('action' => 'sessionsadminpanel'));
            $m->connect('panel/sitenotice', array('action' => 'sitenoticeadminpanel'));
            $m->connect('panel/license', array('action' => 'licenseadminpanel'));

            $m->connect('panel/plugins', array('action' => 'pluginsadminpanel'));
            $m->connect('panel/plugins/enable/:plugin',
805 806
                        array('action' => 'pluginenable'),
                        array('plugin' => '[A-Za-z0-9_]+'));
807
            $m->connect('panel/plugins/disable/:plugin',
808 809
                        array('action' => 'plugindisable'),
                        array('plugin' => '[A-Za-z0-9_]+'));
810

811 812 813 814
            $m->connect('getfile/:filename',
                        array('action' => 'getfile'),
                        array('filename' => '[A-Za-z0-9._-]+'));

815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830
            // Common people-tag stuff

            $m->connect('peopletag/:tag', array('action' => 'peopletag',
                                                'tag'    => self::REGEX_TAG));

            $m->connect('selftag/:tag', array('action' => 'selftag',
                                              'tag'    => self::REGEX_TAG));

            $m->connect('main/addpeopletag', array('action' => 'addpeopletag'));

            $m->connect('main/removepeopletag', array('action' => 'removepeopletag'));

            $m->connect('main/profilecompletion', array('action' => 'profilecompletion'));

            $m->connect('main/peopletagautocomplete', array('action' => 'peopletagautocomplete'));

Evan Prodromou's avatar
Evan Prodromou committed
831
            // In the "root"
832

Evan Prodromou's avatar
Evan Prodromou committed
833 834
            if (common_config('singleuser', 'enabled')) {

835
                $nickname = User::singleUserNickname();
Evan Prodromou's avatar
Evan Prodromou committed
836 837

                foreach (array('subscriptions', 'subscribers',
838
                               'all', 'foaf', 'replies',
839
                               ) as $a) {
Evan Prodromou's avatar
Evan Prodromou committed
840 841 842 843 844 845 846 847 848
                    $m->connect($a,
                                array('action' => $a,
                                      'nickname' => $nickname));
                }

                foreach (array('subscriptions', 'subscribers') as $a) {
                    $m->connect($a.'/:tag',
                                array('action' => $a,
                                      'nickname' => $nickname),
849
                                array('tag' => self::REGEX_TAG));
Evan Prodromou's avatar
Evan Prodromou committed
850 851
                }

852 853 854 855
                $m->connect('subscribers/pending',
                            array('action' => 'subqueue',
                                  'nickname' => $nickname));

Evan Prodromou's avatar
Evan Prodromou committed
856 857 858 859 860 861
                foreach (array('rss', 'groups') as $a) {
                    $m->connect($a,
                                array('action' => 'user'.$a,
                                      'nickname' => $nickname));
                }

862
                foreach (array('all', 'replies') as $a) {
Evan Prodromou's avatar
Evan Prodromou committed
863 864 865 866 867
                    $m->connect($a.'/rss',
                                array('action' => $a.'rss',
                                      'nickname' => $nickname));
                }

868 869 870
                $m->connect('avatar',
                            array('action' => 'avatarbynickname',
                                  'nickname' => $nickname));
Evan Prodromou's avatar
Evan Prodromou committed
871 872 873
                $m->connect('avatar/:size',
                            array('action' => 'avatarbynickname',
                                  'nickname' => $nickname),
874
                            array('size' => '(|original|\d+)'));
Evan Prodromou's avatar
Evan Prodromou committed
875 876 877 878

                $m->connect('tag/:tag/rss',
                            array('action' => 'userrss',
                                  'nickname' => $nickname),
879
                            array('tag' => self::REGEX_TAG));
Evan Prodromou's avatar
Evan Prodromou committed
880 881 882 883

                $m->connect('tag/:tag',
                            array('action' => 'showstream',
                                  'nickname' => $nickname),
884
                            array('tag' => self::REGEX_TAG));
Evan Prodromou's avatar
Evan Prodromou committed
885

886 887 888 889
                $m->connect('rsd.xml',
                            array('action' => 'rsd',
                                  'nickname' => $nickname));

890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932
                // peopletags

                $m->connect('peopletags',
                            array('action' => 'peopletagsbyuser'));

                $m->connect('peopletags/private',
                            array('action' => 'peopletagsbyuser',
                                  'private' => 1));

                $m->connect('peopletags/public',
                            array('action' => 'peopletagsbyuser',
                                  'public' => 1));

                $m->connect('othertags',
                            array('action' => 'peopletagsforuser'));

                $m->connect('peopletagsubscriptions',
                            array('action' => 'peopletagsubscriptions'));

                $m->connect('all/:tag/subscribers',
                            array('action' => 'peopletagsubscribers',
                                  'tag' => self::REGEX_TAG));

                $m->connect('all/:tag/tagged',
                                array('action' => 'peopletagged',
                                      'tag' => self::REGEX_TAG));

                $m->connect('all/:tag/edit',
                                array('action' => 'editpeopletag',
                                      'tag' => self::REGEX_TAG));

                foreach(array('subscribe', 'unsubscribe') as $v) {
                    $m->connect('peopletag/:id/'.$v,
                                    array('action' => $v.'peopletag',
                                          'id' => '[0-9]{1,64}'));
                }
                $m->connect('user/:tagger_id/profiletag/:id/id',
                                array('action' => 'profiletagbyid',
                                      'tagger_id' => '[0-9]+',
                                      'id' => '[0-9]+'));

                $m->connect('all/:tag',
                                array('action' => 'showprofiletag',
933
                                      'nickname' => $nickname,
934 935 936 937 938 939 940
                                      'tag' => self::REGEX_TAG));

                foreach (array('subscriptions', 'subscribers') as $a) {
                    $m->connect($a.'/:tag',
                                array('action' => $a),
                                array('tag' => self::REGEX_TAG));
                }
941
            }
Evan Prodromou's avatar
Evan Prodromou committed
942

943 944 945 946 947 948 949 950
            $m->connect('rss', array('action' => 'publicrss'));
            $m->connect('featuredrss', array('action' => 'featuredrss'));
            $m->connect('featured/', array('action' => 'featured'));
            $m->connect('featured', array('action' => 'featured'));
            $m->connect('rsd.xml', array('action' => 'rsd'));

            foreach (array('subscriptions', 'subscribers',
                           'nudge', 'all', 'foaf', 'replies',
951
                           'inbox', 'outbox') as $a) {
952 953
                $m->connect(':nickname/'.$a,
                            array('action' => $a),
954
                            array('nickname' => Nickname::DISPLAY_FMT));
955 956 957 958
            }
            $m->connect(':nickname/subscribers/pending',
                        array('action' => 'subqueue'),
                        array('nickname' => Nickname::DISPLAY_FMT));
Evan Prodromou's avatar
Evan Prodromou committed
959

960 961 962 963 964 965 966
            // some targeted RSS 1.0 actions (extends TargetedRss10Action)
            foreach (array('all', 'replies') as $a) {
                $m->connect(':nickname/'.$a.'/rss',
                            array('action' => $a.'rss'),
                            array('nickname' => Nickname::DISPLAY_FMT));
            }

967
            // people tags
Shashi Gowda's avatar
Shashi Gowda committed
968

969 970 971
            $m->connect(':nickname/peopletags',
                            array('action' => 'peopletagsbyuser',
                                  'nickname' => Nickname::DISPLAY_FMT));
Shashi Gowda's avatar
Shashi Gowda committed
972

973 974 975 976
            $m->connect(':nickname/peopletags/private',
                            array('action' => 'peopletagsbyuser',
                                  'nickname' => Nickname::DISPLAY_FMT,
                                  'private' => 1));
Shashi Gowda's avatar
Shashi Gowda committed
977

978 979 980 981
            $m->connect(':nickname/peopletags/public',
                            array('action' => 'peopletagsbyuser',
                                  'nickname' => Nickname::DISPLAY_FMT,
                                  'public' => 1));
Shashi Gowda's avatar
Shashi Gowda committed
982

983 984 985
            $m->connect(':nickname/othertags',
                            array('action' => 'peopletagsforuser',
                                  'nickname' => Nickname::DISPLAY_FMT));
Shashi Gowda's avatar
Shashi Gowda committed
986

987 988 989
            $m->connect(':nickname/peopletagsubscriptions',
                            array('action' => 'peopletagsubscriptions',
                                  'nickname' => Nickname::DISPLAY_FMT));
Shashi Gowda's avatar
Shashi Gowda committed
990

991 992 993 994
            $m->connect(':tagger/all/:tag/subscribers',
                            array('action' => 'peopletagsubscribers',
                                  'tagger' => Nickname::DISPLAY_FMT,
                                  'tag' => self::REGEX_TAG));
Shashi Gowda's avatar
Shashi Gowda committed
995

996 997 998 999
            $m->connect(':tagger/all/:tag/tagged',
                            array('action' => 'peopletagged',
                                  'tagger' => Nickname::DISPLAY_FMT,
                                  'tag' => self::REGEX_TAG));
Shashi Gowda's avatar
Shashi Gowda committed
1000

1001 1002 1003 1004
            $m->connect(':tagger/all/:tag/edit',
                            array('action' => 'editpeopletag',
                                  'tagger' => Nickname::DISPLAY_FMT,
                                  'tag' => self::REGEX_TAG));
Shashi Gowda's avatar
Shashi Gowda committed
1005

1006 1007 1008 1009 1010 1011 1012 1013 1014 1015
            foreach(array('subscribe', 'unsubscribe') as $v) {
                $m->connect('peopletag/:id/'.$v,
                                array('action' => $v.'peopletag',
                                      'id' => '[0-9]{1,64}'));
            }
            $m->connect('user/:tagger_id/profiletag/:id/id',
                            array('action' => 'profiletagbyid',
                                  'tagger_id' => '[0-9]+',
                                  'id' => '[0-9]+'));

1016 1017 1018
            $m->connect(':nickname/all/:tag',
                            array('action' => 'showprofiletag'),
                            array('nickname' => Nickname::DISPLAY_FMT,
1019
                                  'tag' => self::REGEX_TAG));
Evan Prodromou's avatar
Evan Prodromou committed
1020

1021 1022 1023 1024 1025 1026
            foreach (array('subscriptions', 'subscribers') as $a) {
                $m->connect(':nickname/'.$a.'/:tag',
                            array('action' => $a),
                            array('tag' => self::REGEX_TAG,
                                  'nickname' => Nickname::DISPLAY_FMT));
            }
Evan Prodromou's avatar
Evan Prodromou committed
1027

1028 1029 1030 1031 1032
            foreach (array('rss', 'groups') as $a) {
                $m->connect(':nickname/'.$a,
                            array('action' => 'user'.$a),
                            array('nickname' => Nickname::DISPLAY_FMT));
            }
Evan Prodromou's avatar
Evan Prodromou committed
1033

1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045
            $m->connect(':nickname/avatar',
                        array('action' => 'avatarbynickname'),
                        array('nickname' => Nickname::DISPLAY_FMT));
            $m->connect(':nickname/avatar/:size',
                        array('action' => 'avatarbynickname'),
                        array('size' => '(|original|\d+)',
                              'nickname' => Nickname::DISPLAY_FMT));

            $m->connect(':nickname/tag/:tag/rss',
                        array('action' => 'userrss'),
                        array('nickname' => Nickname::DISPLAY_FMT),
                        array('tag' => self::REGEX_TAG));
Evan Prodromou's avatar
Evan Prodromou committed
1046

1047 1048 1049 1050
            $m->connect(':nickname/tag/:tag',
                        array('action' => 'showstream'),
                        array('nickname' => Nickname::DISPLAY_FMT),
                        array('tag' => self::REGEX_TAG));
1051

1052 1053 1054
            $m->connect(':nickname/rsd.xml',
                        array('action' => 'rsd'),
                        array('nickname' => Nickname::DISPLAY_FMT));
1055

1056 1057 1058
            $m->connect(':nickname',
                        array('action' => 'showstream'),
                        array('nickname' => Nickname::DISPLAY_FMT));
1059

1060 1061 1062
            $m->connect(':nickname/',
                        array('action' => 'showstream'),
                        array('nickname' => Nickname::DISPLAY_FMT));
1063

1064 1065 1066
            // AtomPub API

            $m->connect('api/statusnet/app/service/:id.xml',
1067 1068
                        array('action' => 'ApiAtomService'),
                        array('id' => Nickname::DISPLAY_FMT));
1069 1070 1071 1072 1073 1074 1075 1076 1077

            $m->connect('api/statusnet/app/service.xml',
                        array('action' => 'ApiAtomService'));

            $m->connect('api/statusnet/app/subscriptions/:subscriber/:subscribed.atom',
                        array('action' => 'AtomPubShowSubscription'),
                        array('subscriber' => '[0-9]+',
                              'subscribed' => '[0-9]+'));

1078 1079 1080 1081
            $m->connect('api/statusnet/app/subscriptions/:subscriber.atom',
                        array('action' => 'AtomPubSubscriptionFeed'),
                        array('subscriber' => '[0-9]+'));

1082 1083 1084 1085 1086
            $m->connect('api/statusnet/app/memberships/:profile/:group.atom',
                        array('action' => 'AtomPubShowMembership'),
                        array('profile' => '[0-9]+',
                              'group' => '[0-9]+'));

1087
            $m->connect('api/statusnet/app/memberships/:profile.atom',
1088 1089 1090
                        array('action' => 'AtomPubMembershipFeed'),
                        array('profile' => '[0-9]+'));

1091 1092 1093 1094 1095 1096
            // URL shortening

            $m->connect('url/:id',
                        array('action' => 'redirecturl',
                              'id' => '[0-9]+'));

Evan Prodromou's avatar
Evan Prodromou committed
1097
            // user stuff
1098

1099 1100
            Event::handle('RouterInitialized', array($m));
        }
1101

1102
        return $m;
1103 1104 1105 1106
    }

    function map($path)
    {
1107
        try {
1108 1109 1110
            return $this->m->match($path);
        } catch (NoRouteMapException $e) {
            common_debug($e->getMessage());
1111
            // TRANS: Client error on action trying to visit a non-existing page.
1112
            throw new ClientException(_('Page not found.'), 404);
1113
        }
1114 1115
    }

1116
    function build($action, $args=null, $params=null, $fragment=null)
1117 1118 1119 1120
    {
        $action_arg = array('action' => $action);

        if ($args) {
Evan Prodromou's avatar
Evan Prodromou committed
1121
            $args = array_merge($action_arg, $args);
1122 1123 1124 1125
        } else {
            $args = $action_arg;
        }

1126
        $url = $this->m->generate($args, $params, $fragment);
1127 1128 1129
        // Due to a bug in the Net_URL_Mapper code, the returned URL may
        // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
        // repair that here rather than modifying the upstream code...
1130 1131

        $qpos = strpos($url, '?');
1132 1133
        if ($qpos !== false) {
            $url = substr($url, 0, $qpos+1) .
1134
                str_replace('?', '&', substr($url, $qpos+1));
1135 1136 1137 1138 1139 1140

            // @fixme this is a hacky workaround for http_build_query in the
            // lower-level code and bad configs that set the default separator
            // to &amp; instead of &. Encoded &s in parameters will not be
            // affected.
            $url = substr($url, 0, $qpos+1) .
1141
                str_replace('&amp;', '&', substr($url, $qpos+1));
1142

1143
        }
1144

1145
        return $url;
1146
    }
Adrian Lang's avatar
Adrian Lang committed
1147
}