]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/router.php
Merge branch '0.9.x' of gitorious.org:statusnet/mainline 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/:id.:format',
555                         array('action' => 'ApiBlockCreate',
556                               'id' => '[a-zA-Z0-9]+',
557                               'format' => '(xml|json)'));
558
559             $m->connect('api/blocks/destroy/:id.:format',
560                         array('action' => 'ApiBlockDestroy',
561                               'id' => '[a-zA-Z0-9]+',
562                               'format' => '(xml|json)'));
563             // help
564
565             $m->connect('api/help/test.:format',
566                         array('action' => 'ApiHelpTest',
567                               'format' => '(xml|json)'));
568
569             // statusnet
570
571             $m->connect('api/statusnet/version.:format',
572                         array('action' => 'ApiStatusnetVersion',
573                               'format' => '(xml|json)'));
574
575             $m->connect('api/statusnet/config.:format',
576                         array('action' => 'ApiStatusnetConfig',
577                               'format' => '(xml|json)'));
578
579             // For older methods, we provide "laconica" base action
580
581             $m->connect('api/laconica/version.:format',
582                         array('action' => 'ApiStatusnetVersion',
583                               'format' => '(xml|json)'));
584
585             $m->connect('api/laconica/config.:format',
586                         array('action' => 'ApiStatusnetConfig',
587                               'format' => '(xml|json)'));
588
589             // Groups and tags are newer than 0.8.1 so no backward-compatibility
590             // necessary
591
592             // Groups
593             //'list' has to be handled differently, as php will not allow a method to be named 'list'
594
595             $m->connect('api/statusnet/groups/timeline/:id.:format',
596                         array('action' => 'ApiTimelineGroup',
597                               'id' => '[a-zA-Z0-9]+',
598                               'format' => '(xml|json|rss|atom)'));
599
600             $m->connect('api/statusnet/groups/show.:format',
601                         array('action' => 'ApiGroupShow',
602                               'format' => '(xml|json)'));
603
604             $m->connect('api/statusnet/groups/show/:id.:format',
605                         array('action' => 'ApiGroupShow',
606                               'id' => '[a-zA-Z0-9]+',
607                               'format' => '(xml|json)'));
608
609             $m->connect('api/statusnet/groups/join.:format',
610                         array('action' => 'ApiGroupJoin',
611                               'id' => '[a-zA-Z0-9]+',
612                               'format' => '(xml|json)'));
613
614             $m->connect('api/statusnet/groups/join/:id.:format',
615                         array('action' => 'ApiGroupJoin',
616                               'format' => '(xml|json)'));
617
618             $m->connect('api/statusnet/groups/leave.:format',
619                         array('action' => 'ApiGroupLeave',
620                               'id' => '[a-zA-Z0-9]+',
621                               'format' => '(xml|json)'));
622
623             $m->connect('api/statusnet/groups/leave/:id.:format',
624                         array('action' => 'ApiGroupLeave',
625                               'format' => '(xml|json)'));
626
627             $m->connect('api/statusnet/groups/is_member.:format',
628                         array('action' => 'ApiGroupIsMember',
629                               'format' => '(xml|json)'));
630
631             $m->connect('api/statusnet/groups/list.:format',
632                         array('action' => 'ApiGroupList',
633                               'format' => '(xml|json|rss|atom)'));
634
635             $m->connect('api/statusnet/groups/list/:id.:format',
636                         array('action' => 'ApiGroupList',
637                               'id' => '[a-zA-Z0-9]+',
638                               'format' => '(xml|json|rss|atom)'));
639
640             $m->connect('api/statusnet/groups/list_all.:format',
641                         array('action' => 'ApiGroupListAll',
642                               'format' => '(xml|json|rss|atom)'));
643
644             $m->connect('api/statusnet/groups/membership.:format',
645                         array('action' => 'ApiGroupMembership',
646                               'format' => '(xml|json)'));
647
648             $m->connect('api/statusnet/groups/membership/:id.:format',
649                         array('action' => 'ApiGroupMembership',
650                               'id' => '[a-zA-Z0-9]+',
651                               'format' => '(xml|json)'));
652
653             $m->connect('api/statusnet/groups/create.:format',
654                         array('action' => 'ApiGroupCreate',
655                               'format' => '(xml|json)'));
656             // Tags
657             $m->connect('api/statusnet/tags/timeline/:tag.:format',
658                         array('action' => 'ApiTimelineTag',
659                               'format' => '(xml|json|rss|atom)'));
660
661             // media related
662             $m->connect(
663                 'api/statusnet/media/upload',
664                 array('action' => 'ApiMediaUpload')
665             );
666
667             // search
668             $m->connect('api/search.atom', array('action' => 'ApiSearchAtom'));
669             $m->connect('api/search.json', array('action' => 'ApiSearchJSON'));
670             $m->connect('api/trends.json', array('action' => 'ApiTrends'));
671
672             $m->connect('api/oauth/request_token',
673                         array('action' => 'ApiOauthRequestToken'));
674
675             $m->connect('api/oauth/access_token',
676                         array('action' => 'ApiOauthAccessToken'));
677
678             $m->connect('api/oauth/authorize',
679                         array('action' => 'ApiOauthAuthorize'));
680
681             // Admin
682
683             $m->connect('admin/site', array('action' => 'siteadminpanel'));
684             $m->connect('admin/design', array('action' => 'designadminpanel'));
685             $m->connect('admin/user', array('action' => 'useradminpanel'));
686                 $m->connect('admin/access', array('action' => 'accessadminpanel'));
687             $m->connect('admin/paths', array('action' => 'pathsadminpanel'));
688             $m->connect('admin/sessions', array('action' => 'sessionsadminpanel'));
689             $m->connect('admin/sitenotice', array('action' => 'sitenoticeadminpanel'));
690             $m->connect('admin/snapshot', array('action' => 'snapshotadminpanel'));
691             $m->connect('admin/license', array('action' => 'licenseadminpanel'));
692
693             $m->connect('getfile/:filename',
694                         array('action' => 'getfile'),
695                         array('filename' => '[A-Za-z0-9._-]+'));
696
697             // In the "root"
698
699             if (common_config('singleuser', 'enabled')) {
700
701                 $user = User::singleUser();
702                 $nickname = $user->nickname;
703
704                 foreach (array('subscriptions', 'subscribers',
705                                'all', 'foaf', 'xrds',
706                                'replies', 'microsummary', 'hcard') as $a) {
707                     $m->connect($a,
708                                 array('action' => $a,
709                                       'nickname' => $nickname));
710                 }
711
712                 foreach (array('subscriptions', 'subscribers') as $a) {
713                     $m->connect($a.'/:tag',
714                                 array('action' => $a,
715                                       'nickname' => $nickname),
716                                 array('tag' => '[a-zA-Z0-9]+'));
717                 }
718
719                 foreach (array('rss', 'groups') as $a) {
720                     $m->connect($a,
721                                 array('action' => 'user'.$a,
722                                       'nickname' => $nickname));
723                 }
724
725                 foreach (array('all', 'replies', 'favorites') as $a) {
726                     $m->connect($a.'/rss',
727                                 array('action' => $a.'rss',
728                                       'nickname' => $nickname));
729                 }
730
731                 $m->connect('favorites',
732                             array('action' => 'showfavorites',
733                                   'nickname' => $nickname));
734
735                 $m->connect('avatar/:size',
736                             array('action' => 'avatarbynickname',
737                                   'nickname' => $nickname),
738                             array('size' => '(original|96|48|24)'));
739
740                 $m->connect('tag/:tag/rss',
741                             array('action' => 'userrss',
742                                   'nickname' => $nickname),
743                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
744
745                 $m->connect('tag/:tag',
746                             array('action' => 'showstream',
747                                   'nickname' => $nickname),
748                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
749
750                 $m->connect('rsd.xml',
751                             array('action' => 'rsd',
752                                   'nickname' => $nickname));
753
754                 $m->connect('',
755                             array('action' => 'showstream',
756                                   'nickname' => $nickname));
757             } else {
758                 $m->connect('', array('action' => 'public'));
759                 $m->connect('rss', array('action' => 'publicrss'));
760                 $m->connect('featuredrss', array('action' => 'featuredrss'));
761                 $m->connect('favoritedrss', array('action' => 'favoritedrss'));
762                 $m->connect('featured/', array('action' => 'featured'));
763                 $m->connect('featured', array('action' => 'featured'));
764                 $m->connect('favorited/', array('action' => 'favorited'));
765                 $m->connect('favorited', array('action' => 'favorited'));
766                 $m->connect('rsd.xml', array('action' => 'rsd'));
767
768                 foreach (array('subscriptions', 'subscribers',
769                                'nudge', 'all', 'foaf', 'xrds',
770                                'replies', 'inbox', 'outbox', 'microsummary', 'hcard') as $a) {
771                     $m->connect(':nickname/'.$a,
772                                 array('action' => $a),
773                                 array('nickname' => '[a-zA-Z0-9]{1,64}'));
774                 }
775
776                 foreach (array('subscriptions', 'subscribers') as $a) {
777                     $m->connect(':nickname/'.$a.'/:tag',
778                                 array('action' => $a),
779                                 array('tag' => '[a-zA-Z0-9]+',
780                                       'nickname' => '[a-zA-Z0-9]{1,64}'));
781                 }
782
783                 foreach (array('rss', 'groups') as $a) {
784                     $m->connect(':nickname/'.$a,
785                                 array('action' => 'user'.$a),
786                                 array('nickname' => '[a-zA-Z0-9]{1,64}'));
787                 }
788
789                 foreach (array('all', 'replies', 'favorites') as $a) {
790                     $m->connect(':nickname/'.$a.'/rss',
791                                 array('action' => $a.'rss'),
792                                 array('nickname' => '[a-zA-Z0-9]{1,64}'));
793                 }
794
795                 $m->connect(':nickname/favorites',
796                             array('action' => 'showfavorites'),
797                             array('nickname' => '[a-zA-Z0-9]{1,64}'));
798
799                 $m->connect(':nickname/avatar/:size',
800                             array('action' => 'avatarbynickname'),
801                             array('size' => '(original|96|48|24)',
802                                   'nickname' => '[a-zA-Z0-9]{1,64}'));
803
804                 $m->connect(':nickname/tag/:tag/rss',
805                             array('action' => 'userrss'),
806                             array('nickname' => '[a-zA-Z0-9]{1,64}'),
807                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
808
809                 $m->connect(':nickname/tag/:tag',
810                             array('action' => 'showstream'),
811                             array('nickname' => '[a-zA-Z0-9]{1,64}'),
812                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
813
814                 $m->connect(':nickname/rsd.xml',
815                             array('action' => 'rsd'),
816                             array('nickname' => '[a-zA-Z0-9]{1,64}'));
817
818                 $m->connect(':nickname',
819                             array('action' => 'showstream'),
820                             array('nickname' => '[a-zA-Z0-9]{1,64}'));
821             }
822
823             // user stuff
824
825             Event::handle('RouterInitialized', array($m));
826         }
827
828         return $m;
829     }
830
831     function map($path)
832     {
833         try {
834             $match = $this->m->match($path);
835         } catch (Net_URL_Mapper_InvalidException $e) {
836             common_log(LOG_ERR, "Problem getting route for $path - " .
837                        $e->getMessage());
838             // TRANS: Client error on action trying to visit a non-existing page.
839             $cac = new ClientErrorAction(_('Page not found.'), 404);
840             $cac->showPage();
841         }
842
843         return $match;
844     }
845
846     function build($action, $args=null, $params=null, $fragment=null)
847     {
848         $action_arg = array('action' => $action);
849
850         if ($args) {
851             $args = array_merge($action_arg, $args);
852         } else {
853             $args = $action_arg;
854         }
855
856         $url = $this->m->generate($args, $params, $fragment);
857
858         // Due to a bug in the Net_URL_Mapper code, the returned URL may
859         // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
860         // repair that here rather than modifying the upstream code...
861
862         $qpos = strpos($url, '?');
863         if ($qpos !== false) {
864             $url = substr($url, 0, $qpos+1) .
865               str_replace('?', '&', substr($url, $qpos+1));
866         }
867         return $url;
868     }
869 }