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