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