]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/router.php
3bbb4a044e5009c22a2ab8d00b8a92703788f4d9
[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/license', array('action' => 'licenseadminpanel'));
702             $m->connect('admin/plugins', array('action' => 'pluginsadminpanel'));
703             $m->connect('admin/plugins/enable/:plugin',
704                         array('action' => 'pluginenable'),
705                         array('plugin' => '[A-Za-z0-9_]+'));
706             $m->connect('admin/plugins/disable/:plugin',
707                         array('action' => 'plugindisable'),
708                         array('plugin' => '[A-Za-z0-9_]+'));
709
710             $m->connect('getfile/:filename',
711                         array('action' => 'getfile'),
712                         array('filename' => '[A-Za-z0-9._-]+'));
713
714             // In the "root"
715
716             if (common_config('singleuser', 'enabled')) {
717
718                 $user = User::siteOwner();
719
720                 if (!empty($user)) {
721                     $nickname = $user->nickname;
722                 } else {
723                     $nickname = common_config('singleuser', 'nickname');
724                     if (empty($nickname)) {
725                         throw new ServerException(_("No single user defined for single-user mode."));
726                     }
727                 }
728
729                 foreach (array('subscriptions', 'subscribers',
730                                'all', 'foaf', 'xrds',
731                                'replies', 'microsummary', 'hcard') as $a) {
732                     $m->connect($a,
733                                 array('action' => $a,
734                                       'nickname' => $nickname));
735                 }
736
737                 foreach (array('subscriptions', 'subscribers') as $a) {
738                     $m->connect($a.'/:tag',
739                                 array('action' => $a,
740                                       'nickname' => $nickname),
741                                 array('tag' => '[a-zA-Z0-9]+'));
742                 }
743
744                 foreach (array('rss', 'groups') as $a) {
745                     $m->connect($a,
746                                 array('action' => 'user'.$a,
747                                       'nickname' => $nickname));
748                 }
749
750                 foreach (array('all', 'replies', 'favorites') as $a) {
751                     $m->connect($a.'/rss',
752                                 array('action' => $a.'rss',
753                                       'nickname' => $nickname));
754                 }
755
756                 $m->connect('favorites',
757                             array('action' => 'showfavorites',
758                                   'nickname' => $nickname));
759
760                 $m->connect('avatar/:size',
761                             array('action' => 'avatarbynickname',
762                                   'nickname' => $nickname),
763                             array('size' => '(original|96|48|24)'));
764
765                 $m->connect('tag/:tag/rss',
766                             array('action' => 'userrss',
767                                   'nickname' => $nickname),
768                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
769
770                 $m->connect('tag/:tag',
771                             array('action' => 'showstream',
772                                   'nickname' => $nickname),
773                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
774
775                 $m->connect('rsd.xml',
776                             array('action' => 'rsd',
777                                   'nickname' => $nickname));
778
779                 $m->connect('',
780                             array('action' => 'showstream',
781                                   'nickname' => $nickname));
782
783             } else {
784
785                 $m->connect('', array('action' => 'public'));
786                 $m->connect('rss', array('action' => 'publicrss'));
787                 $m->connect('featuredrss', array('action' => 'featuredrss'));
788                 $m->connect('favoritedrss', array('action' => 'favoritedrss'));
789                 $m->connect('featured/', array('action' => 'featured'));
790                 $m->connect('featured', array('action' => 'featured'));
791                 $m->connect('favorited/', array('action' => 'favorited'));
792                 $m->connect('favorited', array('action' => 'favorited'));
793                 $m->connect('rsd.xml', array('action' => 'rsd'));
794
795                 foreach (array('subscriptions', 'subscribers',
796                                'nudge', 'all', 'foaf', 'xrds',
797                                'replies', 'inbox', 'outbox', 'microsummary', 'hcard') as $a) {
798                     $m->connect(':nickname/'.$a,
799                                 array('action' => $a),
800                                 array('nickname' => '[a-zA-Z0-9]{1,64}'));
801                 }
802
803                 foreach (array('subscriptions', 'subscribers') as $a) {
804                     $m->connect(':nickname/'.$a.'/:tag',
805                                 array('action' => $a),
806                                 array('tag' => '[a-zA-Z0-9]+',
807                                       'nickname' => '[a-zA-Z0-9]{1,64}'));
808                 }
809
810                 foreach (array('rss', 'groups') as $a) {
811                     $m->connect(':nickname/'.$a,
812                                 array('action' => 'user'.$a),
813                                 array('nickname' => '[a-zA-Z0-9]{1,64}'));
814                 }
815
816                 foreach (array('all', 'replies', 'favorites') as $a) {
817                     $m->connect(':nickname/'.$a.'/rss',
818                                 array('action' => $a.'rss'),
819                                 array('nickname' => '[a-zA-Z0-9]{1,64}'));
820                 }
821
822                 $m->connect(':nickname/favorites',
823                             array('action' => 'showfavorites'),
824                             array('nickname' => '[a-zA-Z0-9]{1,64}'));
825
826                 $m->connect(':nickname/avatar/:size',
827                             array('action' => 'avatarbynickname'),
828                             array('size' => '(original|96|48|24)',
829                                   'nickname' => '[a-zA-Z0-9]{1,64}'));
830
831                 $m->connect(':nickname/tag/:tag/rss',
832                             array('action' => 'userrss'),
833                             array('nickname' => '[a-zA-Z0-9]{1,64}'),
834                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
835
836                 $m->connect(':nickname/tag/:tag',
837                             array('action' => 'showstream'),
838                             array('nickname' => '[a-zA-Z0-9]{1,64}'),
839                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
840
841                 $m->connect(':nickname/rsd.xml',
842                             array('action' => 'rsd'),
843                             array('nickname' => '[a-zA-Z0-9]{1,64}'));
844
845                 $m->connect(':nickname',
846                             array('action' => 'showstream'),
847                             array('nickname' => '[a-zA-Z0-9]{1,64}'));
848             }
849
850             // user stuff
851
852             Event::handle('RouterInitialized', array($m));
853         }
854
855         return $m;
856     }
857
858     function map($path)
859     {
860         try {
861             $match = $this->m->match($path);
862         } catch (Net_URL_Mapper_InvalidException $e) {
863             common_log(LOG_ERR, "Problem getting route for $path - " .
864                        $e->getMessage());
865             $cac = new ClientErrorAction("Page not found.", 404);
866             $cac->showPage();
867         }
868
869         return $match;
870     }
871
872     function build($action, $args=null, $params=null, $fragment=null)
873     {
874         $action_arg = array('action' => $action);
875
876         if ($args) {
877             $args = array_merge($action_arg, $args);
878         } else {
879             $args = $action_arg;
880         }
881
882         $url = $this->m->generate($args, $params, $fragment);
883
884         // Due to a bug in the Net_URL_Mapper code, the returned URL may
885         // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
886         // repair that here rather than modifying the upstream code...
887
888         $qpos = strpos($url, '?');
889         if ($qpos !== false) {
890             $url = substr($url, 0, $qpos+1) .
891               str_replace('?', '&', substr($url, $qpos+1));
892         }
893         return $url;
894     }
895 }