]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/router.php
Merge commit 'refs/merge-requests/30' of https://gitorious.org/social/mainline into...
[quix0rs-gnu-social.git] / lib / router.php
1 <?php
2 /**
3  * StatusNet, the distributed open-source microblogging tool
4  *
5  * URL routing utilities
6  *
7  * PHP version 5
8  *
9  * LICENCE: This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU Affero General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Affero General Public License for more details.
18  *
19  * You should have received a copy of the GNU Affero General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  * @category  URL
23  * @package   StatusNet
24  * @author    Evan Prodromou <evan@status.net>
25  * @copyright 2009 StatusNet, Inc.
26  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
27  * @link      http://status.net/
28  */
29
30 if (!defined('STATUSNET') && !defined('LACONICA')) {
31     exit(1);
32 }
33
34 /**
35  * URL Router
36  *
37  * Cheap wrapper around Net_URL_Mapper
38  *
39  * @category URL
40  * @package  StatusNet
41  * @author   Evan Prodromou <evan@status.net>
42  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
43  * @link     http://status.net/
44  */
45 class Router
46 {
47     var $m = null;
48     static $inst = null;
49
50     const REGEX_TAG = '[^\/]+'; // [\pL\pN_\-\.]{1,64} better if we can do unicode regexes
51
52     static function get()
53     {
54         if (!Router::$inst) {
55             Router::$inst = new Router();
56         }
57         return Router::$inst;
58     }
59
60     /**
61      * Clear the global singleton instance for this class.
62      * Needed to ensure reset when switching site configurations.
63      */
64     static function clear()
65     {
66         Router::$inst = null;
67     }
68
69     function __construct()
70     {
71         if (empty($this->m)) {
72             $this->m = $this->initialize();
73         }
74     }
75
76     /**
77      * Create a unique hashkey for the router.
78      *
79      * The router's url map can change based on the version of the software
80      * you're running and the plugins that are enabled. To avoid having bad routes
81      * get stuck in the cache, the key includes a list of plugins and the software
82      * version.
83      * 
84     * There can still be problems with a) differences in versions of the plugins and
85      * b) people running code between official versions, but these tend to be more
86      * sophisticated users who can grok what's going on and clear their caches.
87      *
88      * @return string cache key string that should uniquely identify a router
89      */
90
91     static function cacheKey()
92     {
93         $parts = array('router');
94
95         // Many router paths depend on this setting.
96         if (common_config('singleuser', 'enabled')) {
97             $parts[] = '1user';
98         } else {
99             $parts[] = 'multi';
100         }
101
102         return Cache::codeKey(implode(':', $parts));
103     }
104
105     function initialize()
106     {
107         $m = new URLMapper();
108
109         if (Event::handle('StartInitializeRouter', array(&$m))) {
110
111             $m->connect('robots.txt', array('action' => 'robotstxt'));
112
113             $m->connect('opensearch/people', array('action' => 'opensearch',
114                                                    'type' => 'people'));
115             $m->connect('opensearch/notice', array('action' => 'opensearch',
116                                                    'type' => 'notice'));
117
118             // docs
119
120             $m->connect('doc/:title', array('action' => 'doc'));
121
122             $m->connect('main/otp/:user_id/:token',
123                         array('action' => 'otp'),
124                         array('user_id' => '[0-9]+',
125                               'token' => '.+'));
126
127             // these take a code; before the main part
128
129             foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) {
130                 $m->connect('main/'.$c.'/:code', array('action' => $c));
131             }
132
133             // Also need a block variant accepting ID on URL for mail links
134             $m->connect('main/block/:profileid',
135                         array('action' => 'block'),
136                         array('profileid' => '[0-9]+'));
137
138             $m->connect('main/sup/:seconds', array('action' => 'sup'),
139                         array('seconds' => '[0-9]+'));
140
141             // main stuff is repetitive
142
143             $main = array('login', 'logout', 'register', 'subscribe',
144                           'unsubscribe', 'cancelsubscription', 'approvesub',
145                           'confirmaddress', 'recoverpassword',
146                           'invite', 'sup',
147                           'block', 'unblock', 'subedit',
148                           'groupblock', 'groupunblock',
149                           'sandbox', 'unsandbox',
150                           'silence', 'unsilence',
151                           'grantrole', 'revokerole',
152                           'repeat',
153                           'deleteuser',
154                           'geocode',
155                           'version',
156                           'backupaccount',
157                           'deleteaccount',
158                           'restoreaccount',
159                           'top',
160             );
161
162             foreach ($main as $a) {
163                 $m->connect('main/'.$a, array('action' => $a));
164             }
165
166             $m->connect('main/public', array('action' => 'public'));
167             $m->connect('main/all', array('action' => 'networkpublic'));
168
169             $m->connect('main/tagprofile/:id', array('action' => 'tagprofile'),
170                                                array('id' => '[0-9]+'));
171
172             $m->connect('main/tagprofile', array('action' => 'tagprofile'));
173
174             $m->connect('main/xrds',
175                         array('action' => 'publicxrds'));
176
177             // settings
178
179             foreach (array('profile', 'avatar', 'password', 'im', 'oauthconnections',
180                            'oauthapps', 'email', 'sms', 'url') as $s) {
181                 $m->connect('settings/'.$s, array('action' => $s.'settings'));
182             }
183
184             if (common_config('oldschool', 'enabled')) {
185                 $m->connect('settings/oldschool', array('action' => 'oldschoolsettings'));
186             }
187
188             $m->connect('settings/oauthapps/show/:id',
189                         array('action' => 'showapplication'),
190                         array('id' => '[0-9]+')
191             );
192             $m->connect('settings/oauthapps/new',
193                         array('action' => 'newapplication')
194             );
195             $m->connect('settings/oauthapps/edit/:id',
196                         array('action' => 'editapplication'),
197                         array('id' => '[0-9]+')
198             );
199             $m->connect('settings/oauthapps/delete/:id',
200                         array('action' => 'deleteapplication'),
201                         array('id' => '[0-9]+')
202             );
203
204             // search
205
206             foreach (array('group', 'people', 'notice') as $s) {
207                 $m->connect('search/'.$s.'?q=:q',
208                             array('action' => $s.'search'),
209                             array('q' => '.+'));
210                 $m->connect('search/'.$s, array('action' => $s.'search'));
211             }
212
213             // The second of these is needed to make the link work correctly
214             // when inserted into the page. The first is needed to match the
215             // route on the way in. Seems to be another Net_URL_Mapper bug to me.
216             $m->connect('search/notice/rss?q=:q', array('action' => 'noticesearchrss'),
217                         array('q' => '.+'));
218             $m->connect('search/notice/rss', array('action' => 'noticesearchrss'));
219
220             $m->connect('attachment/:attachment',
221                         array('action' => 'attachment'),
222                         array('attachment' => '[0-9]+'));
223
224             $m->connect('attachment/:attachment/thumbnail',
225                         array('action' => 'attachment_thumbnail'),
226                         array('attachment' => '[0-9]+'));
227
228             $m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
229                         array('action' => 'newnotice'),
230                         array('replyto' => Nickname::DISPLAY_FMT),
231                         array('inreplyto' => '[0-9]+'));
232
233             $m->connect('notice/new?replyto=:replyto',
234                         array('action' => 'newnotice'),
235                         array('replyto' => Nickname::DISPLAY_FMT));
236
237             $m->connect('notice/new', array('action' => 'newnotice'));
238
239             $m->connect('notice/:notice',
240                         array('action' => 'shownotice'),
241                         array('notice' => '[0-9]+'));
242
243             $m->connect('notice/delete/:notice',
244                         array('action' => 'deletenotice'),
245                         array('notice' => '[0-9]+'));
246
247             $m->connect('notice/delete', array('action' => 'deletenotice'));
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/retweeted_by_me.:format',
397                         array('action' => 'ApiTimelineRetweetedByMe',
398                               'format' => '(xml|json|atom|as)'));
399
400             $m->connect('api/statuses/retweeted_to_me.:format',
401                         array('action' => 'ApiTimelineRetweetedToMe',
402                               'format' => '(xml|json|atom|as)'));
403
404             $m->connect('api/statuses/retweets_of_me.:format',
405                         array('action' => 'ApiTimelineRetweetsOfMe',
406                               'format' => '(xml|json|atom|as)'));
407
408             $m->connect('api/statuses/friends/:id.:format',
409                         array('action' => 'ApiUserFriends',
410                               'id' => Nickname::INPUT_FMT,
411                               'format' => '(xml|json)'));
412
413             $m->connect('api/statuses/friends.:format',
414                         array('action' => 'ApiUserFriends',
415                               'format' => '(xml|json)'));
416
417             $m->connect('api/statuses/followers/:id.:format',
418                         array('action' => 'ApiUserFollowers',
419                               'id' => Nickname::INPUT_FMT,
420                               'format' => '(xml|json)'));
421
422             $m->connect('api/statuses/followers.:format',
423                         array('action' => 'ApiUserFollowers',
424                               'format' => '(xml|json)'));
425
426             $m->connect('api/statuses/show/:id.:format',
427                         array('action' => 'ApiStatusesShow',
428                               'id' => '[0-9]+',
429                               'format' => '(xml|json|atom)'));
430
431             $m->connect('api/statuses/show.:format',
432                         array('action' => 'ApiStatusesShow',
433                               'format' => '(xml|json|atom)'));
434
435             $m->connect('api/statuses/update.:format',
436                         array('action' => 'ApiStatusesUpdate',
437                               'format' => '(xml|json)'));
438
439             $m->connect('api/statuses/destroy/:id.:format',
440                         array('action' => 'ApiStatusesDestroy',
441                               'id' => '[0-9]+',
442                               'format' => '(xml|json)'));
443
444             $m->connect('api/statuses/destroy.:format',
445                         array('action' => 'ApiStatusesDestroy',
446                               'format' => '(xml|json)'));
447
448             $m->connect('api/statuses/retweet/:id.:format',
449                         array('action' => 'ApiStatusesRetweet',
450                               'id' => '[0-9]+',
451                               'format' => '(xml|json)'));
452
453             $m->connect('api/statuses/retweets/:id.:format',
454                         array('action' => 'ApiStatusesRetweets',
455                               'id' => '[0-9]+',
456                               'format' => '(xml|json)'));
457
458             // START qvitter API additions
459             
460             $m->connect('api/attachment/:id.:format',
461                         array('action' => 'ApiAttachment',
462                               'id' => '[0-9]+',
463                               'format' => '(xml|json)'));
464             
465             $m->connect('api/checkhub.:format',
466                         array('action' => 'ApiCheckHub',
467                               'format' => '(xml|json)'));
468             
469             $m->connect('api/externalprofile/show.:format',
470                         array('action' => 'ApiExternalProfileShow',
471                               'format' => '(xml|json)'));
472
473             $m->connect('api/statusnet/groups/admins/:id.:format',
474                         array('action' => 'ApiGroupAdmins',
475                               'id' => Nickname::INPUT_FMT,
476                               'format' => '(xml|json)'));
477             
478             $m->connect('api/account/update_link_color.:format',
479                         array('action' => 'ApiAccountUpdateLinkColor',
480                               'format' => '(xml|json)'));
481                 
482             $m->connect('api/account/update_background_color.:format',
483                         array('action' => 'ApiAccountUpdateBackgroundColor',
484                               'format' => '(xml|json)'));
485
486             $m->connect('api/account/register.:format',
487                         array('action' => 'ApiAccountRegister',
488                               'format' => '(xml|json)'));
489             
490             $m->connect('api/check_nickname.:format',
491                         array('action' => 'ApiCheckNickname',
492                               'format' => '(xml|json)'));
493
494             // END qvitter API additions
495
496             // users
497
498             $m->connect('api/users/show/:id.:format',
499                         array('action' => 'ApiUserShow',
500                               'id' => Nickname::INPUT_FMT,
501                               'format' => '(xml|json)'));
502
503             $m->connect('api/users/show.:format',
504                         array('action' => 'ApiUserShow',
505                               'format' => '(xml|json)'));
506
507             $m->connect('api/users/profile_image/:screen_name.:format',
508                         array('action' => 'ApiUserProfileImage',
509                               'screen_name' => Nickname::DISPLAY_FMT,
510                               'format' => '(xml|json)'));
511
512             // friendships
513
514             $m->connect('api/friendships/show.:format',
515                         array('action' => 'ApiFriendshipsShow',
516                               'format' => '(xml|json)'));
517
518             $m->connect('api/friendships/exists.:format',
519                         array('action' => 'ApiFriendshipsExists',
520                               'format' => '(xml|json)'));
521
522             $m->connect('api/friendships/create/:id.:format',
523                         array('action' => 'ApiFriendshipsCreate',
524                               'id' => Nickname::INPUT_FMT,
525                               'format' => '(xml|json)'));
526
527             $m->connect('api/friendships/create.:format',
528                         array('action' => 'ApiFriendshipsCreate',
529                               'format' => '(xml|json)'));
530
531             $m->connect('api/friendships/destroy/:id.:format',
532                         array('action' => 'ApiFriendshipsDestroy',
533                               'id' => Nickname::INPUT_FMT,
534                               'format' => '(xml|json)'));
535
536             $m->connect('api/friendships/destroy.:format',
537                         array('action' => 'ApiFriendshipsDestroy',
538                               'format' => '(xml|json)'));
539
540             // Social graph
541
542             $m->connect('api/friends/ids/:id.:format',
543                         array('action' => 'ApiUserFriends',
544                               'ids_only' => true));
545
546             $m->connect('api/followers/ids/:id.:format',
547                         array('action' => 'ApiUserFollowers',
548                               'ids_only' => true));
549
550             $m->connect('api/friends/ids.:format',
551                         array('action' => 'ApiUserFriends',
552                               'ids_only' => true));
553
554             $m->connect('api/followers/ids.:format',
555                         array('action' => 'ApiUserFollowers',
556                               'ids_only' => true));
557
558             // account
559
560             $m->connect('api/account/verify_credentials.:format',
561                         array('action' => 'ApiAccountVerifyCredentials'));
562
563             $m->connect('api/account/update_profile.:format',
564                         array('action' => 'ApiAccountUpdateProfile'));
565
566             $m->connect('api/account/update_profile_image.:format',
567                         array('action' => 'ApiAccountUpdateProfileImage'));
568
569             $m->connect('api/account/update_delivery_device.:format',
570                         array('action' => 'ApiAccountUpdateDeliveryDevice'));
571
572             // special case where verify_credentials is called w/out a format
573
574             $m->connect('api/account/verify_credentials',
575                         array('action' => 'ApiAccountVerifyCredentials'));
576
577             $m->connect('api/account/rate_limit_status.:format',
578                         array('action' => 'ApiAccountRateLimitStatus'));
579
580             // blocks
581
582             $m->connect('api/blocks/create/:id.:format',
583                         array('action' => 'ApiBlockCreate',
584                               'id' => Nickname::INPUT_FMT,
585                               'format' => '(xml|json)'));
586
587             $m->connect('api/blocks/create.:format',
588                         array('action' => 'ApiBlockCreate',
589                               'format' => '(xml|json)'));
590
591             $m->connect('api/blocks/destroy/:id.:format',
592                         array('action' => 'ApiBlockDestroy',
593                               'id' => Nickname::INPUT_FMT,
594                               'format' => '(xml|json)'));
595
596             $m->connect('api/blocks/destroy.:format',
597                         array('action' => 'ApiBlockDestroy',
598                               'format' => '(xml|json)'));
599
600             // help
601
602             $m->connect('api/help/test.:format',
603                         array('action' => 'ApiHelpTest',
604                               'format' => '(xml|json)'));
605
606             // statusnet
607
608             $m->connect('api/statusnet/version.:format',
609                         array('action' => 'ApiGNUsocialVersion',
610                               'format' => '(xml|json)'));
611
612             $m->connect('api/statusnet/config.:format',
613                         array('action' => 'ApiGNUsocialConfig',
614                               'format' => '(xml|json)'));
615
616             // For our current software name, we provide "gnusocial" base action
617
618             $m->connect('api/gnusocial/version.:format',
619                         array('action' => 'ApiGNUsocialVersion',
620                               'format' => '(xml|json)'));
621
622             $m->connect('api/gnusocial/config.:format',
623                         array('action' => 'ApiGNUsocialConfig',
624                               'format' => '(xml|json)'));
625
626             // Groups and tags are newer than 0.8.1 so no backward-compatibility
627             // necessary
628
629             // Groups
630             //'list' has to be handled differently, as php will not allow a method to be named 'list'
631
632             $m->connect('api/statusnet/groups/timeline/:id.:format',
633                         array('action' => 'ApiTimelineGroup',
634                               'id' => Nickname::INPUT_FMT,
635                               'format' => '(xml|json|rss|atom|as)'));
636
637             $m->connect('api/statusnet/groups/show/:id.:format',
638                         array('action' => 'ApiGroupShow',
639                               'id' => Nickname::INPUT_FMT,
640                               'format' => '(xml|json)'));
641
642             $m->connect('api/statusnet/groups/show.:format',
643                         array('action' => 'ApiGroupShow',
644                               'format' => '(xml|json)'));
645
646             $m->connect('api/statusnet/groups/join/:id.:format',
647                         array('action' => 'ApiGroupJoin',
648                               'id' => Nickname::INPUT_FMT,
649                               'format' => '(xml|json)'));
650
651             $m->connect('api/statusnet/groups/join.:format',
652                         array('action' => 'ApiGroupJoin',
653                               'format' => '(xml|json)'));
654
655             $m->connect('api/statusnet/groups/leave/:id.:format',
656                         array('action' => 'ApiGroupLeave',
657                               'format' => '(xml|json)'));
658
659             $m->connect('api/statusnet/groups/leave.:format',
660                         array('action' => 'ApiGroupLeave',
661                               'id' => Nickname::INPUT_FMT,
662                               'format' => '(xml|json)'));
663
664             $m->connect('api/statusnet/groups/is_member.:format',
665                         array('action' => 'ApiGroupIsMember',
666                               'format' => '(xml|json)'));
667
668             $m->connect('api/statusnet/groups/list/:id.:format',
669                         array('action' => 'ApiGroupList',
670                               'id' => Nickname::INPUT_FMT,
671                               'format' => '(xml|json|rss|atom)'));
672
673             $m->connect('api/statusnet/groups/list.:format',
674                         array('action' => 'ApiGroupList',
675                               'format' => '(xml|json|rss|atom)'));
676
677             $m->connect('api/statusnet/groups/list_all.:format',
678                         array('action' => 'ApiGroupListAll',
679                               'format' => '(xml|json|rss|atom)'));
680
681             $m->connect('api/statusnet/groups/membership/:id.:format',
682                         array('action' => 'ApiGroupMembership',
683                               'id' => Nickname::INPUT_FMT,
684                               'format' => '(xml|json)'));
685
686             $m->connect('api/statusnet/groups/membership.:format',
687                         array('action' => 'ApiGroupMembership',
688                               'format' => '(xml|json)'));
689
690             $m->connect('api/statusnet/groups/create.:format',
691                         array('action' => 'ApiGroupCreate',
692                               'format' => '(xml|json)'));
693
694             $m->connect('api/statusnet/groups/update/:id.:format',
695                         array('action' => 'ApiGroupProfileUpdate',
696                               'id' => '[a-zA-Z0-9]+',
697                               'format' => '(xml|json)'));
698                               
699             $m->connect('api/statusnet/conversation/:id.:format',
700                         array('action' => 'apiconversation',
701                               'id' => '[0-9]+',
702                               'format' => '(xml|json|rss|atom|as)'));
703
704             // Lists (people tags)
705             $m->connect('api/lists/list.:format',
706                         array('action' => 'ApiListSubscriptions',
707                               'format' => '(xml|json)'));
708
709             $m->connect('api/lists/memberships.:format',
710                         array('action' => 'ApiListMemberships',
711                               'format' => '(xml|json)'));
712
713             $m->connect('api/:user/lists/memberships.:format',
714                         array('action' => 'ApiListMemberships',
715                               'user' => '[a-zA-Z0-9]+',
716                               'format' => '(xml|json)'));
717
718             $m->connect('api/lists/subscriptions.:format',
719                         array('action' => 'ApiListSubscriptions',
720                               'format' => '(xml|json)'));
721
722             $m->connect('api/:user/lists/subscriptions.:format',
723                         array('action' => 'ApiListSubscriptions',
724                               'user' => '[a-zA-Z0-9]+',
725                               'format' => '(xml|json)'));
726
727             $m->connect('api/lists.:format',
728                         array('action' => 'ApiLists',
729                               'format' => '(xml|json)'));
730
731             $m->connect('api/:user/lists/:id.:format',
732                         array('action' => 'ApiList',
733                               'user' => '[a-zA-Z0-9]+',
734                               'id' => '[a-zA-Z0-9]+',
735                               'format' => '(xml|json)'));
736
737             $m->connect('api/:user/lists.:format',
738                         array('action' => 'ApiLists',
739                               'user' => '[a-zA-Z0-9]+',
740                               'format' => '(xml|json)'));
741
742             $m->connect('api/:user/lists/:id/statuses.:format',
743                         array('action' => 'ApiTimelineList',
744                               'user' => '[a-zA-Z0-9]+',
745                               'id' => '[a-zA-Z0-9]+',
746                               'format' => '(xml|json|rss|atom)'));
747
748             $m->connect('api/:user/:list_id/members/:id.:format',
749                         array('action' => 'ApiListMember',
750                               'user' => '[a-zA-Z0-9]+',
751                               'list_id' => '[a-zA-Z0-9]+',
752                               'id' => '[a-zA-Z0-9]+',
753                               'format' => '(xml|json)'));
754
755             $m->connect('api/:user/:list_id/members.:format',
756                         array('action' => 'ApiListMembers',
757                               'user' => '[a-zA-Z0-9]+',
758                               'list_id' => '[a-zA-Z0-9]+',
759                               'format' => '(xml|json)'));
760
761             $m->connect('api/:user/:list_id/subscribers/:id.:format',
762                         array('action' => 'ApiListSubscriber',
763                               'user' => '[a-zA-Z0-9]+',
764                               'list_id' => '[a-zA-Z0-9]+',
765                               'id' => '[a-zA-Z0-9]+',
766                               'format' => '(xml|json)'));
767
768             $m->connect('api/:user/:list_id/subscribers.:format',
769                         array('action' => 'ApiListSubscribers',
770                               'user' => '[a-zA-Z0-9]+',
771                               'list_id' => '[a-zA-Z0-9]+',
772                               'format' => '(xml|json)'));
773
774             // Tags
775             $m->connect('api/statusnet/tags/timeline/:tag.:format',
776                         array('action' => 'ApiTimelineTag',
777                               'tag'    => self::REGEX_TAG,
778                               'format' => '(xml|json|rss|atom|as)'));
779
780             // media related
781             $m->connect(
782                 'api/statusnet/media/upload',
783                 array('action' => 'ApiMediaUpload')
784             );
785             $m->connect(
786                 'api/statuses/update_with_media.json',
787                 array('action' => 'ApiMediaUpload')
788             );
789
790             // search
791             $m->connect('api/search.atom', array('action' => 'ApiSearchAtom'));
792             $m->connect('api/search.json', array('action' => 'ApiSearchJSON'));
793             $m->connect('api/trends.json', array('action' => 'ApiTrends'));
794
795             $m->connect('api/oauth/request_token',
796                         array('action' => 'ApiOAuthRequestToken'));
797
798             $m->connect('api/oauth/access_token',
799                         array('action' => 'ApiOAuthAccessToken'));
800
801             $m->connect('api/oauth/authorize',
802                         array('action' => 'ApiOAuthAuthorize'));
803
804             // Admin
805
806             $m->connect('panel/site', array('action' => 'siteadminpanel'));
807             $m->connect('panel/user', array('action' => 'useradminpanel'));
808             $m->connect('panel/access', array('action' => 'accessadminpanel'));
809             $m->connect('panel/paths', array('action' => 'pathsadminpanel'));
810             $m->connect('panel/sessions', array('action' => 'sessionsadminpanel'));
811             $m->connect('panel/sitenotice', array('action' => 'sitenoticeadminpanel'));
812             $m->connect('panel/license', array('action' => 'licenseadminpanel'));
813
814             $m->connect('panel/plugins', array('action' => 'pluginsadminpanel'));
815             $m->connect('panel/plugins/enable/:plugin',
816                         array('action' => 'pluginenable'),
817                         array('plugin' => '[A-Za-z0-9_]+'));
818             $m->connect('panel/plugins/disable/:plugin',
819                         array('action' => 'plugindisable'),
820                         array('plugin' => '[A-Za-z0-9_]+'));
821
822             $m->connect('getfile/:filename',
823                         array('action' => 'getfile'),
824                         array('filename' => '[A-Za-z0-9._-]+'));
825
826             // Common people-tag stuff
827
828             $m->connect('peopletag/:tag', array('action' => 'peopletag',
829                                                 'tag'    => self::REGEX_TAG));
830
831             $m->connect('selftag/:tag', array('action' => 'selftag',
832                                               'tag'    => self::REGEX_TAG));
833
834             $m->connect('main/addpeopletag', array('action' => 'addpeopletag'));
835
836             $m->connect('main/removepeopletag', array('action' => 'removepeopletag'));
837
838             $m->connect('main/profilecompletion', array('action' => 'profilecompletion'));
839
840             $m->connect('main/peopletagautocomplete', array('action' => 'peopletagautocomplete'));
841
842             // In the "root"
843
844             if (common_config('singleuser', 'enabled')) {
845
846                 $nickname = User::singleUserNickname();
847
848                 foreach (array('subscriptions', 'subscribers',
849                                'all', 'foaf', 'replies',
850                                'microsummary') as $a) {
851                     $m->connect($a,
852                                 array('action' => $a,
853                                       'nickname' => $nickname));
854                 }
855
856                 foreach (array('subscriptions', 'subscribers') as $a) {
857                     $m->connect($a.'/:tag',
858                                 array('action' => $a,
859                                       'nickname' => $nickname),
860                                 array('tag' => self::REGEX_TAG));
861                 }
862
863                 $m->connect('subscribers/pending',
864                             array('action' => 'subqueue',
865                                   'nickname' => $nickname));
866
867                 foreach (array('rss', 'groups') as $a) {
868                     $m->connect($a,
869                                 array('action' => 'user'.$a,
870                                       'nickname' => $nickname));
871                 }
872
873                 foreach (array('all', 'replies') as $a) {
874                     $m->connect($a.'/rss',
875                                 array('action' => $a.'rss',
876                                       'nickname' => $nickname));
877                 }
878
879                 $m->connect('avatar',
880                             array('action' => 'avatarbynickname',
881                                   'nickname' => $nickname));
882                 $m->connect('avatar/:size',
883                             array('action' => 'avatarbynickname',
884                                   'nickname' => $nickname),
885                             array('size' => '(|original|\d+)'));
886
887                 $m->connect('tag/:tag/rss',
888                             array('action' => 'userrss',
889                                   'nickname' => $nickname),
890                             array('tag' => self::REGEX_TAG));
891
892                 $m->connect('tag/:tag',
893                             array('action' => 'showstream',
894                                   'nickname' => $nickname),
895                             array('tag' => self::REGEX_TAG));
896
897                 $m->connect('rsd.xml',
898                             array('action' => 'rsd',
899                                   'nickname' => $nickname));
900
901                 $m->connect('',
902                             array('action' => 'startpage'));
903
904                 // peopletags
905
906                 $m->connect('peopletags',
907                             array('action' => 'peopletagsbyuser'));
908
909                 $m->connect('peopletags/private',
910                             array('action' => 'peopletagsbyuser',
911                                   'private' => 1));
912
913                 $m->connect('peopletags/public',
914                             array('action' => 'peopletagsbyuser',
915                                   'public' => 1));
916
917                 $m->connect('othertags',
918                             array('action' => 'peopletagsforuser'));
919
920                 $m->connect('peopletagsubscriptions',
921                             array('action' => 'peopletagsubscriptions'));
922
923                 $m->connect('all/:tag/subscribers',
924                             array('action' => 'peopletagsubscribers',
925                                   'tag' => self::REGEX_TAG));
926
927                 $m->connect('all/:tag/tagged',
928                                 array('action' => 'peopletagged',
929                                       'tag' => self::REGEX_TAG));
930
931                 $m->connect('all/:tag/edit',
932                                 array('action' => 'editpeopletag',
933                                       'tag' => self::REGEX_TAG));
934
935                 foreach(array('subscribe', 'unsubscribe') as $v) {
936                     $m->connect('peopletag/:id/'.$v,
937                                     array('action' => $v.'peopletag',
938                                           'id' => '[0-9]{1,64}'));
939                 }
940                 $m->connect('user/:tagger_id/profiletag/:id/id',
941                                 array('action' => 'profiletagbyid',
942                                       'tagger_id' => '[0-9]+',
943                                       'id' => '[0-9]+'));
944
945                 $m->connect('all/:tag',
946                                 array('action' => 'showprofiletag',
947                                       'tag' => self::REGEX_TAG));
948
949                 foreach (array('subscriptions', 'subscribers') as $a) {
950                     $m->connect($a.'/:tag',
951                                 array('action' => $a),
952                                 array('tag' => self::REGEX_TAG));
953                 }
954             }
955
956             $m->connect('', array('action' => 'startpage'));
957             $m->connect('main/public', array('action' => 'public'));
958             $m->connect('main/all', array('action' => 'networkpublic'));
959             $m->connect('rss', array('action' => 'publicrss'));
960             $m->connect('featuredrss', array('action' => 'featuredrss'));
961             $m->connect('featured/', array('action' => 'featured'));
962             $m->connect('featured', array('action' => 'featured'));
963             $m->connect('rsd.xml', array('action' => 'rsd'));
964
965             foreach (array('subscriptions', 'subscribers',
966                            'nudge', 'all', 'foaf', 'replies',
967                            'inbox', 'outbox', 'microsummary') as $a) {
968                 $m->connect(':nickname/'.$a,
969                             array('action' => $a),
970                             array('nickname' => Nickname::DISPLAY_FMT));
971             }
972             $m->connect(':nickname/subscribers/pending',
973                         array('action' => 'subqueue'),
974                         array('nickname' => Nickname::DISPLAY_FMT));
975
976             // people tags
977
978             $m->connect(':nickname/peopletags',
979                             array('action' => 'peopletagsbyuser',
980                                   'nickname' => Nickname::DISPLAY_FMT));
981
982             $m->connect(':nickname/peopletags/private',
983                             array('action' => 'peopletagsbyuser',
984                                   'nickname' => Nickname::DISPLAY_FMT,
985                                   'private' => 1));
986
987             $m->connect(':nickname/peopletags/public',
988                             array('action' => 'peopletagsbyuser',
989                                   'nickname' => Nickname::DISPLAY_FMT,
990                                   'public' => 1));
991
992             $m->connect(':nickname/othertags',
993                             array('action' => 'peopletagsforuser',
994                                   'nickname' => Nickname::DISPLAY_FMT));
995
996             $m->connect(':nickname/peopletagsubscriptions',
997                             array('action' => 'peopletagsubscriptions',
998                                   'nickname' => Nickname::DISPLAY_FMT));
999
1000             $m->connect(':tagger/all/:tag/subscribers',
1001                             array('action' => 'peopletagsubscribers',
1002                                   'tagger' => Nickname::DISPLAY_FMT,
1003                                   'tag' => self::REGEX_TAG));
1004
1005             $m->connect(':tagger/all/:tag/tagged',
1006                             array('action' => 'peopletagged',
1007                                   'tagger' => Nickname::DISPLAY_FMT,
1008                                   'tag' => self::REGEX_TAG));
1009
1010             $m->connect(':tagger/all/:tag/edit',
1011                             array('action' => 'editpeopletag',
1012                                   'tagger' => Nickname::DISPLAY_FMT,
1013                                   'tag' => self::REGEX_TAG));
1014
1015             foreach(array('subscribe', 'unsubscribe') as $v) {
1016                 $m->connect('peopletag/:id/'.$v,
1017                                 array('action' => $v.'peopletag',
1018                                       'id' => '[0-9]{1,64}'));
1019             }
1020             $m->connect('user/:tagger_id/profiletag/:id/id',
1021                             array('action' => 'profiletagbyid',
1022                                   'tagger_id' => '[0-9]+',
1023                                   'id' => '[0-9]+'));
1024
1025             $m->connect(':tagger/all/:tag',
1026                             array('action' => 'showprofiletag',
1027                                   'tagger' => Nickname::DISPLAY_FMT,
1028                                   'tag' => self::REGEX_TAG));
1029
1030             foreach (array('subscriptions', 'subscribers') as $a) {
1031                 $m->connect(':nickname/'.$a.'/:tag',
1032                             array('action' => $a),
1033                             array('tag' => self::REGEX_TAG,
1034                                   'nickname' => Nickname::DISPLAY_FMT));
1035             }
1036
1037             foreach (array('rss', 'groups') as $a) {
1038                 $m->connect(':nickname/'.$a,
1039                             array('action' => 'user'.$a),
1040                             array('nickname' => Nickname::DISPLAY_FMT));
1041             }
1042
1043             foreach (array('all', 'replies') as $a) {
1044                 $m->connect(':nickname/'.$a.'/rss',
1045                             array('action' => $a.'rss'),
1046                             array('nickname' => Nickname::DISPLAY_FMT));
1047             }
1048
1049             $m->connect(':nickname/avatar',
1050                         array('action' => 'avatarbynickname'),
1051                         array('nickname' => Nickname::DISPLAY_FMT));
1052             $m->connect(':nickname/avatar/:size',
1053                         array('action' => 'avatarbynickname'),
1054                         array('size' => '(|original|\d+)',
1055                               'nickname' => Nickname::DISPLAY_FMT));
1056
1057             $m->connect(':nickname/tag/:tag/rss',
1058                         array('action' => 'userrss'),
1059                         array('nickname' => Nickname::DISPLAY_FMT),
1060                         array('tag' => self::REGEX_TAG));
1061
1062             $m->connect(':nickname/tag/:tag',
1063                         array('action' => 'showstream'),
1064                         array('nickname' => Nickname::DISPLAY_FMT),
1065                         array('tag' => self::REGEX_TAG));
1066
1067             $m->connect(':nickname/rsd.xml',
1068                         array('action' => 'rsd'),
1069                         array('nickname' => Nickname::DISPLAY_FMT));
1070
1071             $m->connect(':nickname',
1072                         array('action' => 'showstream'),
1073                         array('nickname' => Nickname::DISPLAY_FMT));
1074
1075             $m->connect(':nickname/',
1076                         array('action' => 'showstream'),
1077                         array('nickname' => Nickname::DISPLAY_FMT));
1078
1079             // AtomPub API
1080
1081             $m->connect('api/statusnet/app/service/:id.xml',
1082                         array('action' => 'ApiAtomService'),
1083                         array('id' => Nickname::DISPLAY_FMT));
1084
1085             $m->connect('api/statusnet/app/service.xml',
1086                         array('action' => 'ApiAtomService'));
1087
1088             $m->connect('api/statusnet/app/subscriptions/:subscriber/:subscribed.atom',
1089                         array('action' => 'AtomPubShowSubscription'),
1090                         array('subscriber' => '[0-9]+',
1091                               'subscribed' => '[0-9]+'));
1092
1093             $m->connect('api/statusnet/app/subscriptions/:subscriber.atom',
1094                         array('action' => 'AtomPubSubscriptionFeed'),
1095                         array('subscriber' => '[0-9]+'));
1096
1097             $m->connect('api/statusnet/app/memberships/:profile/:group.atom',
1098                         array('action' => 'AtomPubShowMembership'),
1099                         array('profile' => '[0-9]+',
1100                               'group' => '[0-9]+'));
1101
1102             $m->connect('api/statusnet/app/memberships/:profile.atom',
1103                         array('action' => 'AtomPubMembershipFeed'),
1104                         array('profile' => '[0-9]+'));
1105
1106             // URL shortening
1107
1108             $m->connect('url/:id',
1109                         array('action' => 'redirecturl',
1110                               'id' => '[0-9]+'));
1111
1112             // user stuff
1113
1114             Event::handle('RouterInitialized', array($m));
1115         }
1116
1117         return $m;
1118     }
1119
1120     function map($path)
1121     {
1122         try {
1123             $match = $this->m->match($path);
1124         } catch (Exception $e) {
1125             common_log(LOG_ERR, "Problem getting route for $path - " .
1126                        $e->getMessage());
1127             // TRANS: Client error on action trying to visit a non-existing page.
1128             $cac = new ClientErrorAction(_('Page not found.'), 404);
1129             $cac->showPage();
1130         }
1131
1132         return $match;
1133     }
1134
1135     function build($action, $args=null, $params=null, $fragment=null)
1136     {
1137         $action_arg = array('action' => $action);
1138
1139         if ($args) {
1140             $args = array_merge($action_arg, $args);
1141         } else {
1142             $args = $action_arg;
1143         }
1144
1145         $url = $this->m->generate($args, $params, $fragment);
1146         // Due to a bug in the Net_URL_Mapper code, the returned URL may
1147         // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
1148         // repair that here rather than modifying the upstream code...
1149
1150         $qpos = strpos($url, '?');
1151         if ($qpos !== false) {
1152             $url = substr($url, 0, $qpos+1) .
1153                 str_replace('?', '&', substr($url, $qpos+1));
1154
1155             // @fixme this is a hacky workaround for http_build_query in the
1156             // lower-level code and bad configs that set the default separator
1157             // to &amp; instead of &. Encoded &s in parameters will not be
1158             // affected.
1159             $url = substr($url, 0, $qpos+1) .
1160                 str_replace('&amp;', '&', substr($url, $qpos+1));
1161
1162         }
1163
1164         return $url;
1165     }
1166 }