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