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