]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/router.php
StartpageAction essentially duplicated TopAction
[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             // top of the menu hierarchy, sometimes "Home"
112             $m->connect('', array('action' => 'top'));
113
114             // public endpoints
115
116             $m->connect('robots.txt', array('action' => 'robotstxt'));
117
118             $m->connect('opensearch/people', array('action' => 'opensearch',
119                                                    'type' => 'people'));
120             $m->connect('opensearch/notice', array('action' => 'opensearch',
121                                                    'type' => 'notice'));
122
123             // docs
124
125             $m->connect('doc/:title', array('action' => 'doc'));
126
127             $m->connect('main/otp/:user_id/:token',
128                         array('action' => 'otp'),
129                         array('user_id' => '[0-9]+',
130                               'token' => '.+'));
131
132             // these take a code; before the main part
133
134             foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) {
135                 $m->connect('main/'.$c.'/:code', array('action' => $c));
136             }
137
138             // Also need a block variant accepting ID on URL for mail links
139             $m->connect('main/block/:profileid',
140                         array('action' => 'block'),
141                         array('profileid' => '[0-9]+'));
142
143             $m->connect('main/sup/:seconds', array('action' => 'sup'),
144                         array('seconds' => '[0-9]+'));
145
146             // main stuff is repetitive
147
148             $main = array('login', 'logout', 'register', 'subscribe',
149                           'unsubscribe', 'cancelsubscription', 'approvesub',
150                           'confirmaddress', 'recoverpassword',
151                           'invite', 'sup',
152                           'block', 'unblock', 'subedit',
153                           'groupblock', 'groupunblock',
154                           'sandbox', 'unsandbox',
155                           'silence', 'unsilence',
156                           'grantrole', 'revokerole',
157                           'deleteuser',
158                           'geocode',
159                           'version',
160                           'backupaccount',
161                           'deleteaccount',
162                           'restoreaccount',
163                           'top',
164                           'public',
165             );
166
167             foreach ($main as $a) {
168                 $m->connect('main/'.$a, array('action' => $a));
169             }
170
171             $m->connect('main/all', array('action' => 'networkpublic'));
172
173             $m->connect('main/tagprofile/:id', array('action' => 'tagprofile'),
174                                                array('id' => '[0-9]+'));
175
176             $m->connect('main/tagprofile', array('action' => 'tagprofile'));
177
178             $m->connect('main/xrds',
179                         array('action' => 'publicxrds'));
180
181             // settings
182
183             foreach (array('profile', 'avatar', 'password', 'im', 'oauthconnections',
184                            'oauthapps', 'email', 'sms', 'url') as $s) {
185                 $m->connect('settings/'.$s, array('action' => $s.'settings'));
186             }
187
188             if (common_config('oldschool', 'enabled')) {
189                 $m->connect('settings/oldschool', array('action' => 'oldschoolsettings'));
190             }
191
192             $m->connect('settings/oauthapps/show/:id',
193                         array('action' => 'showapplication'),
194                         array('id' => '[0-9]+')
195             );
196             $m->connect('settings/oauthapps/new',
197                         array('action' => 'newapplication')
198             );
199             $m->connect('settings/oauthapps/edit/:id',
200                         array('action' => 'editapplication'),
201                         array('id' => '[0-9]+')
202             );
203             $m->connect('settings/oauthapps/delete/:id',
204                         array('action' => 'deleteapplication'),
205                         array('id' => '[0-9]+')
206             );
207
208             // search
209
210             foreach (array('group', 'people', 'notice') as $s) {
211                 $m->connect('search/'.$s.'?q=:q',
212                             array('action' => $s.'search'),
213                             array('q' => '.+'));
214                 $m->connect('search/'.$s, array('action' => $s.'search'));
215             }
216
217             // The second of these is needed to make the link work correctly
218             // when inserted into the page. The first is needed to match the
219             // route on the way in. Seems to be another Net_URL_Mapper bug to me.
220             $m->connect('search/notice/rss?q=:q', array('action' => 'noticesearchrss'),
221                         array('q' => '.+'));
222             $m->connect('search/notice/rss', array('action' => 'noticesearchrss'));
223
224             $m->connect('attachment/:attachment',
225                         array('action' => 'attachment'),
226                         array('attachment' => '[0-9]+'));
227
228             $m->connect('attachment/:attachment/thumbnail',
229                         array('action' => 'attachment_thumbnail'),
230                         array('attachment' => '[0-9]+'));
231
232             $m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
233                         array('action' => 'newnotice'),
234                         array('replyto' => Nickname::DISPLAY_FMT),
235                         array('inreplyto' => '[0-9]+'));
236
237             $m->connect('notice/new?replyto=:replyto',
238                         array('action' => 'newnotice'),
239                         array('replyto' => Nickname::DISPLAY_FMT));
240
241             $m->connect('notice/new', array('action' => 'newnotice'));
242
243             $m->connect('notice/:notice',
244                         array('action' => 'shownotice'),
245                         array('notice' => '[0-9]+'));
246
247             $m->connect('notice/delete/:notice',
248                         array('action' => 'deletenotice'),
249                         array('notice' => '[0-9]+'));
250
251             $m->connect('notice/delete', array('action' => 'deletenotice'));
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
772             // search
773             $m->connect('api/search.atom', array('action' => 'ApiSearchAtom'));
774             $m->connect('api/search.json', array('action' => 'ApiSearchJSON'));
775             $m->connect('api/trends.json', array('action' => 'ApiTrends'));
776
777             $m->connect('api/oauth/request_token',
778                         array('action' => 'ApiOAuthRequestToken'));
779
780             $m->connect('api/oauth/access_token',
781                         array('action' => 'ApiOAuthAccessToken'));
782
783             $m->connect('api/oauth/authorize',
784                         array('action' => 'ApiOAuthAuthorize'));
785
786             // Admin
787
788             $m->connect('panel/site', array('action' => 'siteadminpanel'));
789             $m->connect('panel/user', array('action' => 'useradminpanel'));
790             $m->connect('panel/access', array('action' => 'accessadminpanel'));
791             $m->connect('panel/paths', array('action' => 'pathsadminpanel'));
792             $m->connect('panel/sessions', array('action' => 'sessionsadminpanel'));
793             $m->connect('panel/sitenotice', array('action' => 'sitenoticeadminpanel'));
794             $m->connect('panel/license', array('action' => 'licenseadminpanel'));
795
796             $m->connect('panel/plugins', array('action' => 'pluginsadminpanel'));
797             $m->connect('panel/plugins/enable/:plugin',
798                         array('action' => 'pluginenable'),
799                         array('plugin' => '[A-Za-z0-9_]+'));
800             $m->connect('panel/plugins/disable/:plugin',
801                         array('action' => 'plugindisable'),
802                         array('plugin' => '[A-Za-z0-9_]+'));
803
804             $m->connect('getfile/:filename',
805                         array('action' => 'getfile'),
806                         array('filename' => '[A-Za-z0-9._-]+'));
807
808             // Common people-tag stuff
809
810             $m->connect('peopletag/:tag', array('action' => 'peopletag',
811                                                 'tag'    => self::REGEX_TAG));
812
813             $m->connect('selftag/:tag', array('action' => 'selftag',
814                                               'tag'    => self::REGEX_TAG));
815
816             $m->connect('main/addpeopletag', array('action' => 'addpeopletag'));
817
818             $m->connect('main/removepeopletag', array('action' => 'removepeopletag'));
819
820             $m->connect('main/profilecompletion', array('action' => 'profilecompletion'));
821
822             $m->connect('main/peopletagautocomplete', array('action' => 'peopletagautocomplete'));
823
824             // In the "root"
825
826             if (common_config('singleuser', 'enabled')) {
827
828                 $nickname = User::singleUserNickname();
829
830                 foreach (array('subscriptions', 'subscribers',
831                                'all', 'foaf', 'replies',
832                                'microsummary') as $a) {
833                     $m->connect($a,
834                                 array('action' => $a,
835                                       'nickname' => $nickname));
836                 }
837
838                 foreach (array('subscriptions', 'subscribers') as $a) {
839                     $m->connect($a.'/:tag',
840                                 array('action' => $a,
841                                       'nickname' => $nickname),
842                                 array('tag' => self::REGEX_TAG));
843                 }
844
845                 $m->connect('subscribers/pending',
846                             array('action' => 'subqueue',
847                                   'nickname' => $nickname));
848
849                 foreach (array('rss', 'groups') as $a) {
850                     $m->connect($a,
851                                 array('action' => 'user'.$a,
852                                       'nickname' => $nickname));
853                 }
854
855                 foreach (array('all', 'replies') as $a) {
856                     $m->connect($a.'/rss',
857                                 array('action' => $a.'rss',
858                                       'nickname' => $nickname));
859                 }
860
861                 $m->connect('avatar',
862                             array('action' => 'avatarbynickname',
863                                   'nickname' => $nickname));
864                 $m->connect('avatar/:size',
865                             array('action' => 'avatarbynickname',
866                                   'nickname' => $nickname),
867                             array('size' => '(|original|\d+)'));
868
869                 $m->connect('tag/:tag/rss',
870                             array('action' => 'userrss',
871                                   'nickname' => $nickname),
872                             array('tag' => self::REGEX_TAG));
873
874                 $m->connect('tag/:tag',
875                             array('action' => 'showstream',
876                                   'nickname' => $nickname),
877                             array('tag' => self::REGEX_TAG));
878
879                 $m->connect('rsd.xml',
880                             array('action' => 'rsd',
881                                   'nickname' => $nickname));
882
883                 // peopletags
884
885                 $m->connect('peopletags',
886                             array('action' => 'peopletagsbyuser'));
887
888                 $m->connect('peopletags/private',
889                             array('action' => 'peopletagsbyuser',
890                                   'private' => 1));
891
892                 $m->connect('peopletags/public',
893                             array('action' => 'peopletagsbyuser',
894                                   'public' => 1));
895
896                 $m->connect('othertags',
897                             array('action' => 'peopletagsforuser'));
898
899                 $m->connect('peopletagsubscriptions',
900                             array('action' => 'peopletagsubscriptions'));
901
902                 $m->connect('all/:tag/subscribers',
903                             array('action' => 'peopletagsubscribers',
904                                   'tag' => self::REGEX_TAG));
905
906                 $m->connect('all/:tag/tagged',
907                                 array('action' => 'peopletagged',
908                                       'tag' => self::REGEX_TAG));
909
910                 $m->connect('all/:tag/edit',
911                                 array('action' => 'editpeopletag',
912                                       'tag' => self::REGEX_TAG));
913
914                 foreach(array('subscribe', 'unsubscribe') as $v) {
915                     $m->connect('peopletag/:id/'.$v,
916                                     array('action' => $v.'peopletag',
917                                           'id' => '[0-9]{1,64}'));
918                 }
919                 $m->connect('user/:tagger_id/profiletag/:id/id',
920                                 array('action' => 'profiletagbyid',
921                                       'tagger_id' => '[0-9]+',
922                                       'id' => '[0-9]+'));
923
924                 $m->connect('all/:tag',
925                                 array('action' => 'showprofiletag',
926                                       'tag' => self::REGEX_TAG));
927
928                 foreach (array('subscriptions', 'subscribers') as $a) {
929                     $m->connect($a.'/:tag',
930                                 array('action' => $a),
931                                 array('tag' => self::REGEX_TAG));
932                 }
933             }
934
935             $m->connect('rss', array('action' => 'publicrss'));
936             $m->connect('featuredrss', array('action' => 'featuredrss'));
937             $m->connect('featured/', array('action' => 'featured'));
938             $m->connect('featured', array('action' => 'featured'));
939             $m->connect('rsd.xml', array('action' => 'rsd'));
940
941             foreach (array('subscriptions', 'subscribers',
942                            'nudge', 'all', 'foaf', 'replies',
943                            'inbox', 'outbox', 'microsummary') as $a) {
944                 $m->connect(':nickname/'.$a,
945                             array('action' => $a),
946                             array('nickname' => Nickname::DISPLAY_FMT));
947             }
948             $m->connect(':nickname/subscribers/pending',
949                         array('action' => 'subqueue'),
950                         array('nickname' => Nickname::DISPLAY_FMT));
951
952             // people tags
953
954             $m->connect(':nickname/peopletags',
955                             array('action' => 'peopletagsbyuser',
956                                   'nickname' => Nickname::DISPLAY_FMT));
957
958             $m->connect(':nickname/peopletags/private',
959                             array('action' => 'peopletagsbyuser',
960                                   'nickname' => Nickname::DISPLAY_FMT,
961                                   'private' => 1));
962
963             $m->connect(':nickname/peopletags/public',
964                             array('action' => 'peopletagsbyuser',
965                                   'nickname' => Nickname::DISPLAY_FMT,
966                                   'public' => 1));
967
968             $m->connect(':nickname/othertags',
969                             array('action' => 'peopletagsforuser',
970                                   'nickname' => Nickname::DISPLAY_FMT));
971
972             $m->connect(':nickname/peopletagsubscriptions',
973                             array('action' => 'peopletagsubscriptions',
974                                   'nickname' => Nickname::DISPLAY_FMT));
975
976             $m->connect(':tagger/all/:tag/subscribers',
977                             array('action' => 'peopletagsubscribers',
978                                   'tagger' => Nickname::DISPLAY_FMT,
979                                   'tag' => self::REGEX_TAG));
980
981             $m->connect(':tagger/all/:tag/tagged',
982                             array('action' => 'peopletagged',
983                                   'tagger' => Nickname::DISPLAY_FMT,
984                                   'tag' => self::REGEX_TAG));
985
986             $m->connect(':tagger/all/:tag/edit',
987                             array('action' => 'editpeopletag',
988                                   'tagger' => Nickname::DISPLAY_FMT,
989                                   'tag' => self::REGEX_TAG));
990
991             foreach(array('subscribe', 'unsubscribe') as $v) {
992                 $m->connect('peopletag/:id/'.$v,
993                                 array('action' => $v.'peopletag',
994                                       'id' => '[0-9]{1,64}'));
995             }
996             $m->connect('user/:tagger_id/profiletag/:id/id',
997                             array('action' => 'profiletagbyid',
998                                   'tagger_id' => '[0-9]+',
999                                   'id' => '[0-9]+'));
1000
1001             $m->connect(':tagger/all/:tag',
1002                             array('action' => 'showprofiletag',
1003                                   'tagger' => Nickname::DISPLAY_FMT,
1004                                   'tag' => self::REGEX_TAG));
1005
1006             foreach (array('subscriptions', 'subscribers') as $a) {
1007                 $m->connect(':nickname/'.$a.'/:tag',
1008                             array('action' => $a),
1009                             array('tag' => self::REGEX_TAG,
1010                                   'nickname' => Nickname::DISPLAY_FMT));
1011             }
1012
1013             foreach (array('rss', 'groups') as $a) {
1014                 $m->connect(':nickname/'.$a,
1015                             array('action' => 'user'.$a),
1016                             array('nickname' => Nickname::DISPLAY_FMT));
1017             }
1018
1019             foreach (array('all', 'replies') as $a) {
1020                 $m->connect(':nickname/'.$a.'/rss',
1021                             array('action' => $a.'rss'),
1022                             array('nickname' => Nickname::DISPLAY_FMT));
1023             }
1024
1025             $m->connect(':nickname/avatar',
1026                         array('action' => 'avatarbynickname'),
1027                         array('nickname' => Nickname::DISPLAY_FMT));
1028             $m->connect(':nickname/avatar/:size',
1029                         array('action' => 'avatarbynickname'),
1030                         array('size' => '(|original|\d+)',
1031                               'nickname' => Nickname::DISPLAY_FMT));
1032
1033             $m->connect(':nickname/tag/:tag/rss',
1034                         array('action' => 'userrss'),
1035                         array('nickname' => Nickname::DISPLAY_FMT),
1036                         array('tag' => self::REGEX_TAG));
1037
1038             $m->connect(':nickname/tag/:tag',
1039                         array('action' => 'showstream'),
1040                         array('nickname' => Nickname::DISPLAY_FMT),
1041                         array('tag' => self::REGEX_TAG));
1042
1043             $m->connect(':nickname/rsd.xml',
1044                         array('action' => 'rsd'),
1045                         array('nickname' => Nickname::DISPLAY_FMT));
1046
1047             $m->connect(':nickname',
1048                         array('action' => 'showstream'),
1049                         array('nickname' => Nickname::DISPLAY_FMT));
1050
1051             $m->connect(':nickname/',
1052                         array('action' => 'showstream'),
1053                         array('nickname' => Nickname::DISPLAY_FMT));
1054
1055             // AtomPub API
1056
1057             $m->connect('api/statusnet/app/service/:id.xml',
1058                         array('action' => 'ApiAtomService'),
1059                         array('id' => Nickname::DISPLAY_FMT));
1060
1061             $m->connect('api/statusnet/app/service.xml',
1062                         array('action' => 'ApiAtomService'));
1063
1064             $m->connect('api/statusnet/app/subscriptions/:subscriber/:subscribed.atom',
1065                         array('action' => 'AtomPubShowSubscription'),
1066                         array('subscriber' => '[0-9]+',
1067                               'subscribed' => '[0-9]+'));
1068
1069             $m->connect('api/statusnet/app/subscriptions/:subscriber.atom',
1070                         array('action' => 'AtomPubSubscriptionFeed'),
1071                         array('subscriber' => '[0-9]+'));
1072
1073             $m->connect('api/statusnet/app/memberships/:profile/:group.atom',
1074                         array('action' => 'AtomPubShowMembership'),
1075                         array('profile' => '[0-9]+',
1076                               'group' => '[0-9]+'));
1077
1078             $m->connect('api/statusnet/app/memberships/:profile.atom',
1079                         array('action' => 'AtomPubMembershipFeed'),
1080                         array('profile' => '[0-9]+'));
1081
1082             // URL shortening
1083
1084             $m->connect('url/:id',
1085                         array('action' => 'redirecturl',
1086                               'id' => '[0-9]+'));
1087
1088             // user stuff
1089
1090             Event::handle('RouterInitialized', array($m));
1091         }
1092
1093         return $m;
1094     }
1095
1096     function map($path)
1097     {
1098         try {
1099             $match = $this->m->match($path);
1100         } catch (Exception $e) {
1101             common_log(LOG_ERR, "Problem getting route for $path - " .
1102                        $e->getMessage());
1103             // TRANS: Client error on action trying to visit a non-existing page.
1104             $cac = new ClientErrorAction(_('Page not found.'), 404);
1105             $cac->showPage();
1106         }
1107
1108         return $match;
1109     }
1110
1111     function build($action, $args=null, $params=null, $fragment=null)
1112     {
1113         $action_arg = array('action' => $action);
1114
1115         if ($args) {
1116             $args = array_merge($action_arg, $args);
1117         } else {
1118             $args = $action_arg;
1119         }
1120
1121         $url = $this->m->generate($args, $params, $fragment);
1122         // Due to a bug in the Net_URL_Mapper code, the returned URL may
1123         // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
1124         // repair that here rather than modifying the upstream code...
1125
1126         $qpos = strpos($url, '?');
1127         if ($qpos !== false) {
1128             $url = substr($url, 0, $qpos+1) .
1129                 str_replace('?', '&', substr($url, $qpos+1));
1130
1131             // @fixme this is a hacky workaround for http_build_query in the
1132             // lower-level code and bad configs that set the default separator
1133             // to &amp; instead of &. Encoded &s in parameters will not be
1134             // affected.
1135             $url = substr($url, 0, $qpos+1) .
1136                 str_replace('&amp;', '&', substr($url, $qpos+1));
1137
1138         }
1139
1140         return $url;
1141     }
1142 }