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