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