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