]> 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                 $user = User::singleUser();
802                 $nickname = $user->nickname;
803
804                 foreach (array('subscriptions', 'subscribers',
805                                'all', 'foaf', 'xrds',
806                                'replies', 'microsummary', 'hcard') as $a) {
807                     $m->connect($a,
808                                 array('action' => $a,
809                                       'nickname' => $nickname));
810                 }
811
812                 foreach (array('subscriptions', 'subscribers') as $a) {
813                     $m->connect($a.'/:tag',
814                                 array('action' => $a,
815                                       'nickname' => $nickname),
816                                 array('tag' => '[a-zA-Z0-9]+'));
817                 }
818
819                 foreach (array('rss', 'groups') as $a) {
820                     $m->connect($a,
821                                 array('action' => 'user'.$a,
822                                       'nickname' => $nickname));
823                 }
824
825                 foreach (array('all', 'replies', 'favorites') as $a) {
826                     $m->connect($a.'/rss',
827                                 array('action' => $a.'rss',
828                                       'nickname' => $nickname));
829                 }
830
831                 $m->connect('favorites',
832                             array('action' => 'showfavorites',
833                                   'nickname' => $nickname));
834
835                 $m->connect('avatar/:size',
836                             array('action' => 'avatarbynickname',
837                                   'nickname' => $nickname),
838                             array('size' => '(original|96|48|24)'));
839
840                 $m->connect('tag/:tag/rss',
841                             array('action' => 'userrss',
842                                   'nickname' => $nickname),
843                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
844
845                 $m->connect('tag/:tag',
846                             array('action' => 'showstream',
847                                   'nickname' => $nickname),
848                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
849
850                 $m->connect('rsd.xml',
851                             array('action' => 'rsd',
852                                   'nickname' => $nickname));
853
854                 $m->connect('',
855                             array('action' => 'showstream',
856                                   'nickname' => $nickname));
857             } else {
858                 $m->connect('', array('action' => 'public'));
859                 $m->connect('rss', array('action' => 'publicrss'));
860                 $m->connect('featuredrss', array('action' => 'featuredrss'));
861                 $m->connect('favoritedrss', array('action' => 'favoritedrss'));
862                 $m->connect('featured/', array('action' => 'featured'));
863                 $m->connect('featured', array('action' => 'featured'));
864                 $m->connect('favorited/', array('action' => 'favorited'));
865                 $m->connect('favorited', array('action' => 'favorited'));
866                 $m->connect('rsd.xml', array('action' => 'rsd'));
867
868                 foreach (array('subscriptions', 'subscribers',
869                                'nudge', 'all', 'foaf', 'xrds',
870                                'replies', 'inbox', 'outbox', 'microsummary', 'hcard') as $a) {
871                     $m->connect(':nickname/'.$a,
872                                 array('action' => $a),
873                                 array('nickname' => Nickname::DISPLAY_FMT));
874                 }
875
876                 foreach (array('subscriptions', 'subscribers') as $a) {
877                     $m->connect(':nickname/'.$a.'/:tag',
878                                 array('action' => $a),
879                                 array('tag' => '[a-zA-Z0-9]+',
880                                       'nickname' => Nickname::DISPLAY_FMT));
881                 }
882
883                 foreach (array('rss', 'groups') as $a) {
884                     $m->connect(':nickname/'.$a,
885                                 array('action' => 'user'.$a),
886                                 array('nickname' => Nickname::DISPLAY_FMT));
887                 }
888
889                 foreach (array('all', 'replies', 'favorites') as $a) {
890                     $m->connect(':nickname/'.$a.'/rss',
891                                 array('action' => $a.'rss'),
892                                 array('nickname' => Nickname::DISPLAY_FMT));
893                 }
894
895                 $m->connect(':nickname/favorites',
896                             array('action' => 'showfavorites'),
897                             array('nickname' => Nickname::DISPLAY_FMT));
898
899                 $m->connect(':nickname/avatar/:size',
900                             array('action' => 'avatarbynickname'),
901                             array('size' => '(original|96|48|24)',
902                                   'nickname' => Nickname::DISPLAY_FMT));
903
904                 $m->connect(':nickname/tag/:tag/rss',
905                             array('action' => 'userrss'),
906                             array('nickname' => Nickname::DISPLAY_FMT),
907                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
908
909                 $m->connect(':nickname/tag/:tag',
910                             array('action' => 'showstream'),
911                             array('nickname' => Nickname::DISPLAY_FMT),
912                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
913
914                 $m->connect(':nickname/rsd.xml',
915                             array('action' => 'rsd'),
916                             array('nickname' => Nickname::DISPLAY_FMT));
917
918                 $m->connect(':nickname',
919                             array('action' => 'showstream'),
920                             array('nickname' => Nickname::DISPLAY_FMT));
921             }
922
923             // user stuff
924
925             Event::handle('RouterInitialized', array($m));
926         }
927
928         return $m;
929     }
930
931     function map($path)
932     {
933         try {
934             $match = $this->m->match($path);
935         } catch (Net_URL_Mapper_InvalidException $e) {
936             common_log(LOG_ERR, "Problem getting route for $path - " .
937                        $e->getMessage());
938             // TRANS: Client error on action trying to visit a non-existing page.
939             $cac = new ClientErrorAction(_('Page not found.'), 404);
940             $cac->showPage();
941         }
942
943         return $match;
944     }
945
946     function build($action, $args=null, $params=null, $fragment=null)
947     {
948         $action_arg = array('action' => $action);
949
950         if ($args) {
951             $args = array_merge($action_arg, $args);
952         } else {
953             $args = $action_arg;
954         }
955
956         $url = $this->m->generate($args, $params, $fragment);
957
958         // Due to a bug in the Net_URL_Mapper code, the returned URL may
959         // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
960         // repair that here rather than modifying the upstream code...
961
962         $qpos = strpos($url, '?');
963         if ($qpos !== false) {
964             $url = substr($url, 0, $qpos+1) .
965               str_replace('?', '&', substr($url, $qpos+1));
966
967             // @fixme this is a hacky workaround for http_build_query in the
968             // lower-level code and bad configs that set the default separator
969             // to &amp; instead of &. Encoded &s in parameters will not be
970             // affected.
971             $url = substr($url, 0, $qpos+1) .
972               str_replace('&amp;', '&', substr($url, $qpos+1));
973
974         }
975
976         return $url;
977     }
978 }