]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/router.php
Merge branch '1.0.x' of gitorious.org:statusnet/mainline into 1.0.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 /**
35  * URL Router
36  *
37  * Cheap wrapper around Net_URL_Mapper
38  *
39  * @category URL
40  * @package  StatusNet
41  * @author   Evan Prodromou <evan@status.net>
42  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
43  * @link     http://status.net/
44  */
45 class Router
46 {
47     var $m = null;
48     static $inst = null;
49
50     const REGEX_TAG = '[^\/]+'; // [\pL\pN_\-\.]{1,64} better if we can do unicode regexes
51
52     static function get()
53     {
54         if (!Router::$inst) {
55             Router::$inst = new Router();
56         }
57         return Router::$inst;
58     }
59
60     /**
61      * Clear the global singleton instance for this class.
62      * Needed to ensure reset when switching site configurations.
63      */
64     static function clear()
65     {
66         Router::$inst = null;
67     }
68
69     function __construct()
70     {
71         if (empty($this->m)) {
72             $this->m = $this->initialize();
73         }
74     }
75
76     /**
77      * Create a unique hashkey for the router.
78      *
79      * The router's url map can change based on the version of the software
80      * you're running and the plugins that are enabled. To avoid having bad routes
81      * get stuck in the cache, the key includes a list of plugins and the software
82      * version.
83      * 
84     * There can still be problems with a) differences in versions of the plugins and
85      * b) people running code between official versions, but these tend to be more
86      * sophisticated users who can grok what's going on and clear their caches.
87      *
88      * @return string cache key string that should uniquely identify a router
89      */
90
91     static function cacheKey()
92     {
93         $parts = array('router');
94
95         // Many router paths depend on this setting.
96         if (common_config('singleuser', 'enabled')) {
97             $parts[] = '1user';
98         } else {
99             $parts[] = 'multi';
100         }
101
102         return Cache::codeKey(implode(':', $parts));
103     }
104
105     function initialize()
106     {
107         $m = new URLMapper();
108
109         if (Event::handle('StartInitializeRouter', array(&$m))) {
110
111             $m->connect('robots.txt', array('action' => 'robotstxt'));
112
113             $m->connect('opensearch/people', array('action' => 'opensearch',
114                                                    'type' => 'people'));
115             $m->connect('opensearch/notice', array('action' => 'opensearch',
116                                                    'type' => 'notice'));
117
118             // docs
119
120             $m->connect('doc/:title', array('action' => 'doc'));
121
122             $m->connect('main/otp/:user_id/:token',
123                         array('action' => 'otp'),
124                         array('user_id' => '[0-9]+',
125                               'token' => '.+'));
126
127             // these take a code; before the main part
128
129             foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) {
130                 $m->connect('main/'.$c.'/:code', array('action' => $c));
131             }
132
133             // Also need a block variant accepting ID on URL for mail links
134             $m->connect('main/block/:profileid',
135                         array('action' => 'block'),
136                         array('profileid' => '[0-9]+'));
137
138             $m->connect('main/sup/:seconds', array('action' => 'sup'),
139                         array('seconds' => '[0-9]+'));
140
141             // main stuff is repetitive
142
143             $main = array('login', 'logout', 'register', 'subscribe',
144                           'unsubscribe', 'cancelsubscription', 'approvesub',
145                           'confirmaddress', 'recoverpassword',
146                           'invite', 'favor', 'disfavor', 'sup',
147                           'block', 'unblock', 'subedit',
148                           'groupblock', 'groupunblock',
149                           'sandbox', 'unsandbox',
150                           'silence', 'unsilence',
151                           'grantrole', 'revokerole',
152                           'repeat',
153                           'deleteuser',
154                           'geocode',
155                           'version',
156                           'backupaccount',
157                           'deleteaccount',
158                           'restoreaccount',
159                           'top',
160             );
161
162             foreach ($main as $a) {
163                 $m->connect('main/'.$a, array('action' => $a));
164             }
165
166             $m->connect('main/tagprofile/:id', array('action' => 'tagprofile'),
167                                                array('id' => '[0-9]+'));
168
169             $m->connect('main/tagprofile', array('action' => 'tagprofile'));
170
171             $m->connect('main/oembed',
172                         array('action' => 'oembed'));
173
174             $m->connect('main/xrds',
175                         array('action' => 'publicxrds'));
176             $m->connect('.well-known/host-meta',
177                         array('action' => 'hostmeta'));
178             $m->connect('main/xrd',
179                         array('action' => 'userxrd'));
180
181             // settings
182
183             foreach (array('profile', 'avatar', 'password', 'im', 'oauthconnections',
184                            'oauthapps', 'email', 'sms', 'url') as $s) {
185                 $m->connect('settings/'.$s, array('action' => $s.'settings'));
186             }
187
188             if (common_config('oldschool', 'enabled')) {
189                 $m->connect('settings/oldschool', array('action' => 'oldschoolsettings'));
190             }
191
192             $m->connect('settings/oauthapps/show/:id',
193                         array('action' => 'showapplication'),
194                         array('id' => '[0-9]+')
195             );
196             $m->connect('settings/oauthapps/new',
197                         array('action' => 'newapplication')
198             );
199             $m->connect('settings/oauthapps/edit/:id',
200                         array('action' => 'editapplication'),
201                         array('id' => '[0-9]+')
202             );
203             $m->connect('settings/oauthapps/delete/:id',
204                         array('action' => 'deleteapplication'),
205                         array('id' => '[0-9]+')
206             );
207
208             // search
209
210             foreach (array('group', 'people', 'notice') as $s) {
211                 $m->connect('search/'.$s.'?q=:q',
212                             array('action' => $s.'search'),
213                             array('q' => '.+'));
214                 $m->connect('search/'.$s, array('action' => $s.'search'));
215             }
216
217             // The second of these is needed to make the link work correctly
218             // when inserted into the page. The first is needed to match the
219             // route on the way in. Seems to be another Net_URL_Mapper bug to me.
220             $m->connect('search/notice/rss?q=:q', array('action' => 'noticesearchrss'),
221                         array('q' => '.+'));
222             $m->connect('search/notice/rss', array('action' => 'noticesearchrss'));
223
224             $m->connect('attachment/:attachment',
225                         array('action' => 'attachment'),
226                         array('attachment' => '[0-9]+'));
227
228             $m->connect('attachment/:attachment/ajax',
229                         array('action' => 'attachment_ajax'),
230                         array('attachment' => '[0-9]+'));
231
232             $m->connect('attachment/:attachment/thumbnail',
233                         array('action' => 'attachment_thumbnail'),
234                         array('attachment' => '[0-9]+'));
235
236             $m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
237                         array('action' => 'newnotice'),
238                         array('replyto' => Nickname::DISPLAY_FMT),
239                         array('inreplyto' => '[0-9]+'));
240
241             $m->connect('notice/new?replyto=:replyto',
242                         array('action' => 'newnotice'),
243                         array('replyto' => Nickname::DISPLAY_FMT));
244
245             $m->connect('notice/new', array('action' => 'newnotice'));
246
247             $m->connect('notice/:notice/file',
248                         array('action' => 'file'),
249                         array('notice' => '[0-9]+'));
250
251             $m->connect('notice/:notice',
252                         array('action' => 'shownotice'),
253                         array('notice' => '[0-9]+'));
254
255             $m->connect('notice/delete/:notice',
256                         array('action' => 'deletenotice'),
257                         array('notice' => '[0-9]+'));
258
259             $m->connect('notice/delete', array('action' => 'deletenotice'));
260
261             $m->connect('bookmarklet/new', array('action' => 'bookmarklet'));
262
263             // conversation
264
265             $m->connect('conversation/:id',
266                         array('action' => 'conversation'),
267                         array('id' => '[0-9]+'));
268             $m->connect('conversation/:id/replies',
269                         array('action' => 'conversationreplies'),
270                         array('id' => '[0-9]+'));
271
272             $m->connect('message/new', array('action' => 'newmessage'));
273             $m->connect('message/new?to=:to', array('action' => 'newmessage'), array('to' => Nickname::DISPLAY_FMT));
274             $m->connect('message/:message',
275                         array('action' => 'showmessage'),
276                         array('message' => '[0-9]+'));
277
278             $m->connect('user/:id',
279                         array('action' => 'userbyid'),
280                         array('id' => '[0-9]+'));
281
282             if (!common_config('performance', 'high')) {
283                 $m->connect('tags/', array('action' => 'publictagcloud'));
284                 $m->connect('tag/', array('action' => 'publictagcloud'));
285                 $m->connect('tags', array('action' => 'publictagcloud'));
286                 $m->connect('tag', array('action' => 'publictagcloud'));
287             }
288             $m->connect('tag/:tag/rss',
289                         array('action' => 'tagrss'),
290                         array('tag' => self::REGEX_TAG));
291             $m->connect('tag/:tag',
292                         array('action' => 'tag'),
293                         array('tag' => self::REGEX_TAG));
294
295             // groups
296
297             $m->connect('group/new', array('action' => 'newgroup'));
298
299             foreach (array('edit', 'join', 'leave', 'delete', 'cancel', 'approve') as $v) {
300                 $m->connect('group/:nickname/'.$v,
301                             array('action' => $v.'group'),
302                             array('nickname' => Nickname::DISPLAY_FMT));
303                 $m->connect('group/:id/id/'.$v,
304                             array('action' => $v.'group'),
305                             array('id' => '[0-9]+'));
306             }
307
308             foreach (array('members', 'logo', 'rss') as $n) {
309                 $m->connect('group/:nickname/'.$n,
310                             array('action' => 'group'.$n),
311                             array('nickname' => Nickname::DISPLAY_FMT));
312             }
313
314             $m->connect('group/:nickname/foaf',
315                         array('action' => 'foafgroup'),
316                         array('nickname' => Nickname::DISPLAY_FMT));
317
318             $m->connect('group/:nickname/blocked',
319                         array('action' => 'blockedfromgroup'),
320                         array('nickname' => Nickname::DISPLAY_FMT));
321
322             $m->connect('group/:nickname/makeadmin',
323                         array('action' => 'makeadmin'),
324                         array('nickname' => Nickname::DISPLAY_FMT));
325
326             $m->connect('group/:nickname/members/pending',
327                         array('action' => 'groupqueue'),
328                         array('nickname' => Nickname::DISPLAY_FMT));
329
330             $m->connect('group/:id/id',
331                         array('action' => 'groupbyid'),
332                         array('id' => '[0-9]+'));
333
334             $m->connect('group/:nickname',
335                         array('action' => 'showgroup'),
336                         array('nickname' => Nickname::DISPLAY_FMT));
337
338             $m->connect('group/:nickname/',
339                         array('action' => 'showgroup'),
340                         array('nickname' => Nickname::DISPLAY_FMT));
341
342             $m->connect('group/', array('action' => 'groups'));
343             $m->connect('group', array('action' => 'groups'));
344             $m->connect('groups/', array('action' => 'groups'));
345             $m->connect('groups', array('action' => 'groups'));
346
347             // Twitter-compatible API
348
349             // statuses API
350
351             $m->connect('api',
352                         array('action' => 'Redirect',
353                               'nextAction' => 'doc',
354                               'args' => array('title' => 'api')));
355
356             $m->connect('api/statuses/public_timeline.:format',
357                         array('action' => 'ApiTimelinePublic',
358                               'format' => '(xml|json|rss|atom|as)'));
359
360             $m->connect('api/statuses/friends_timeline/:id.:format',
361                         array('action' => 'ApiTimelineFriends',
362                               'id' => Nickname::INPUT_FMT,
363                               'format' => '(xml|json|rss|atom|as)'));
364
365             $m->connect('api/statuses/friends_timeline.:format',
366                         array('action' => 'ApiTimelineFriends',
367                               'format' => '(xml|json|rss|atom|as)'));
368
369             $m->connect('api/statuses/home_timeline/:id.:format',
370                         array('action' => 'ApiTimelineHome',
371                               'id' => Nickname::INPUT_FMT,
372                               'format' => '(xml|json|rss|atom|as)'));
373
374             $m->connect('api/statuses/home_timeline.:format',
375                         array('action' => 'ApiTimelineHome',
376                               'format' => '(xml|json|rss|atom|as)'));
377
378             $m->connect('api/statuses/user_timeline/:id.:format',
379                         array('action' => 'ApiTimelineUser',
380                               'id' => Nickname::INPUT_FMT,
381                               'format' => '(xml|json|rss|atom|as)'));
382
383             $m->connect('api/statuses/user_timeline.:format',
384                         array('action' => 'ApiTimelineUser',
385                               'format' => '(xml|json|rss|atom|as)'));
386
387             $m->connect('api/statuses/mentions/:id.:format',
388                         array('action' => 'ApiTimelineMentions',
389                               'id' => Nickname::INPUT_FMT,
390                               'format' => '(xml|json|rss|atom|as)'));
391
392             $m->connect('api/statuses/mentions.:format',
393                         array('action' => 'ApiTimelineMentions',
394                               'format' => '(xml|json|rss|atom|as)'));
395
396             $m->connect('api/statuses/replies/:id.:format',
397                         array('action' => 'ApiTimelineMentions',
398                               'id' => Nickname::INPUT_FMT,
399                               'format' => '(xml|json|rss|atom|as)'));
400
401             $m->connect('api/statuses/replies.:format',
402                         array('action' => 'ApiTimelineMentions',
403                               'format' => '(xml|json|rss|atom|as)'));
404
405             $m->connect('api/statuses/retweeted_by_me.:format',
406                         array('action' => 'ApiTimelineRetweetedByMe',
407                               'format' => '(xml|json|atom|as)'));
408
409             $m->connect('api/statuses/retweeted_to_me.:format',
410                         array('action' => 'ApiTimelineRetweetedToMe',
411                               'format' => '(xml|json|atom|as)'));
412
413             $m->connect('api/statuses/retweets_of_me.:format',
414                         array('action' => 'ApiTimelineRetweetsOfMe',
415                               'format' => '(xml|json|atom|as)'));
416
417             $m->connect('api/statuses/friends/:id.:format',
418                         array('action' => 'ApiUserFriends',
419                               'id' => Nickname::INPUT_FMT,
420                               'format' => '(xml|json)'));
421
422             $m->connect('api/statuses/friends.:format',
423                         array('action' => 'ApiUserFriends',
424                               'format' => '(xml|json)'));
425
426             $m->connect('api/statuses/followers/:id.:format',
427                         array('action' => 'ApiUserFollowers',
428                               'id' => Nickname::INPUT_FMT,
429                               'format' => '(xml|json)'));
430
431             $m->connect('api/statuses/followers.:format',
432                         array('action' => 'ApiUserFollowers',
433                               'format' => '(xml|json)'));
434
435             $m->connect('api/statuses/show/:id.:format',
436                         array('action' => 'ApiStatusesShow',
437                               'id' => '[0-9]+',
438                               'format' => '(xml|json|atom)'));
439
440             $m->connect('api/statuses/show.:format',
441                         array('action' => 'ApiStatusesShow',
442                               'format' => '(xml|json|atom)'));
443
444             $m->connect('api/statuses/update.:format',
445                         array('action' => 'ApiStatusesUpdate',
446                               'format' => '(xml|json)'));
447
448             $m->connect('api/statuses/destroy/:id.:format',
449                         array('action' => 'ApiStatusesDestroy',
450                               'id' => '[0-9]+',
451                               'format' => '(xml|json)'));
452
453             $m->connect('api/statuses/destroy.:format',
454                         array('action' => 'ApiStatusesDestroy',
455                               'format' => '(xml|json)'));
456
457             $m->connect('api/statuses/retweet/:id.:format',
458                         array('action' => 'ApiStatusesRetweet',
459                               'id' => '[0-9]+',
460                               'format' => '(xml|json)'));
461
462             $m->connect('api/statuses/retweets/:id.:format',
463                         array('action' => 'ApiStatusesRetweets',
464                               'id' => '[0-9]+',
465                               'format' => '(xml|json)'));
466
467             // users
468
469             $m->connect('api/users/show/:id.:format',
470                         array('action' => 'ApiUserShow',
471                               'id' => Nickname::INPUT_FMT,
472                               'format' => '(xml|json)'));
473
474             $m->connect('api/users/show.:format',
475                         array('action' => 'ApiUserShow',
476                               'format' => '(xml|json)'));
477
478             $m->connect('api/users/profile_image/:screen_name.:format',
479                         array('action' => 'ApiUserProfileImage',
480                               'screen_name' => Nickname::DISPLAY_FMT,
481                               'format' => '(xml|json)'));
482
483             // direct messages
484
485             $m->connect('api/direct_messages.:format',
486                         array('action' => 'ApiDirectMessage',
487                               'format' => '(xml|json|rss|atom)'));
488
489             $m->connect('api/direct_messages/sent.:format',
490                         array('action' => 'ApiDirectMessage',
491                               'format' => '(xml|json|rss|atom)',
492                               'sent' => true));
493
494             $m->connect('api/direct_messages/new.:format',
495                         array('action' => 'ApiDirectMessageNew',
496                               'format' => '(xml|json)'));
497
498             // friendships
499
500             $m->connect('api/friendships/show.:format',
501                         array('action' => 'ApiFriendshipsShow',
502                               'format' => '(xml|json)'));
503
504             $m->connect('api/friendships/exists.:format',
505                         array('action' => 'ApiFriendshipsExists',
506                               'format' => '(xml|json)'));
507
508             $m->connect('api/friendships/create/:id.:format',
509                         array('action' => 'ApiFriendshipsCreate',
510                               'id' => Nickname::INPUT_FMT,
511                               'format' => '(xml|json)'));
512
513             $m->connect('api/friendships/create.:format',
514                         array('action' => 'ApiFriendshipsCreate',
515                               'format' => '(xml|json)'));
516
517             $m->connect('api/friendships/destroy/:id.:format',
518                         array('action' => 'ApiFriendshipsDestroy',
519                               'id' => Nickname::INPUT_FMT,
520                               'format' => '(xml|json)'));
521
522             $m->connect('api/friendships/destroy.:format',
523                         array('action' => 'ApiFriendshipsDestroy',
524                               'format' => '(xml|json)'));
525
526             // Social graph
527
528             $m->connect('api/friends/ids/:id.:format',
529                         array('action' => 'ApiUserFriends',
530                               'ids_only' => true));
531
532             $m->connect('api/followers/ids/:id.:format',
533                         array('action' => 'ApiUserFollowers',
534                               'ids_only' => true));
535
536             $m->connect('api/friends/ids.:format',
537                         array('action' => 'ApiUserFriends',
538                               'ids_only' => true));
539
540             $m->connect('api/followers/ids.:format',
541                         array('action' => 'ApiUserFollowers',
542                               'ids_only' => true));
543
544             // account
545
546             $m->connect('api/account/verify_credentials.:format',
547                         array('action' => 'ApiAccountVerifyCredentials'));
548
549             $m->connect('api/account/update_profile.:format',
550                         array('action' => 'ApiAccountUpdateProfile'));
551
552             $m->connect('api/account/update_profile_image.:format',
553                         array('action' => 'ApiAccountUpdateProfileImage'));
554
555             $m->connect('api/account/update_delivery_device.:format',
556                         array('action' => 'ApiAccountUpdateDeliveryDevice'));
557
558             // special case where verify_credentials is called w/out a format
559
560             $m->connect('api/account/verify_credentials',
561                         array('action' => 'ApiAccountVerifyCredentials'));
562
563             $m->connect('api/account/rate_limit_status.:format',
564                         array('action' => 'ApiAccountRateLimitStatus'));
565
566             // favorites
567
568             $m->connect('api/favorites/:id.:format',
569                         array('action' => 'ApiTimelineFavorites',
570                               'id' => Nickname::INPUT_FMT,
571                               'format' => '(xml|json|rss|atom|as)'));
572
573             $m->connect('api/favorites.:format',
574                         array('action' => 'ApiTimelineFavorites',
575                               'format' => '(xml|json|rss|atom|as)'));
576
577             $m->connect('api/favorites/create/:id.:format',
578                         array('action' => 'ApiFavoriteCreate',
579                               'id' => '[0-9]+',
580                               'format' => '(xml|json)'));
581
582             $m->connect('api/favorites/destroy/:id.:format',
583                         array('action' => 'ApiFavoriteDestroy',
584                               'id' => '[0-9]+',
585                               'format' => '(xml|json)'));
586             // blocks
587
588             $m->connect('api/blocks/create/:id.:format',
589                         array('action' => 'ApiBlockCreate',
590                               'id' => Nickname::INPUT_FMT,
591                               'format' => '(xml|json)'));
592
593             $m->connect('api/blocks/create.:format',
594                         array('action' => 'ApiBlockCreate',
595                               'format' => '(xml|json)'));
596
597             $m->connect('api/blocks/destroy/:id.:format',
598                         array('action' => 'ApiBlockDestroy',
599                               'id' => Nickname::INPUT_FMT,
600                               'format' => '(xml|json)'));
601
602             $m->connect('api/blocks/destroy.:format',
603                         array('action' => 'ApiBlockDestroy',
604                               'format' => '(xml|json)'));
605
606             // help
607
608             $m->connect('api/help/test.:format',
609                         array('action' => 'ApiHelpTest',
610                               'format' => '(xml|json)'));
611
612             // statusnet
613
614             $m->connect('api/statusnet/version.:format',
615                         array('action' => 'ApiStatusnetVersion',
616                               'format' => '(xml|json)'));
617
618             $m->connect('api/statusnet/config.:format',
619                         array('action' => 'ApiStatusnetConfig',
620                               'format' => '(xml|json)'));
621
622             // For older methods, we provide "laconica" base action
623
624             $m->connect('api/laconica/version.:format',
625                         array('action' => 'ApiStatusnetVersion',
626                               'format' => '(xml|json)'));
627
628             $m->connect('api/laconica/config.:format',
629                         array('action' => 'ApiStatusnetConfig',
630                               'format' => '(xml|json)'));
631
632             // Groups and tags are newer than 0.8.1 so no backward-compatibility
633             // necessary
634
635             // Groups
636             //'list' has to be handled differently, as php will not allow a method to be named 'list'
637
638             $m->connect('api/statusnet/groups/timeline/:id.:format',
639                         array('action' => 'ApiTimelineGroup',
640                               'id' => Nickname::INPUT_FMT,
641                               'format' => '(xml|json|rss|atom|as)'));
642
643             $m->connect('api/statusnet/groups/show/:id.:format',
644                         array('action' => 'ApiGroupShow',
645                               'id' => Nickname::INPUT_FMT,
646                               'format' => '(xml|json)'));
647
648             $m->connect('api/statusnet/groups/show.:format',
649                         array('action' => 'ApiGroupShow',
650                               'format' => '(xml|json)'));
651
652             $m->connect('api/statusnet/groups/join/:id.:format',
653                         array('action' => 'ApiGroupJoin',
654                               'format' => '(xml|json)'));
655
656             $m->connect('api/statusnet/groups/join.:format',
657                         array('action' => 'ApiGroupJoin',
658                               'id' => Nickname::INPUT_FMT,
659                               'format' => '(xml|json)'));
660
661             $m->connect('api/statusnet/groups/leave/:id.:format',
662                         array('action' => 'ApiGroupLeave',
663                               'format' => '(xml|json)'));
664
665             $m->connect('api/statusnet/groups/leave.:format',
666                         array('action' => 'ApiGroupLeave',
667                               'id' => Nickname::INPUT_FMT,
668                               'format' => '(xml|json)'));
669
670             $m->connect('api/statusnet/groups/is_member.:format',
671                         array('action' => 'ApiGroupIsMember',
672                               'format' => '(xml|json)'));
673
674             $m->connect('api/statusnet/groups/list/:id.:format',
675                         array('action' => 'ApiGroupList',
676                               'id' => Nickname::INPUT_FMT,
677                               'format' => '(xml|json|rss|atom)'));
678
679             $m->connect('api/statusnet/groups/list.:format',
680                         array('action' => 'ApiGroupList',
681                               'format' => '(xml|json|rss|atom)'));
682
683             $m->connect('api/statusnet/groups/list_all.:format',
684                         array('action' => 'ApiGroupListAll',
685                               'format' => '(xml|json|rss|atom)'));
686
687             $m->connect('api/statusnet/groups/membership/:id.:format',
688                         array('action' => 'ApiGroupMembership',
689                               'id' => Nickname::INPUT_FMT,
690                               'format' => '(xml|json)'));
691
692             $m->connect('api/statusnet/groups/membership.:format',
693                         array('action' => 'ApiGroupMembership',
694                               'format' => '(xml|json)'));
695
696             $m->connect('api/statusnet/groups/create.:format',
697                         array('action' => 'ApiGroupCreate',
698                               'format' => '(xml|json)'));
699
700             $m->connect('api/statusnet/groups/update/:id.:format',
701                         array('action' => 'ApiGroupProfileUpdate',
702                               'id' => '[a-zA-Z0-9]+',
703                               'format' => '(xml|json)'));
704                               
705             $m->connect('api/statusnet/conversation/:id.:format',
706                         array('action' => 'apiconversation',
707                               'id' => '[0-9]+',
708                               'format' => '(xml|json|rss|atom|as)'));
709
710             // Lists (people tags)
711
712             $m->connect('api/lists/memberships.:format',
713                         array('action' => 'ApiListMemberships',
714                               'format' => '(xml|json)'));
715
716             $m->connect('api/:user/lists/memberships.:format',
717                         array('action' => 'ApiListMemberships',
718                               'user' => '[a-zA-Z0-9]+',
719                               'format' => '(xml|json)'));
720
721             $m->connect('api/lists/subscriptions.:format',
722                         array('action' => 'ApiListSubscriptions',
723                               'format' => '(xml|json)'));
724
725             $m->connect('api/:user/lists/subscriptions.:format',
726                         array('action' => 'ApiListSubscriptions',
727                               'user' => '[a-zA-Z0-9]+',
728                               'format' => '(xml|json)'));
729
730             $m->connect('api/lists.:format',
731                         array('action' => 'ApiLists',
732                               'format' => '(xml|json)'));
733
734             $m->connect('api/:user/lists/:id.:format',
735                         array('action' => 'ApiList',
736                               'user' => '[a-zA-Z0-9]+',
737                               'id' => '[a-zA-Z0-9]+',
738                               'format' => '(xml|json)'));
739
740             $m->connect('api/:user/lists.:format',
741                         array('action' => 'ApiLists',
742                               'user' => '[a-zA-Z0-9]+',
743                               'format' => '(xml|json)'));
744
745             $m->connect('api/:user/lists/:id/statuses.:format',
746                         array('action' => 'ApiTimelineList',
747                               'user' => '[a-zA-Z0-9]+',
748                               'id' => '[a-zA-Z0-9]+',
749                               'format' => '(xml|json|rss|atom)'));
750
751             $m->connect('api/:user/:list_id/members/:id.:format',
752                         array('action' => 'ApiListMember',
753                               'user' => '[a-zA-Z0-9]+',
754                               'list_id' => '[a-zA-Z0-9]+',
755                               'id' => '[a-zA-Z0-9]+',
756                               'format' => '(xml|json)'));
757
758             $m->connect('api/:user/:list_id/members.:format',
759                         array('action' => 'ApiListMembers',
760                               'user' => '[a-zA-Z0-9]+',
761                               'list_id' => '[a-zA-Z0-9]+',
762                               'format' => '(xml|json)'));
763
764             $m->connect('api/:user/:list_id/subscribers/:id.:format',
765                         array('action' => 'ApiListSubscriber',
766                               'user' => '[a-zA-Z0-9]+',
767                               'list_id' => '[a-zA-Z0-9]+',
768                               'id' => '[a-zA-Z0-9]+',
769                               'format' => '(xml|json)'));
770
771             $m->connect('api/:user/:list_id/subscribers.:format',
772                         array('action' => 'ApiListSubscribers',
773                               'user' => '[a-zA-Z0-9]+',
774                               'list_id' => '[a-zA-Z0-9]+',
775                               'format' => '(xml|json)'));
776
777             // Tags
778             $m->connect('api/statusnet/tags/timeline/:tag.:format',
779                         array('action' => 'ApiTimelineTag',
780                               'format' => '(xml|json|rss|atom|as)'));
781
782             // media related
783             $m->connect(
784                 'api/statusnet/media/upload',
785                 array('action' => 'ApiMediaUpload')
786             );
787
788             // search
789             $m->connect('api/search.atom', array('action' => 'ApiSearchAtom'));
790             $m->connect('api/search.json', array('action' => 'ApiSearchJSON'));
791             $m->connect('api/trends.json', array('action' => 'ApiTrends'));
792
793             $m->connect('api/oauth/request_token',
794                         array('action' => 'ApiOauthRequestToken'));
795
796             $m->connect('api/oauth/access_token',
797                         array('action' => 'ApiOauthAccessToken'));
798
799             $m->connect('api/oauth/authorize',
800                         array('action' => 'ApiOauthAuthorize'));
801
802             // Admin
803
804             $m->connect('panel/site', array('action' => 'siteadminpanel'));
805             $m->connect('panel/user', array('action' => 'useradminpanel'));
806                 $m->connect('panel/access', array('action' => 'accessadminpanel'));
807             $m->connect('panel/paths', array('action' => 'pathsadminpanel'));
808             $m->connect('panel/sessions', array('action' => 'sessionsadminpanel'));
809             $m->connect('panel/sitenotice', array('action' => 'sitenoticeadminpanel'));
810             $m->connect('panel/snapshot', array('action' => 'snapshotadminpanel'));
811             $m->connect('panel/license', array('action' => 'licenseadminpanel'));
812
813             $m->connect('panel/plugins', array('action' => 'pluginsadminpanel'));
814             $m->connect('panel/plugins/enable/:plugin',
815                         array('action' => 'pluginenable'),
816                         array('plugin' => '[A-Za-z0-9_]+'));
817             $m->connect('panel/plugins/disable/:plugin',
818                         array('action' => 'plugindisable'),
819                         array('plugin' => '[A-Za-z0-9_]+'));
820
821             $m->connect('getfile/:filename',
822                         array('action' => 'getfile'),
823                         array('filename' => '[A-Za-z0-9._-]+'));
824
825             // Common people-tag stuff
826
827             $m->connect('peopletag/:tag', array('action' => 'peopletag',
828                                                 'tag'    => self::REGEX_TAG));
829
830             $m->connect('selftag/:tag', array('action' => 'selftag',
831                                               'tag'    => self::REGEX_TAG));
832
833             $m->connect('main/addpeopletag', array('action' => 'addpeopletag'));
834
835             $m->connect('main/removepeopletag', array('action' => 'removepeopletag'));
836
837             $m->connect('main/profilecompletion', array('action' => 'profilecompletion'));
838
839             $m->connect('main/peopletagautocomplete', array('action' => 'peopletagautocomplete'));
840
841             // In the "root"
842
843             if (common_config('singleuser', 'enabled')) {
844
845                 $nickname = User::singleUserNickname();
846
847                 foreach (array('subscriptions', 'subscribers',
848                                'all', 'foaf', 'replies',
849                                'microsummary') as $a) {
850                     $m->connect($a,
851                                 array('action' => $a,
852                                       'nickname' => $nickname));
853                 }
854
855                 foreach (array('subscriptions', 'subscribers') as $a) {
856                     $m->connect($a.'/:tag',
857                                 array('action' => $a,
858                                       'nickname' => $nickname),
859                                 array('tag' => self::REGEX_TAG));
860                 }
861
862                 $m->connect('subscribers/pending',
863                             array('action' => 'subqueue',
864                                   'nickname' => $nickname));
865
866                 foreach (array('rss', 'groups') as $a) {
867                     $m->connect($a,
868                                 array('action' => 'user'.$a,
869                                       'nickname' => $nickname));
870                 }
871
872                 foreach (array('all', 'replies', 'favorites') as $a) {
873                     $m->connect($a.'/rss',
874                                 array('action' => $a.'rss',
875                                       'nickname' => $nickname));
876                 }
877
878                 $m->connect('favorites',
879                             array('action' => 'showfavorites',
880                                   'nickname' => $nickname));
881
882                 $m->connect('avatar/:size',
883                             array('action' => 'avatarbynickname',
884                                   'nickname' => $nickname),
885                             array('size' => '(original|96|48|24)'));
886
887                 $m->connect('tag/:tag/rss',
888                             array('action' => 'userrss',
889                                   'nickname' => $nickname),
890                             array('tag' => self::REGEX_TAG));
891
892                 $m->connect('tag/:tag',
893                             array('action' => 'showstream',
894                                   'nickname' => $nickname),
895                             array('tag' => self::REGEX_TAG));
896
897                 $m->connect('rsd.xml',
898                             array('action' => 'rsd',
899                                   'nickname' => $nickname));
900
901                 $m->connect('',
902                             array('action' => 'showstream',
903                                   'nickname' => $nickname));
904
905                 // peopletags
906
907                 $m->connect('peopletags',
908                             array('action' => 'peopletagsbyuser'));
909
910                 $m->connect('peopletags/private',
911                             array('action' => 'peopletagsbyuser',
912                                   'private' => 1));
913
914                 $m->connect('peopletags/public',
915                             array('action' => 'peopletagsbyuser',
916                                   'public' => 1));
917
918                 $m->connect('othertags',
919                             array('action' => 'peopletagsforuser'));
920
921                 $m->connect('peopletagsubscriptions',
922                             array('action' => 'peopletagsubscriptions'));
923
924                 $m->connect('all/:tag/subscribers',
925                             array('action' => 'peopletagsubscribers',
926                                   'tag' => self::REGEX_TAG));
927
928                 $m->connect('all/:tag/tagged',
929                                 array('action' => 'peopletagged',
930                                       'tag' => self::REGEX_TAG));
931
932                 $m->connect('all/:tag/edit',
933                                 array('action' => 'editpeopletag',
934                                       'tag' => self::REGEX_TAG));
935
936                 foreach(array('subscribe', 'unsubscribe') as $v) {
937                     $m->connect('peopletag/:id/'.$v,
938                                     array('action' => $v.'peopletag',
939                                           'id' => '[0-9]{1,64}'));
940                 }
941                 $m->connect('user/:tagger_id/profiletag/:id/id',
942                                 array('action' => 'profiletagbyid',
943                                       'tagger_id' => '[0-9]+',
944                                       'id' => '[0-9]+'));
945
946                 $m->connect('all/:tag',
947                                 array('action' => 'showprofiletag',
948                                       'tag' => self::REGEX_TAG));
949
950                 foreach (array('subscriptions', 'subscribers') as $a) {
951                     $m->connect($a.'/:tag',
952                                 array('action' => $a),
953                                 array('tag' => self::REGEX_TAG));
954                 }
955             } else {
956                 $m->connect('', array('action' => 'public'));
957                 $m->connect('rss', array('action' => 'publicrss'));
958                 $m->connect('featuredrss', array('action' => 'featuredrss'));
959                 $m->connect('favoritedrss', array('action' => 'favoritedrss'));
960                 $m->connect('featured/', array('action' => 'featured'));
961                 $m->connect('featured', array('action' => 'featured'));
962                 $m->connect('favorited/', array('action' => 'favorited'));
963                 $m->connect('favorited', array('action' => 'favorited'));
964                 $m->connect('rsd.xml', array('action' => 'rsd'));
965
966                 foreach (array('subscriptions', 'subscribers',
967                                'nudge', 'all', 'foaf', 'replies',
968                                'inbox', 'outbox', 'microsummary') as $a) {
969                     $m->connect(':nickname/'.$a,
970                                 array('action' => $a),
971                                 array('nickname' => Nickname::DISPLAY_FMT));
972                 }
973                 $m->connect(':nickname/subscribers/pending',
974                             array('action' => 'subqueue'),
975                             array('nickname' => Nickname::DISPLAY_FMT));
976
977                 // people tags
978
979                 $m->connect(':nickname/peopletags',
980                                 array('action' => 'peopletagsbyuser',
981                                       'nickname' => Nickname::DISPLAY_FMT));
982
983                 $m->connect(':nickname/peopletags/private',
984                                 array('action' => 'peopletagsbyuser',
985                                       'nickname' => Nickname::DISPLAY_FMT,
986                                       'private' => 1));
987
988                 $m->connect(':nickname/peopletags/public',
989                                 array('action' => 'peopletagsbyuser',
990                                       'nickname' => Nickname::DISPLAY_FMT,
991                                       'public' => 1));
992
993                 $m->connect(':nickname/othertags',
994                                 array('action' => 'peopletagsforuser',
995                                       'nickname' => Nickname::DISPLAY_FMT));
996
997                 $m->connect(':nickname/peopletagsubscriptions',
998                                 array('action' => 'peopletagsubscriptions',
999                                       'nickname' => Nickname::DISPLAY_FMT));
1000
1001                 $m->connect(':tagger/all/:tag/subscribers',
1002                                 array('action' => 'peopletagsubscribers',
1003                                       'tagger' => Nickname::DISPLAY_FMT,
1004                                       'tag' => self::REGEX_TAG));
1005
1006                 $m->connect(':tagger/all/:tag/tagged',
1007                                 array('action' => 'peopletagged',
1008                                       'tagger' => Nickname::DISPLAY_FMT,
1009                                       'tag' => self::REGEX_TAG));
1010
1011                 $m->connect(':tagger/all/:tag/edit',
1012                                 array('action' => 'editpeopletag',
1013                                       'tagger' => Nickname::DISPLAY_FMT,
1014                                       'tag' => self::REGEX_TAG));
1015
1016                 foreach(array('subscribe', 'unsubscribe') as $v) {
1017                     $m->connect('peopletag/:id/'.$v,
1018                                     array('action' => $v.'peopletag',
1019                                           'id' => '[0-9]{1,64}'));
1020                 }
1021                 $m->connect('user/:tagger_id/profiletag/:id/id',
1022                                 array('action' => 'profiletagbyid',
1023                                       'tagger_id' => '[0-9]+',
1024                                       'id' => '[0-9]+'));
1025
1026                 $m->connect(':tagger/all/:tag',
1027                                 array('action' => 'showprofiletag',
1028                                       'tagger' => Nickname::DISPLAY_FMT,
1029                                       'tag' => self::REGEX_TAG));
1030
1031                 foreach (array('subscriptions', 'subscribers') as $a) {
1032                     $m->connect(':nickname/'.$a.'/:tag',
1033                                 array('action' => $a),
1034                                 array('tag' => self::REGEX_TAG,
1035                                       'nickname' => Nickname::DISPLAY_FMT));
1036                 }
1037
1038                 foreach (array('rss', 'groups') as $a) {
1039                     $m->connect(':nickname/'.$a,
1040                                 array('action' => 'user'.$a),
1041                                 array('nickname' => Nickname::DISPLAY_FMT));
1042                 }
1043
1044                 foreach (array('all', 'replies', 'favorites') as $a) {
1045                     $m->connect(':nickname/'.$a.'/rss',
1046                                 array('action' => $a.'rss'),
1047                                 array('nickname' => Nickname::DISPLAY_FMT));
1048                 }
1049
1050                 $m->connect(':nickname/favorites',
1051                             array('action' => 'showfavorites'),
1052                             array('nickname' => Nickname::DISPLAY_FMT));
1053
1054                 $m->connect(':nickname/avatar/:size',
1055                             array('action' => 'avatarbynickname'),
1056                             array('size' => '(original|96|48|24)',
1057                                   'nickname' => Nickname::DISPLAY_FMT));
1058
1059                 $m->connect(':nickname/tag/:tag/rss',
1060                             array('action' => 'userrss'),
1061                             array('nickname' => Nickname::DISPLAY_FMT),
1062                             array('tag' => self::REGEX_TAG));
1063
1064                 $m->connect(':nickname/tag/:tag',
1065                             array('action' => 'showstream'),
1066                             array('nickname' => Nickname::DISPLAY_FMT),
1067                             array('tag' => self::REGEX_TAG));
1068
1069                 $m->connect(':nickname/rsd.xml',
1070                             array('action' => 'rsd'),
1071                             array('nickname' => Nickname::DISPLAY_FMT));
1072
1073                 $m->connect(':nickname',
1074                             array('action' => 'showstream'),
1075                             array('nickname' => Nickname::DISPLAY_FMT));
1076
1077                 $m->connect(':nickname/',
1078                             array('action' => 'showstream'),
1079                             array('nickname' => Nickname::DISPLAY_FMT));
1080             }
1081
1082             // AtomPub API
1083
1084             $m->connect('api/statusnet/app/service/:id.xml',
1085                         array('action' => 'ApiAtomService'),
1086                         array('id' => Nickname::DISPLAY_FMT));
1087
1088             $m->connect('api/statusnet/app/service.xml',
1089                         array('action' => 'ApiAtomService'));
1090
1091             $m->connect('api/statusnet/app/subscriptions/:subscriber/:subscribed.atom',
1092                         array('action' => 'AtomPubShowSubscription'),
1093                         array('subscriber' => '[0-9]+',
1094                               'subscribed' => '[0-9]+'));
1095
1096             $m->connect('api/statusnet/app/subscriptions/:subscriber.atom',
1097                         array('action' => 'AtomPubSubscriptionFeed'),
1098                         array('subscriber' => '[0-9]+'));
1099
1100             $m->connect('api/statusnet/app/favorites/:profile/:notice.atom',
1101                         array('action' => 'AtomPubShowFavorite'),
1102                         array('profile' => '[0-9]+',
1103                               'notice' => '[0-9]+'));
1104
1105             $m->connect('api/statusnet/app/favorites/:profile.atom',
1106                         array('action' => 'AtomPubFavoriteFeed'),
1107                         array('profile' => '[0-9]+'));
1108
1109             $m->connect('api/statusnet/app/memberships/:profile/:group.atom',
1110                         array('action' => 'AtomPubShowMembership'),
1111                         array('profile' => '[0-9]+',
1112                               'group' => '[0-9]+'));
1113
1114             $m->connect('api/statusnet/app/memberships/:profile.atom',
1115                         array('action' => 'AtomPubMembershipFeed'),
1116                         array('profile' => '[0-9]+'));
1117
1118             // URL shortening
1119
1120             $m->connect('url/:id',
1121                         array('action' => 'redirecturl',
1122                               'id' => '[0-9]+'));
1123
1124             // user stuff
1125
1126             Event::handle('RouterInitialized', array($m));
1127         }
1128
1129         return $m;
1130     }
1131
1132     function map($path)
1133     {
1134         try {
1135             $match = $this->m->match($path);
1136         } catch (Exception $e) {
1137             common_log(LOG_ERR, "Problem getting route for $path - " .
1138                        $e->getMessage());
1139             // TRANS: Client error on action trying to visit a non-existing page.
1140             $cac = new ClientErrorAction(_('Page not found.'), 404);
1141             $cac->showPage();
1142         }
1143
1144         return $match;
1145     }
1146
1147     function build($action, $args=null, $params=null, $fragment=null)
1148     {
1149         $action_arg = array('action' => $action);
1150
1151         if ($args) {
1152             $args = array_merge($action_arg, $args);
1153         } else {
1154             $args = $action_arg;
1155         }
1156
1157         $url = $this->m->generate($args, $params, $fragment);
1158         // Due to a bug in the Net_URL_Mapper code, the returned URL may
1159         // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
1160         // repair that here rather than modifying the upstream code...
1161
1162         $qpos = strpos($url, '?');
1163         if ($qpos !== false) {
1164             $url = substr($url, 0, $qpos+1) .
1165                 str_replace('?', '&', substr($url, $qpos+1));
1166
1167             // @fixme this is a hacky workaround for http_build_query in the
1168             // lower-level code and bad configs that set the default separator
1169             // to &amp; instead of &. Encoded &s in parameters will not be
1170             // affected.
1171             $url = substr($url, 0, $qpos+1) .
1172                 str_replace('&amp;', '&', substr($url, $qpos+1));
1173
1174         }
1175
1176         return $url;
1177     }
1178 }