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