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