]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/router.php
Merge branch '0.9.x' into atompub
[quix0rs-gnu-social.git] / lib / router.php
1 <?php
2 /**
3  * StatusNet, the distributed open-source microblogging tool
4  *
5  * URL routing utilities
6  *
7  * PHP version 5
8  *
9  * LICENCE: This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU Affero General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Affero General Public License for more details.
18  *
19  * You should have received a copy of the GNU Affero General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  * @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('STATUSNET') && !defined('LACONICA')) {
31     exit(1);
32 }
33
34 require_once 'Net/URL/Mapper.php';
35
36 class StatusNet_URL_Mapper extends Net_URL_Mapper {
37     private static $_singleton = null;
38
39     private function __construct()
40     {
41     }
42
43     public static function getInstance($id = '__default__')
44     {
45         if (empty(self::$_singleton)) {
46             self::$_singleton = new StatusNet_URL_Mapper();
47         }
48         return self::$_singleton;
49     }
50
51     public function connect($path, $defaults = array(), $rules = array())
52     {
53         $result = null;
54         if (Event::handle('StartConnectPath', array(&$path, &$defaults, &$rules, &$result))) {
55             $result = parent::connect($path, $defaults, $rules);
56             Event::handle('EndConnectPath', array($path, $defaults, $rules, $result));
57         }
58         return $result;
59     }
60 }
61
62 /**
63  * URL Router
64  *
65  * Cheap wrapper around Net_URL_Mapper
66  *
67  * @category URL
68  * @package  StatusNet
69  * @author   Evan Prodromou <evan@status.net>
70  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
71  * @link     http://status.net/
72  */
73 class Router
74 {
75     var $m = null;
76     static $inst = null;
77     static $bare = array('requesttoken', 'accesstoken', 'userauthorization',
78                          'postnotice', 'updateprofile', 'finishremotesubscribe');
79
80     static function get()
81     {
82         if (!Router::$inst) {
83             Router::$inst = new Router();
84         }
85         return Router::$inst;
86     }
87
88     function __construct()
89     {
90         if (!$this->m) {
91             $this->m = $this->initialize();
92         }
93     }
94
95     function initialize()
96     {
97         $m = StatusNet_URL_Mapper::getInstance();
98
99         if (Event::handle('StartInitializeRouter', array(&$m))) {
100
101             $m->connect('robots.txt', array('action' => 'robotstxt'));
102
103             $m->connect('opensearch/people', array('action' => 'opensearch',
104                                                    'type' => 'people'));
105             $m->connect('opensearch/notice', array('action' => 'opensearch',
106                                                    'type' => 'notice'));
107
108             // docs
109
110             $m->connect('doc/:title', array('action' => 'doc'));
111
112             $m->connect('main/otp/:user_id/:token',
113                         array('action' => 'otp'),
114                         array('user_id' => '[0-9]+',
115                               'token' => '.+'));
116
117             // main stuff is repetitive
118
119             $main = array('login', 'logout', 'register', 'subscribe',
120                           'unsubscribe', 'confirmaddress', 'recoverpassword',
121                           'invite', 'favor', 'disfavor', 'sup',
122                           'block', 'unblock', 'subedit',
123                           'groupblock', 'groupunblock',
124                           'sandbox', 'unsandbox',
125                           'silence', 'unsilence',
126                           'grantrole', 'revokerole',
127                           'repeat',
128                           'deleteuser',
129                           'geocode',
130                           'version',
131                           );
132
133             foreach ($main as $a) {
134                 $m->connect('main/'.$a, array('action' => $a));
135             }
136
137             // Also need a block variant accepting ID on URL for mail links
138             $m->connect('main/block/:profileid',
139                         array('action' => 'block'),
140                         array('profileid' => '[0-9]+'));
141
142             $m->connect('main/sup/:seconds', array('action' => 'sup'),
143                         array('seconds' => '[0-9]+'));
144
145             $m->connect('main/tagother/:id', array('action' => 'tagother'));
146
147             $m->connect('main/oembed',
148                         array('action' => 'oembed'));
149
150             $m->connect('main/xrds',
151                         array('action' => 'publicxrds'));
152
153             // these take a code
154
155             foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) {
156                 $m->connect('main/'.$c.'/:code', array('action' => $c));
157             }
158
159             // exceptional
160
161             $m->connect('main/remote', array('action' => 'remotesubscribe'));
162             $m->connect('main/remote?nickname=:nickname', array('action' => 'remotesubscribe'), array('nickname' => '[A-Za-z0-9_-]+'));
163
164             foreach (Router::$bare as $action) {
165                 $m->connect('index.php?action=' . $action, array('action' => $action));
166             }
167
168             // settings
169
170             foreach (array('profile', 'avatar', 'password', 'im', 'oauthconnections',
171                            'oauthapps', 'email', 'sms', 'userdesign', 'other') as $s) {
172                 $m->connect('settings/'.$s, array('action' => $s.'settings'));
173             }
174
175             $m->connect('settings/oauthapps/show/:id',
176                 array('action' => 'showapplication'),
177                 array('id' => '[0-9]+')
178             );
179             $m->connect('settings/oauthapps/new',
180                 array('action' => 'newapplication')
181             );
182             $m->connect('settings/oauthapps/edit/:id',
183                 array('action' => 'editapplication'),
184                 array('id' => '[0-9]+')
185             );
186             $m->connect('settings/oauthapps/delete/:id',
187                 array('action' => 'deleteapplication'),
188                 array('id' => '[0-9]+')
189             );
190
191             // search
192
193             foreach (array('group', 'people', 'notice') as $s) {
194                 $m->connect('search/'.$s, array('action' => $s.'search'));
195                 $m->connect('search/'.$s.'?q=:q',
196                             array('action' => $s.'search'),
197                             array('q' => '.+'));
198             }
199
200             // The second of these is needed to make the link work correctly
201             // when inserted into the page. The first is needed to match the
202             // route on the way in. Seems to be another Net_URL_Mapper bug to me.
203             $m->connect('search/notice/rss', array('action' => 'noticesearchrss'));
204             $m->connect('search/notice/rss?q=:q', array('action' => 'noticesearchrss'),
205                         array('q' => '.+'));
206
207             $m->connect('attachment/:attachment',
208                         array('action' => 'attachment'),
209                         array('attachment' => '[0-9]+'));
210
211             $m->connect('attachment/:attachment/ajax',
212                         array('action' => 'attachment_ajax'),
213                         array('attachment' => '[0-9]+'));
214
215             $m->connect('attachment/:attachment/thumbnail',
216                         array('action' => 'attachment_thumbnail'),
217                         array('attachment' => '[0-9]+'));
218
219             $m->connect('notice/new', array('action' => 'newnotice'));
220             $m->connect('notice/new?replyto=:replyto',
221                         array('action' => 'newnotice'),
222                         array('replyto' => '[A-Za-z0-9_-]+'));
223             $m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
224                         array('action' => 'newnotice'),
225                         array('replyto' => '[A-Za-z0-9_-]+'),
226                         array('inreplyto' => '[0-9]+'));
227
228             $m->connect('notice/:notice/file',
229                         array('action' => 'file'),
230                         array('notice' => '[0-9]+'));
231
232             $m->connect('notice/:notice',
233                         array('action' => 'shownotice'),
234                         array('notice' => '[0-9]+'));
235             $m->connect('notice/delete', array('action' => 'deletenotice'));
236             $m->connect('notice/delete/:notice',
237                         array('action' => 'deletenotice'),
238                         array('notice' => '[0-9]+'));
239
240             $m->connect('bookmarklet/new', array('action' => 'bookmarklet'));
241
242             // conversation
243
244             $m->connect('conversation/:id',
245                         array('action' => 'conversation'),
246                         array('id' => '[0-9]+'));
247
248             $m->connect('message/new', array('action' => 'newmessage'));
249             $m->connect('message/new?to=:to', array('action' => 'newmessage'), array('to' => '[A-Za-z0-9_-]+'));
250             $m->connect('message/:message',
251                         array('action' => 'showmessage'),
252                         array('message' => '[0-9]+'));
253
254             $m->connect('user/:id',
255                         array('action' => 'userbyid'),
256                         array('id' => '[0-9]+'));
257
258             $m->connect('tags/', array('action' => 'publictagcloud'));
259             $m->connect('tag/', array('action' => 'publictagcloud'));
260             $m->connect('tags', array('action' => 'publictagcloud'));
261             $m->connect('tag', array('action' => 'publictagcloud'));
262             $m->connect('tag/:tag/rss',
263                         array('action' => 'tagrss'),
264                         array('tag' => '[\pL\pN_\-\.]{1,64}'));
265             $m->connect('tag/:tag',
266                         array('action' => 'tag'),
267                         array('tag' => '[\pL\pN_\-\.]{1,64}'));
268
269             $m->connect('peopletag/:tag',
270                         array('action' => 'peopletag'),
271                         array('tag' => '[a-zA-Z0-9]+'));
272
273             // groups
274
275             $m->connect('group/new', array('action' => 'newgroup'));
276
277             foreach (array('edit', 'join', 'leave', 'delete') as $v) {
278                 $m->connect('group/:nickname/'.$v,
279                             array('action' => $v.'group'),
280                             array('nickname' => '[a-zA-Z0-9]+'));
281                 $m->connect('group/:id/id/'.$v,
282                             array('action' => $v.'group'),
283                             array('id' => '[0-9]+'));
284             }
285
286             foreach (array('members', 'logo', 'rss', 'designsettings') as $n) {
287                 $m->connect('group/:nickname/'.$n,
288                             array('action' => 'group'.$n),
289                             array('nickname' => '[a-zA-Z0-9]+'));
290             }
291
292             $m->connect('group/:nickname/foaf',
293                         array('action' => 'foafgroup'),
294                         array('nickname' => '[a-zA-Z0-9]+'));
295
296             $m->connect('group/:nickname/blocked',
297                         array('action' => 'blockedfromgroup'),
298                         array('nickname' => '[a-zA-Z0-9]+'));
299
300             $m->connect('group/:nickname/makeadmin',
301                         array('action' => 'makeadmin'),
302                         array('nickname' => '[a-zA-Z0-9]+'));
303
304             $m->connect('group/:id/id',
305                         array('action' => 'groupbyid'),
306                         array('id' => '[0-9]+'));
307
308             $m->connect('group/:nickname',
309                         array('action' => 'showgroup'),
310                         array('nickname' => '[a-zA-Z0-9]+'));
311
312             $m->connect('group/', array('action' => 'groups'));
313             $m->connect('group', array('action' => 'groups'));
314             $m->connect('groups/', array('action' => 'groups'));
315             $m->connect('groups', array('action' => 'groups'));
316
317             // Twitter-compatible API
318
319             // statuses API
320
321             $m->connect('api/statuses/public_timeline.:format',
322                         array('action' => 'ApiTimelinePublic',
323                               'format' => '(xml|json|rss|atom)'));
324
325             $m->connect('api/statuses/friends_timeline.:format',
326                         array('action' => 'ApiTimelineFriends',
327                               'format' => '(xml|json|rss|atom)'));
328
329             $m->connect('api/statuses/friends_timeline/:id.:format',
330                         array('action' => 'ApiTimelineFriends',
331                               'id' => '[a-zA-Z0-9]+',
332                               'format' => '(xml|json|rss|atom)'));
333
334             $m->connect('api/statuses/home_timeline.:format',
335                         array('action' => 'ApiTimelineHome',
336                               'format' => '(xml|json|rss|atom)'));
337
338             $m->connect('api/statuses/home_timeline/:id.:format',
339                         array('action' => 'ApiTimelineHome',
340                               'id' => '[a-zA-Z0-9]+',
341                               'format' => '(xml|json|rss|atom)'));
342
343             $m->connect('api/statuses/user_timeline.:format',
344                         array('action' => 'ApiTimelineUser',
345                               'format' => '(xml|json|rss|atom)'));
346
347             $m->connect('api/statuses/user_timeline/:id.:format',
348                         array('action' => 'ApiTimelineUser',
349                               'id' => '[a-zA-Z0-9]+',
350                               'format' => '(xml|json|rss|atom)'));
351
352             $m->connect('api/statuses/mentions.:format',
353                         array('action' => 'ApiTimelineMentions',
354                               'format' => '(xml|json|rss|atom)'));
355
356             $m->connect('api/statuses/mentions/:id.:format',
357                         array('action' => 'ApiTimelineMentions',
358                               'id' => '[a-zA-Z0-9]+',
359                               'format' => '(xml|json|rss|atom)'));
360
361             $m->connect('api/statuses/replies.:format',
362                         array('action' => 'ApiTimelineMentions',
363                               'format' => '(xml|json|rss|atom)'));
364
365             $m->connect('api/statuses/replies/:id.:format',
366                         array('action' => 'ApiTimelineMentions',
367                               'id' => '[a-zA-Z0-9]+',
368                               'format' => '(xml|json|rss|atom)'));
369
370             $m->connect('api/statuses/retweeted_by_me.:format',
371                         array('action' => 'ApiTimelineRetweetedByMe',
372                               'format' => '(xml|json|atom)'));
373
374             $m->connect('api/statuses/retweeted_to_me.:format',
375                         array('action' => 'ApiTimelineRetweetedToMe',
376                               'format' => '(xml|json|atom)'));
377
378             $m->connect('api/statuses/retweets_of_me.:format',
379                         array('action' => 'ApiTimelineRetweetsOfMe',
380                               'format' => '(xml|json|atom)'));
381
382             $m->connect('api/statuses/friends.:format',
383                         array('action' => 'ApiUserFriends',
384                               'format' => '(xml|json)'));
385
386             $m->connect('api/statuses/friends/:id.:format',
387                         array('action' => 'ApiUserFriends',
388                               'id' => '[a-zA-Z0-9]+',
389                               'format' => '(xml|json)'));
390
391             $m->connect('api/statuses/followers.:format',
392                         array('action' => 'ApiUserFollowers',
393                               'format' => '(xml|json)'));
394
395             $m->connect('api/statuses/followers/:id.:format',
396                         array('action' => 'ApiUserFollowers',
397                               'id' => '[a-zA-Z0-9]+',
398                               'format' => '(xml|json)'));
399
400             $m->connect('api/statuses/show.:format',
401                         array('action' => 'ApiStatusesShow',
402                               'format' => '(xml|json|atom)'));
403
404             $m->connect('api/statuses/show/:id.:format',
405                         array('action' => 'ApiStatusesShow',
406                               'id' => '[0-9]+',
407                               'format' => '(xml|json|atom)'));
408
409             $m->connect('api/statuses/update.:format',
410                         array('action' => 'ApiStatusesUpdate',
411                               'format' => '(xml|json)'));
412
413             $m->connect('api/statuses/destroy.:format',
414                         array('action' => 'ApiStatusesDestroy',
415                               'format' => '(xml|json)'));
416
417             $m->connect('api/statuses/destroy/:id.:format',
418                         array('action' => 'ApiStatusesDestroy',
419                               'id' => '[0-9]+',
420                               'format' => '(xml|json)'));
421
422             $m->connect('api/statuses/retweet/:id.:format',
423                         array('action' => 'ApiStatusesRetweet',
424                               'id' => '[0-9]+',
425                               'format' => '(xml|json)'));
426
427             $m->connect('api/statuses/retweets/:id.:format',
428                         array('action' => 'ApiStatusesRetweets',
429                               'id' => '[0-9]+',
430                               'format' => '(xml|json)'));
431
432             // users
433
434             $m->connect('api/users/show.:format',
435                         array('action' => 'ApiUserShow',
436                               'format' => '(xml|json)'));
437
438             $m->connect('api/users/show/:id.:format',
439                         array('action' => 'ApiUserShow',
440                               'id' => '[a-zA-Z0-9]+',
441                               'format' => '(xml|json)'));
442
443             // direct messages
444
445             $m->connect('api/direct_messages.:format',
446                         array('action' => 'ApiDirectMessage',
447                               'format' => '(xml|json|rss|atom)'));
448
449             $m->connect('api/direct_messages/sent.:format',
450                         array('action' => 'ApiDirectMessage',
451                               'format' => '(xml|json|rss|atom)',
452                               'sent' => true));
453
454             $m->connect('api/direct_messages/new.:format',
455                         array('action' => 'ApiDirectMessageNew',
456                               'format' => '(xml|json)'));
457
458             // friendships
459
460             $m->connect('api/friendships/show.:format',
461                         array('action' => 'ApiFriendshipsShow',
462                               'format' => '(xml|json)'));
463
464             $m->connect('api/friendships/exists.:format',
465                         array('action' => 'ApiFriendshipsExists',
466                               'format' => '(xml|json)'));
467
468             $m->connect('api/friendships/create.:format',
469                         array('action' => 'ApiFriendshipsCreate',
470                               'format' => '(xml|json)'));
471
472             $m->connect('api/friendships/destroy.:format',
473                         array('action' => 'ApiFriendshipsDestroy',
474                               'format' => '(xml|json)'));
475
476             $m->connect('api/friendships/create/:id.:format',
477                         array('action' => 'ApiFriendshipsCreate',
478                               'id' => '[a-zA-Z0-9]+',
479                               'format' => '(xml|json)'));
480
481             $m->connect('api/friendships/destroy/:id.:format',
482                         array('action' => 'ApiFriendshipsDestroy',
483                               'id' => '[a-zA-Z0-9]+',
484                               'format' => '(xml|json)'));
485
486             // Social graph
487
488             $m->connect('api/friends/ids/:id.:format',
489                         array('action' => 'ApiUserFriends',
490                               'ids_only' => true));
491
492             $m->connect('api/followers/ids/:id.:format',
493                         array('action' => 'ApiUserFollowers',
494                               'ids_only' => true));
495
496             $m->connect('api/friends/ids.:format',
497                         array('action' => 'ApiUserFriends',
498                               'ids_only' => true));
499
500             $m->connect('api/followers/ids.:format',
501                         array('action' => 'ApiUserFollowers',
502                               'ids_only' => true));
503
504             // account
505
506             $m->connect('api/account/verify_credentials.:format',
507                         array('action' => 'ApiAccountVerifyCredentials'));
508
509             $m->connect('api/account/update_profile.:format',
510                         array('action' => 'ApiAccountUpdateProfile'));
511
512             $m->connect('api/account/update_profile_image.:format',
513                         array('action' => 'ApiAccountUpdateProfileImage'));
514
515             $m->connect('api/account/update_profile_background_image.:format',
516                         array('action' => 'ApiAccountUpdateProfileBackgroundImage'));
517
518             $m->connect('api/account/update_profile_colors.:format',
519                         array('action' => 'ApiAccountUpdateProfileColors'));
520
521             $m->connect('api/account/update_delivery_device.:format',
522                         array('action' => 'ApiAccountUpdateDeliveryDevice'));
523
524             // special case where verify_credentials is called w/out a format
525
526             $m->connect('api/account/verify_credentials',
527                         array('action' => 'ApiAccountVerifyCredentials'));
528
529             $m->connect('api/account/rate_limit_status.:format',
530                         array('action' => 'ApiAccountRateLimitStatus'));
531
532             // favorites
533
534             $m->connect('api/favorites.:format',
535                         array('action' => 'ApiTimelineFavorites',
536                               'format' => '(xml|json|rss|atom)'));
537
538             $m->connect('api/favorites/:id.:format',
539                         array('action' => 'ApiTimelineFavorites',
540                               'id' => '[a-zA-Z0-9]+',
541                               'format' => '(xml|json|rss|atom)'));
542
543             $m->connect('api/favorites/create/:id.:format',
544                         array('action' => 'ApiFavoriteCreate',
545                               'id' => '[a-zA-Z0-9]+',
546                               'format' => '(xml|json)'));
547
548             $m->connect('api/favorites/destroy/:id.:format',
549                         array('action' => 'ApiFavoriteDestroy',
550                               'id' => '[a-zA-Z0-9]+',
551                               'format' => '(xml|json)'));
552             // blocks
553
554             $m->connect('api/blocks/create.:format',
555                         array('action' => 'ApiBlockCreate',
556                               'format' => '(xml|json)'));
557
558             $m->connect('api/blocks/create/:id.:format',
559                         array('action' => 'ApiBlockCreate',
560                               'id' => '[a-zA-Z0-9]+',
561                               'format' => '(xml|json)'));
562
563             $m->connect('api/blocks/destroy.:format',
564                         array('action' => 'ApiBlockDestroy',
565                               'format' => '(xml|json)'));
566
567             $m->connect('api/blocks/destroy/:id.:format',
568                         array('action' => 'ApiBlockDestroy',
569                               'id' => '[a-zA-Z0-9]+',
570                               'format' => '(xml|json)'));
571             // help
572
573             $m->connect('api/help/test.:format',
574                         array('action' => 'ApiHelpTest',
575                               'format' => '(xml|json)'));
576
577             // statusnet
578
579             $m->connect('api/statusnet/version.:format',
580                         array('action' => 'ApiStatusnetVersion',
581                               'format' => '(xml|json)'));
582
583             $m->connect('api/statusnet/config.:format',
584                         array('action' => 'ApiStatusnetConfig',
585                               'format' => '(xml|json)'));
586
587             // For older methods, we provide "laconica" base action
588
589             $m->connect('api/laconica/version.:format',
590                         array('action' => 'ApiStatusnetVersion',
591                               'format' => '(xml|json)'));
592
593             $m->connect('api/laconica/config.:format',
594                         array('action' => 'ApiStatusnetConfig',
595                               'format' => '(xml|json)'));
596
597             // Groups and tags are newer than 0.8.1 so no backward-compatibility
598             // necessary
599
600             // Groups
601             //'list' has to be handled differently, as php will not allow a method to be named 'list'
602
603             $m->connect('api/statusnet/groups/timeline/:id.:format',
604                         array('action' => 'ApiTimelineGroup',
605                               'id' => '[a-zA-Z0-9]+',
606                               'format' => '(xml|json|rss|atom)'));
607
608             $m->connect('api/statusnet/groups/show.:format',
609                         array('action' => 'ApiGroupShow',
610                               'format' => '(xml|json)'));
611
612             $m->connect('api/statusnet/groups/show/:id.:format',
613                         array('action' => 'ApiGroupShow',
614                               'id' => '[a-zA-Z0-9]+',
615                               'format' => '(xml|json)'));
616
617             $m->connect('api/statusnet/groups/join.:format',
618                         array('action' => 'ApiGroupJoin',
619                               'id' => '[a-zA-Z0-9]+',
620                               'format' => '(xml|json)'));
621
622             $m->connect('api/statusnet/groups/join/:id.:format',
623                         array('action' => 'ApiGroupJoin',
624                               'format' => '(xml|json)'));
625
626             $m->connect('api/statusnet/groups/leave.:format',
627                         array('action' => 'ApiGroupLeave',
628                               'id' => '[a-zA-Z0-9]+',
629                               'format' => '(xml|json)'));
630
631             $m->connect('api/statusnet/groups/leave/:id.:format',
632                         array('action' => 'ApiGroupLeave',
633                               'format' => '(xml|json)'));
634
635             $m->connect('api/statusnet/groups/is_member.:format',
636                         array('action' => 'ApiGroupIsMember',
637                               'format' => '(xml|json)'));
638
639             $m->connect('api/statusnet/groups/list.:format',
640                         array('action' => 'ApiGroupList',
641                               'format' => '(xml|json|rss|atom)'));
642
643             $m->connect('api/statusnet/groups/list/:id.:format',
644                         array('action' => 'ApiGroupList',
645                               'id' => '[a-zA-Z0-9]+',
646                               'format' => '(xml|json|rss|atom)'));
647
648             $m->connect('api/statusnet/groups/list_all.:format',
649                         array('action' => 'ApiGroupListAll',
650                               'format' => '(xml|json|rss|atom)'));
651
652             $m->connect('api/statusnet/groups/membership.:format',
653                         array('action' => 'ApiGroupMembership',
654                               'format' => '(xml|json)'));
655
656             $m->connect('api/statusnet/groups/membership/:id.:format',
657                         array('action' => 'ApiGroupMembership',
658                               'id' => '[a-zA-Z0-9]+',
659                               'format' => '(xml|json)'));
660
661             $m->connect('api/statusnet/groups/create.:format',
662                         array('action' => 'ApiGroupCreate',
663                               'format' => '(xml|json)'));
664             // Tags
665             $m->connect('api/statusnet/tags/timeline/:tag.:format',
666                         array('action' => 'ApiTimelineTag',
667                               'format' => '(xml|json|rss|atom)'));
668
669             // media related
670             $m->connect(
671                 'api/statusnet/media/upload',
672                 array('action' => 'ApiMediaUpload')
673             );
674
675             // search
676             $m->connect('api/search.atom', array('action' => 'ApiSearchAtom'));
677             $m->connect('api/search.json', array('action' => 'ApiSearchJSON'));
678             $m->connect('api/trends.json', array('action' => 'ApiTrends'));
679
680             $m->connect('api/oauth/request_token',
681                         array('action' => 'ApiOauthRequestToken'));
682
683             $m->connect('api/oauth/access_token',
684                         array('action' => 'ApiOauthAccessToken'));
685
686             $m->connect('api/oauth/authorize',
687                         array('action' => 'ApiOauthAuthorize'));
688
689             $m->connect('api/statusnet/app/service/:id.xml',
690                         array('action' => 'ApiAtomService',
691                               'id' => '[a-zA-Z0-9]+'));
692
693             $m->connect('api/statusnet/app/service.xml',
694                         array('action' => 'ApiAtomService'));
695
696             // Admin
697
698             $m->connect('admin/site', array('action' => 'siteadminpanel'));
699             $m->connect('admin/design', array('action' => 'designadminpanel'));
700             $m->connect('admin/user', array('action' => 'useradminpanel'));
701                 $m->connect('admin/access', array('action' => 'accessadminpanel'));
702             $m->connect('admin/paths', array('action' => 'pathsadminpanel'));
703             $m->connect('admin/sessions', array('action' => 'sessionsadminpanel'));
704             $m->connect('admin/sitenotice', array('action' => 'sitenoticeadminpanel'));
705             $m->connect('admin/snapshot', array('action' => 'snapshotadminpanel'));
706             $m->connect('admin/license', array('action' => 'licenseadminpanel'));
707
708             $m->connect('getfile/:filename',
709                         array('action' => 'getfile'),
710                         array('filename' => '[A-Za-z0-9._-]+'));
711
712             // In the "root"
713
714             if (common_config('singleuser', 'enabled')) {
715
716                 $user = User::singleUser();
717                 $nickname = $user->nickname;
718
719                 foreach (array('subscriptions', 'subscribers',
720                                'all', 'foaf', 'xrds',
721                                'replies', 'microsummary', 'hcard') as $a) {
722                     $m->connect($a,
723                                 array('action' => $a,
724                                       'nickname' => $nickname));
725                 }
726
727                 foreach (array('subscriptions', 'subscribers') as $a) {
728                     $m->connect($a.'/:tag',
729                                 array('action' => $a,
730                                       'nickname' => $nickname),
731                                 array('tag' => '[a-zA-Z0-9]+'));
732                 }
733
734                 foreach (array('rss', 'groups') as $a) {
735                     $m->connect($a,
736                                 array('action' => 'user'.$a,
737                                       'nickname' => $nickname));
738                 }
739
740                 foreach (array('all', 'replies', 'favorites') as $a) {
741                     $m->connect($a.'/rss',
742                                 array('action' => $a.'rss',
743                                       'nickname' => $nickname));
744                 }
745
746                 $m->connect('favorites',
747                             array('action' => 'showfavorites',
748                                   'nickname' => $nickname));
749
750                 $m->connect('avatar/:size',
751                             array('action' => 'avatarbynickname',
752                                   'nickname' => $nickname),
753                             array('size' => '(original|96|48|24)'));
754
755                 $m->connect('tag/:tag/rss',
756                             array('action' => 'userrss',
757                                   'nickname' => $nickname),
758                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
759
760                 $m->connect('tag/:tag',
761                             array('action' => 'showstream',
762                                   'nickname' => $nickname),
763                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
764
765                 $m->connect('rsd.xml',
766                             array('action' => 'rsd',
767                                   'nickname' => $nickname));
768
769                 $m->connect('',
770                             array('action' => 'showstream',
771                                   'nickname' => $nickname));
772             } else {
773                 $m->connect('', array('action' => 'public'));
774                 $m->connect('rss', array('action' => 'publicrss'));
775                 $m->connect('featuredrss', array('action' => 'featuredrss'));
776                 $m->connect('favoritedrss', array('action' => 'favoritedrss'));
777                 $m->connect('featured/', array('action' => 'featured'));
778                 $m->connect('featured', array('action' => 'featured'));
779                 $m->connect('favorited/', array('action' => 'favorited'));
780                 $m->connect('favorited', array('action' => 'favorited'));
781                 $m->connect('rsd.xml', array('action' => 'rsd'));
782
783                 foreach (array('subscriptions', 'subscribers',
784                                'nudge', 'all', 'foaf', 'xrds',
785                                'replies', 'inbox', 'outbox', 'microsummary', 'hcard') as $a) {
786                     $m->connect(':nickname/'.$a,
787                                 array('action' => $a),
788                                 array('nickname' => '[a-zA-Z0-9]{1,64}'));
789                 }
790
791                 foreach (array('subscriptions', 'subscribers') as $a) {
792                     $m->connect(':nickname/'.$a.'/:tag',
793                                 array('action' => $a),
794                                 array('tag' => '[a-zA-Z0-9]+',
795                                       'nickname' => '[a-zA-Z0-9]{1,64}'));
796                 }
797
798                 foreach (array('rss', 'groups') as $a) {
799                     $m->connect(':nickname/'.$a,
800                                 array('action' => 'user'.$a),
801                                 array('nickname' => '[a-zA-Z0-9]{1,64}'));
802                 }
803
804                 foreach (array('all', 'replies', 'favorites') as $a) {
805                     $m->connect(':nickname/'.$a.'/rss',
806                                 array('action' => $a.'rss'),
807                                 array('nickname' => '[a-zA-Z0-9]{1,64}'));
808                 }
809
810                 $m->connect(':nickname/favorites',
811                             array('action' => 'showfavorites'),
812                             array('nickname' => '[a-zA-Z0-9]{1,64}'));
813
814                 $m->connect(':nickname/avatar/:size',
815                             array('action' => 'avatarbynickname'),
816                             array('size' => '(original|96|48|24)',
817                                   'nickname' => '[a-zA-Z0-9]{1,64}'));
818
819                 $m->connect(':nickname/tag/:tag/rss',
820                             array('action' => 'userrss'),
821                             array('nickname' => '[a-zA-Z0-9]{1,64}'),
822                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
823
824                 $m->connect(':nickname/tag/:tag',
825                             array('action' => 'showstream'),
826                             array('nickname' => '[a-zA-Z0-9]{1,64}'),
827                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
828
829                 $m->connect(':nickname/rsd.xml',
830                             array('action' => 'rsd'),
831                             array('nickname' => '[a-zA-Z0-9]{1,64}'));
832
833                 $m->connect(':nickname',
834                             array('action' => 'showstream'),
835                             array('nickname' => '[a-zA-Z0-9]{1,64}'));
836             }
837
838             // user stuff
839
840             Event::handle('RouterInitialized', array($m));
841         }
842
843         return $m;
844     }
845
846     function map($path)
847     {
848         try {
849             $match = $this->m->match($path);
850         } catch (Net_URL_Mapper_InvalidException $e) {
851             common_log(LOG_ERR, "Problem getting route for $path - " .
852                        $e->getMessage());
853             // TRANS: Client error on action trying to visit a non-existing page.
854             $cac = new ClientErrorAction(_('Page not found.'), 404);
855             $cac->showPage();
856         }
857
858         return $match;
859     }
860
861     function build($action, $args=null, $params=null, $fragment=null)
862     {
863         $action_arg = array('action' => $action);
864
865         if ($args) {
866             $args = array_merge($action_arg, $args);
867         } else {
868             $args = $action_arg;
869         }
870
871         $url = $this->m->generate($args, $params, $fragment);
872
873         // Due to a bug in the Net_URL_Mapper code, the returned URL may
874         // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
875         // repair that here rather than modifying the upstream code...
876
877         $qpos = strpos($url, '?');
878         if ($qpos !== false) {
879             $url = substr($url, 0, $qpos+1) .
880               str_replace('?', '&', substr($url, $qpos+1));
881
882             // @fixme this is a hacky workaround for http_build_query in the
883             // lower-level code and bad configs that set the default separator
884             // to &amp; instead of &. Encoded &s in parameters will not be
885             // affected.
886             $url = substr($url, 0, $qpos+1) .
887               str_replace('&amp;', '&', substr($url, $qpos+1));
888
889         }
890
891         return $url;
892     }
893 }