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