]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/router.php
Merge branch 'nofollowexternallink' into 0.9.x
[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)'));
403
404             $m->connect('api/statuses/show/:id.:format',
405                         array('action' => 'ApiStatusesShow',
406                               'id' => '[0-9]+',
407                               'format' => '(xml|json)'));
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             // Admin
690
691             $m->connect('admin/site', array('action' => 'siteadminpanel'));
692             $m->connect('admin/design', array('action' => 'designadminpanel'));
693             $m->connect('admin/user', array('action' => 'useradminpanel'));
694                 $m->connect('admin/access', array('action' => 'accessadminpanel'));
695             $m->connect('admin/paths', array('action' => 'pathsadminpanel'));
696             $m->connect('admin/sessions', array('action' => 'sessionsadminpanel'));
697             $m->connect('admin/sitenotice', array('action' => 'sitenoticeadminpanel'));
698             $m->connect('admin/snapshot', array('action' => 'snapshotadminpanel'));
699             $m->connect('admin/license', array('action' => 'licenseadminpanel'));
700
701             $m->connect('getfile/:filename',
702                         array('action' => 'getfile'),
703                         array('filename' => '[A-Za-z0-9._-]+'));
704
705             // In the "root"
706
707             if (common_config('singleuser', 'enabled')) {
708
709                 $user = User::singleUser();
710                 $nickname = $user->nickname;
711
712                 foreach (array('subscriptions', 'subscribers',
713                                'all', 'foaf', 'xrds',
714                                'replies', 'microsummary', 'hcard') as $a) {
715                     $m->connect($a,
716                                 array('action' => $a,
717                                       'nickname' => $nickname));
718                 }
719
720                 foreach (array('subscriptions', 'subscribers') as $a) {
721                     $m->connect($a.'/:tag',
722                                 array('action' => $a,
723                                       'nickname' => $nickname),
724                                 array('tag' => '[a-zA-Z0-9]+'));
725                 }
726
727                 foreach (array('rss', 'groups') as $a) {
728                     $m->connect($a,
729                                 array('action' => 'user'.$a,
730                                       'nickname' => $nickname));
731                 }
732
733                 foreach (array('all', 'replies', 'favorites') as $a) {
734                     $m->connect($a.'/rss',
735                                 array('action' => $a.'rss',
736                                       'nickname' => $nickname));
737                 }
738
739                 $m->connect('favorites',
740                             array('action' => 'showfavorites',
741                                   'nickname' => $nickname));
742
743                 $m->connect('avatar/:size',
744                             array('action' => 'avatarbynickname',
745                                   'nickname' => $nickname),
746                             array('size' => '(original|96|48|24)'));
747
748                 $m->connect('tag/:tag/rss',
749                             array('action' => 'userrss',
750                                   'nickname' => $nickname),
751                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
752
753                 $m->connect('tag/:tag',
754                             array('action' => 'showstream',
755                                   'nickname' => $nickname),
756                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
757
758                 $m->connect('rsd.xml',
759                             array('action' => 'rsd',
760                                   'nickname' => $nickname));
761
762                 $m->connect('',
763                             array('action' => 'showstream',
764                                   'nickname' => $nickname));
765             } else {
766                 $m->connect('', array('action' => 'public'));
767                 $m->connect('rss', array('action' => 'publicrss'));
768                 $m->connect('featuredrss', array('action' => 'featuredrss'));
769                 $m->connect('favoritedrss', array('action' => 'favoritedrss'));
770                 $m->connect('featured/', array('action' => 'featured'));
771                 $m->connect('featured', array('action' => 'featured'));
772                 $m->connect('favorited/', array('action' => 'favorited'));
773                 $m->connect('favorited', array('action' => 'favorited'));
774                 $m->connect('rsd.xml', array('action' => 'rsd'));
775
776                 foreach (array('subscriptions', 'subscribers',
777                                'nudge', 'all', 'foaf', 'xrds',
778                                'replies', 'inbox', 'outbox', 'microsummary', 'hcard') as $a) {
779                     $m->connect(':nickname/'.$a,
780                                 array('action' => $a),
781                                 array('nickname' => '[a-zA-Z0-9]{1,64}'));
782                 }
783
784                 foreach (array('subscriptions', 'subscribers') as $a) {
785                     $m->connect(':nickname/'.$a.'/:tag',
786                                 array('action' => $a),
787                                 array('tag' => '[a-zA-Z0-9]+',
788                                       'nickname' => '[a-zA-Z0-9]{1,64}'));
789                 }
790
791                 foreach (array('rss', 'groups') as $a) {
792                     $m->connect(':nickname/'.$a,
793                                 array('action' => 'user'.$a),
794                                 array('nickname' => '[a-zA-Z0-9]{1,64}'));
795                 }
796
797                 foreach (array('all', 'replies', 'favorites') as $a) {
798                     $m->connect(':nickname/'.$a.'/rss',
799                                 array('action' => $a.'rss'),
800                                 array('nickname' => '[a-zA-Z0-9]{1,64}'));
801                 }
802
803                 $m->connect(':nickname/favorites',
804                             array('action' => 'showfavorites'),
805                             array('nickname' => '[a-zA-Z0-9]{1,64}'));
806
807                 $m->connect(':nickname/avatar/:size',
808                             array('action' => 'avatarbynickname'),
809                             array('size' => '(original|96|48|24)',
810                                   'nickname' => '[a-zA-Z0-9]{1,64}'));
811
812                 $m->connect(':nickname/tag/:tag/rss',
813                             array('action' => 'userrss'),
814                             array('nickname' => '[a-zA-Z0-9]{1,64}'),
815                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
816
817                 $m->connect(':nickname/tag/:tag',
818                             array('action' => 'showstream'),
819                             array('nickname' => '[a-zA-Z0-9]{1,64}'),
820                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
821
822                 $m->connect(':nickname/rsd.xml',
823                             array('action' => 'rsd'),
824                             array('nickname' => '[a-zA-Z0-9]{1,64}'));
825
826                 $m->connect(':nickname',
827                             array('action' => 'showstream'),
828                             array('nickname' => '[a-zA-Z0-9]{1,64}'));
829             }
830
831             // user stuff
832
833             Event::handle('RouterInitialized', array($m));
834         }
835
836         return $m;
837     }
838
839     function map($path)
840     {
841         try {
842             $match = $this->m->match($path);
843         } catch (Net_URL_Mapper_InvalidException $e) {
844             common_log(LOG_ERR, "Problem getting route for $path - " .
845                        $e->getMessage());
846             // TRANS: Client error on action trying to visit a non-existing page.
847             $cac = new ClientErrorAction(_('Page not found.'), 404);
848             $cac->showPage();
849         }
850
851         return $match;
852     }
853
854     function build($action, $args=null, $params=null, $fragment=null)
855     {
856         $action_arg = array('action' => $action);
857
858         if ($args) {
859             $args = array_merge($action_arg, $args);
860         } else {
861             $args = $action_arg;
862         }
863
864         $url = $this->m->generate($args, $params, $fragment);
865
866         // Due to a bug in the Net_URL_Mapper code, the returned URL may
867         // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
868         // repair that here rather than modifying the upstream code...
869
870         $qpos = strpos($url, '?');
871         if ($qpos !== false) {
872             $url = substr($url, 0, $qpos+1) .
873               str_replace('?', '&', substr($url, $qpos+1));
874
875             // @fixme this is a hacky workaround for http_build_query in the
876             // lower-level code and bad configs that set the default separator
877             // to &amp; instead of &. Encoded &s in parameters will not be
878             // affected.
879             $url = substr($url, 0, $qpos+1) .
880               str_replace('&amp;', '&', substr($url, $qpos+1));
881
882         }
883
884         return $url;
885     }
886 }