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