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