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