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