]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/router.php
Merge branch '0.9.x' 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     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', 'url') 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',
406                         array('action' => 'Redirect',
407                               'nextAction' => 'doc',
408                               'args' => array('title' => 'api')));
409
410             $m->connect('api/statuses/public_timeline.:format',
411                         array('action' => 'ApiTimelinePublic',
412                               'format' => '(xml|json|rss|atom)'));
413
414             $m->connect('api/statuses/friends_timeline.:format',
415                         array('action' => 'ApiTimelineFriends',
416                               'format' => '(xml|json|rss|atom)'));
417
418             $m->connect('api/statuses/friends_timeline/:id.:format',
419                         array('action' => 'ApiTimelineFriends',
420                               'id' => Nickname::INPUT_FMT,
421                               'format' => '(xml|json|rss|atom)'));
422
423             $m->connect('api/statuses/home_timeline.:format',
424                         array('action' => 'ApiTimelineHome',
425                               'format' => '(xml|json|rss|atom)'));
426
427             $m->connect('api/statuses/home_timeline/:id.:format',
428                         array('action' => 'ApiTimelineHome',
429                               'id' => Nickname::INPUT_FMT,
430                               'format' => '(xml|json|rss|atom)'));
431
432             $m->connect('api/statuses/user_timeline.:format',
433                         array('action' => 'ApiTimelineUser',
434                               'format' => '(xml|json|rss|atom)'));
435
436             $m->connect('api/statuses/user_timeline/:id.:format',
437                         array('action' => 'ApiTimelineUser',
438                               'id' => Nickname::INPUT_FMT,
439                               'format' => '(xml|json|rss|atom)'));
440
441             $m->connect('api/statuses/mentions.:format',
442                         array('action' => 'ApiTimelineMentions',
443                               'format' => '(xml|json|rss|atom)'));
444
445             $m->connect('api/statuses/mentions/:id.:format',
446                         array('action' => 'ApiTimelineMentions',
447                               'id' => Nickname::INPUT_FMT,
448                               'format' => '(xml|json|rss|atom)'));
449
450             $m->connect('api/statuses/replies.:format',
451                         array('action' => 'ApiTimelineMentions',
452                               'format' => '(xml|json|rss|atom)'));
453
454             $m->connect('api/statuses/replies/:id.:format',
455                         array('action' => 'ApiTimelineMentions',
456                               'id' => Nickname::INPUT_FMT,
457                               'format' => '(xml|json|rss|atom)'));
458
459             $m->connect('api/statuses/retweeted_by_me.:format',
460                         array('action' => 'ApiTimelineRetweetedByMe',
461                               'format' => '(xml|json|atom)'));
462
463             $m->connect('api/statuses/retweeted_to_me.:format',
464                         array('action' => 'ApiTimelineRetweetedToMe',
465                               'format' => '(xml|json|atom)'));
466
467             $m->connect('api/statuses/retweets_of_me.:format',
468                         array('action' => 'ApiTimelineRetweetsOfMe',
469                               'format' => '(xml|json|atom)'));
470
471             $m->connect('api/statuses/friends.:format',
472                         array('action' => 'ApiUserFriends',
473                               'format' => '(xml|json)'));
474
475             $m->connect('api/statuses/friends/:id.:format',
476                         array('action' => 'ApiUserFriends',
477                               'id' => Nickname::INPUT_FMT,
478                               'format' => '(xml|json)'));
479
480             $m->connect('api/statuses/followers.:format',
481                         array('action' => 'ApiUserFollowers',
482                               'format' => '(xml|json)'));
483
484             $m->connect('api/statuses/followers/:id.:format',
485                         array('action' => 'ApiUserFollowers',
486                               'id' => Nickname::INPUT_FMT,
487                               'format' => '(xml|json)'));
488
489             $m->connect('api/statuses/show.:format',
490                         array('action' => 'ApiStatusesShow',
491                               'format' => '(xml|json|atom)'));
492
493             $m->connect('api/statuses/show/:id.:format',
494                         array('action' => 'ApiStatusesShow',
495                               'id' => '[0-9]+',
496                               'format' => '(xml|json|atom)'));
497
498             $m->connect('api/statuses/update.:format',
499                         array('action' => 'ApiStatusesUpdate',
500                               'format' => '(xml|json)'));
501
502             $m->connect('api/statuses/destroy.:format',
503                         array('action' => 'ApiStatusesDestroy',
504                               'format' => '(xml|json)'));
505
506             $m->connect('api/statuses/destroy/:id.:format',
507                         array('action' => 'ApiStatusesDestroy',
508                               'id' => '[0-9]+',
509                               'format' => '(xml|json)'));
510
511             $m->connect('api/statuses/retweet/:id.:format',
512                         array('action' => 'ApiStatusesRetweet',
513                               'id' => '[0-9]+',
514                               'format' => '(xml|json)'));
515
516             $m->connect('api/statuses/retweets/:id.:format',
517                         array('action' => 'ApiStatusesRetweets',
518                               'id' => '[0-9]+',
519                               'format' => '(xml|json)'));
520
521             // users
522
523             $m->connect('api/users/show.:format',
524                         array('action' => 'ApiUserShow',
525                               'format' => '(xml|json)'));
526
527             $m->connect('api/users/show/:id.:format',
528                         array('action' => 'ApiUserShow',
529                               'id' => Nickname::INPUT_FMT,
530                               'format' => '(xml|json)'));
531
532             $m->connect('api/users/profile_image/:screen_name.:format',
533                         array('action' => 'ApiUserProfileImage',
534                               'screen_name' => Nickname::DISPLAY_FMT,
535                               'format' => '(xml|json)'));
536
537             // direct messages
538
539             $m->connect('api/direct_messages.:format',
540                         array('action' => 'ApiDirectMessage',
541                               'format' => '(xml|json|rss|atom)'));
542
543             $m->connect('api/direct_messages/sent.:format',
544                         array('action' => 'ApiDirectMessage',
545                               'format' => '(xml|json|rss|atom)',
546                               'sent' => true));
547
548             $m->connect('api/direct_messages/new.:format',
549                         array('action' => 'ApiDirectMessageNew',
550                               'format' => '(xml|json)'));
551
552             // friendships
553
554             $m->connect('api/friendships/show.:format',
555                         array('action' => 'ApiFriendshipsShow',
556                               'format' => '(xml|json)'));
557
558             $m->connect('api/friendships/exists.:format',
559                         array('action' => 'ApiFriendshipsExists',
560                               'format' => '(xml|json)'));
561
562             $m->connect('api/friendships/create.:format',
563                         array('action' => 'ApiFriendshipsCreate',
564                               'format' => '(xml|json)'));
565
566             $m->connect('api/friendships/destroy.:format',
567                         array('action' => 'ApiFriendshipsDestroy',
568                               'format' => '(xml|json)'));
569
570             $m->connect('api/friendships/create/:id.:format',
571                         array('action' => 'ApiFriendshipsCreate',
572                               'id' => Nickname::INPUT_FMT,
573                               'format' => '(xml|json)'));
574
575             $m->connect('api/friendships/destroy/:id.:format',
576                         array('action' => 'ApiFriendshipsDestroy',
577                               'id' => Nickname::INPUT_FMT,
578                               'format' => '(xml|json)'));
579
580             // Social graph
581
582             $m->connect('api/friends/ids/:id.:format',
583                         array('action' => 'ApiUserFriends',
584                               'ids_only' => true));
585
586             $m->connect('api/followers/ids/:id.:format',
587                         array('action' => 'ApiUserFollowers',
588                               'ids_only' => true));
589
590             $m->connect('api/friends/ids.:format',
591                         array('action' => 'ApiUserFriends',
592                               'ids_only' => true));
593
594             $m->connect('api/followers/ids.:format',
595                         array('action' => 'ApiUserFollowers',
596                               'ids_only' => true));
597
598             // account
599
600             $m->connect('api/account/verify_credentials.:format',
601                         array('action' => 'ApiAccountVerifyCredentials'));
602
603             $m->connect('api/account/update_profile.:format',
604                         array('action' => 'ApiAccountUpdateProfile'));
605
606             $m->connect('api/account/update_profile_image.:format',
607                         array('action' => 'ApiAccountUpdateProfileImage'));
608
609             $m->connect('api/account/update_profile_background_image.:format',
610                         array('action' => 'ApiAccountUpdateProfileBackgroundImage'));
611
612             $m->connect('api/account/update_profile_colors.:format',
613                         array('action' => 'ApiAccountUpdateProfileColors'));
614
615             $m->connect('api/account/update_delivery_device.:format',
616                         array('action' => 'ApiAccountUpdateDeliveryDevice'));
617
618             // special case where verify_credentials is called w/out a format
619
620             $m->connect('api/account/verify_credentials',
621                         array('action' => 'ApiAccountVerifyCredentials'));
622
623             $m->connect('api/account/rate_limit_status.:format',
624                         array('action' => 'ApiAccountRateLimitStatus'));
625
626             // favorites
627
628             $m->connect('api/favorites.:format',
629                         array('action' => 'ApiTimelineFavorites',
630                               'format' => '(xml|json|rss|atom)'));
631
632             $m->connect('api/favorites/:id.:format',
633                         array('action' => 'ApiTimelineFavorites',
634                               'id' => Nickname::INPUT_FMT,
635                               'format' => '(xml|json|rss|atom)'));
636
637             $m->connect('api/favorites/create/:id.:format',
638                         array('action' => 'ApiFavoriteCreate',
639                               'id' => '[0-9]+',
640                               'format' => '(xml|json)'));
641
642             $m->connect('api/favorites/destroy/:id.:format',
643                         array('action' => 'ApiFavoriteDestroy',
644                               'id' => '[0-9]+',
645                               'format' => '(xml|json)'));
646             // blocks
647
648             $m->connect('api/blocks/create.:format',
649                         array('action' => 'ApiBlockCreate',
650                               'format' => '(xml|json)'));
651
652             $m->connect('api/blocks/create/:id.:format',
653                         array('action' => 'ApiBlockCreate',
654                               'id' => Nickname::INPUT_FMT,
655                               'format' => '(xml|json)'));
656
657             $m->connect('api/blocks/destroy.:format',
658                         array('action' => 'ApiBlockDestroy',
659                               'format' => '(xml|json)'));
660
661             $m->connect('api/blocks/destroy/:id.:format',
662                         array('action' => 'ApiBlockDestroy',
663                               'id' => Nickname::INPUT_FMT,
664                               'format' => '(xml|json)'));
665             // help
666
667             $m->connect('api/help/test.:format',
668                         array('action' => 'ApiHelpTest',
669                               'format' => '(xml|json)'));
670
671             // statusnet
672
673             $m->connect('api/statusnet/version.:format',
674                         array('action' => 'ApiStatusnetVersion',
675                               'format' => '(xml|json)'));
676
677             $m->connect('api/statusnet/config.:format',
678                         array('action' => 'ApiStatusnetConfig',
679                               'format' => '(xml|json)'));
680
681             // For older methods, we provide "laconica" base action
682
683             $m->connect('api/laconica/version.:format',
684                         array('action' => 'ApiStatusnetVersion',
685                               'format' => '(xml|json)'));
686
687             $m->connect('api/laconica/config.:format',
688                         array('action' => 'ApiStatusnetConfig',
689                               'format' => '(xml|json)'));
690
691             // Groups and tags are newer than 0.8.1 so no backward-compatibility
692             // necessary
693
694             // Groups
695             //'list' has to be handled differently, as php will not allow a method to be named 'list'
696
697             $m->connect('api/statusnet/groups/timeline/:id.:format',
698                         array('action' => 'ApiTimelineGroup',
699                               'id' => Nickname::INPUT_FMT,
700                               'format' => '(xml|json|rss|atom)'));
701
702             $m->connect('api/statusnet/groups/show.:format',
703                         array('action' => 'ApiGroupShow',
704                               'format' => '(xml|json)'));
705
706             $m->connect('api/statusnet/groups/show/:id.:format',
707                         array('action' => 'ApiGroupShow',
708                               'id' => Nickname::INPUT_FMT,
709                               'format' => '(xml|json)'));
710
711             $m->connect('api/statusnet/groups/join.:format',
712                         array('action' => 'ApiGroupJoin',
713                               'id' => Nickname::INPUT_FMT,
714                               'format' => '(xml|json)'));
715
716             $m->connect('api/statusnet/groups/join/:id.:format',
717                         array('action' => 'ApiGroupJoin',
718                               'format' => '(xml|json)'));
719
720             $m->connect('api/statusnet/groups/leave.:format',
721                         array('action' => 'ApiGroupLeave',
722                               'id' => Nickname::INPUT_FMT,
723                               'format' => '(xml|json)'));
724
725             $m->connect('api/statusnet/groups/leave/:id.:format',
726                         array('action' => 'ApiGroupLeave',
727                               'format' => '(xml|json)'));
728
729             $m->connect('api/statusnet/groups/is_member.:format',
730                         array('action' => 'ApiGroupIsMember',
731                               'format' => '(xml|json)'));
732
733             $m->connect('api/statusnet/groups/list.:format',
734                         array('action' => 'ApiGroupList',
735                               'format' => '(xml|json|rss|atom)'));
736
737             $m->connect('api/statusnet/groups/list/:id.:format',
738                         array('action' => 'ApiGroupList',
739                               'id' => Nickname::INPUT_FMT,
740                               'format' => '(xml|json|rss|atom)'));
741
742             $m->connect('api/statusnet/groups/list_all.:format',
743                         array('action' => 'ApiGroupListAll',
744                               'format' => '(xml|json|rss|atom)'));
745
746             $m->connect('api/statusnet/groups/membership.:format',
747                         array('action' => 'ApiGroupMembership',
748                               'format' => '(xml|json)'));
749
750             $m->connect('api/statusnet/groups/membership/:id.:format',
751                         array('action' => 'ApiGroupMembership',
752                               'id' => Nickname::INPUT_FMT,
753                               'format' => '(xml|json)'));
754
755             $m->connect('api/statusnet/groups/create.:format',
756                         array('action' => 'ApiGroupCreate',
757                               'format' => '(xml|json)'));
758
759             $m->connect('api/statusnet/groups/update/:id.:format',
760                         array('action' => 'ApiGroupProfileUpdate',
761                               'id' => '[a-zA-Z0-9]+',
762                               'format' => '(xml|json)'));
763
764             // Tags
765             $m->connect('api/statusnet/tags/timeline/:tag.:format',
766                         array('action' => 'ApiTimelineTag',
767                               'format' => '(xml|json|rss|atom)'));
768
769             // media related
770             $m->connect(
771                 'api/statusnet/media/upload',
772                 array('action' => 'ApiMediaUpload')
773             );
774
775             // search
776             $m->connect('api/search.atom', array('action' => 'ApiSearchAtom'));
777             $m->connect('api/search.json', array('action' => 'ApiSearchJSON'));
778             $m->connect('api/trends.json', array('action' => 'ApiTrends'));
779
780             $m->connect('api/oauth/request_token',
781                         array('action' => 'ApiOauthRequestToken'));
782
783             $m->connect('api/oauth/access_token',
784                         array('action' => 'ApiOauthAccessToken'));
785
786             $m->connect('api/oauth/authorize',
787                         array('action' => 'ApiOauthAuthorize'));
788
789             // Admin
790
791             $m->connect('admin/site', array('action' => 'siteadminpanel'));
792             $m->connect('admin/design', array('action' => 'designadminpanel'));
793             $m->connect('admin/user', array('action' => 'useradminpanel'));
794                 $m->connect('admin/access', array('action' => 'accessadminpanel'));
795             $m->connect('admin/paths', array('action' => 'pathsadminpanel'));
796             $m->connect('admin/sessions', array('action' => 'sessionsadminpanel'));
797             $m->connect('admin/sitenotice', array('action' => 'sitenoticeadminpanel'));
798             $m->connect('admin/snapshot', array('action' => 'snapshotadminpanel'));
799             $m->connect('admin/license', array('action' => 'licenseadminpanel'));
800
801             $m->connect('admin/plugins', array('action' => 'pluginsadminpanel'));
802             $m->connect('admin/plugins/enable/:plugin',
803                         array('action' => 'pluginenable'),
804                         array('plugin' => '[A-Za-z0-9_]+'));
805             $m->connect('admin/plugins/disable/:plugin',
806                         array('action' => 'plugindisable'),
807                         array('plugin' => '[A-Za-z0-9_]+'));
808
809             $m->connect('getfile/:filename',
810                         array('action' => 'getfile'),
811                         array('filename' => '[A-Za-z0-9._-]+'));
812
813             // In the "root"
814
815             if (common_config('singleuser', 'enabled')) {
816
817                 $nickname = User::singleUserNickname();
818
819                 foreach (array('subscriptions', 'subscribers',
820                                'all', 'foaf', 'xrds',
821                                'replies', 'microsummary', 'hcard') as $a) {
822                     $m->connect($a,
823                                 array('action' => $a,
824                                       'nickname' => $nickname));
825                 }
826
827                 foreach (array('subscriptions', 'subscribers') as $a) {
828                     $m->connect($a.'/:tag',
829                                 array('action' => $a,
830                                       'nickname' => $nickname),
831                                 array('tag' => '[a-zA-Z0-9]+'));
832                 }
833
834                 foreach (array('rss', 'groups') as $a) {
835                     $m->connect($a,
836                                 array('action' => 'user'.$a,
837                                       'nickname' => $nickname));
838                 }
839
840                 foreach (array('all', 'replies', 'favorites') as $a) {
841                     $m->connect($a.'/rss',
842                                 array('action' => $a.'rss',
843                                       'nickname' => $nickname));
844                 }
845
846                 $m->connect('favorites',
847                             array('action' => 'showfavorites',
848                                   'nickname' => $nickname));
849
850                 $m->connect('avatar/:size',
851                             array('action' => 'avatarbynickname',
852                                   'nickname' => $nickname),
853                             array('size' => '(original|96|48|24)'));
854
855                 $m->connect('tag/:tag/rss',
856                             array('action' => 'userrss',
857                                   'nickname' => $nickname),
858                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
859
860                 $m->connect('tag/:tag',
861                             array('action' => 'showstream',
862                                   'nickname' => $nickname),
863                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
864
865                 $m->connect('rsd.xml',
866                             array('action' => 'rsd',
867                                   'nickname' => $nickname));
868
869                 $m->connect('',
870                             array('action' => 'showstream',
871                                   'nickname' => $nickname));
872             } else {
873                 $m->connect('', array('action' => 'public'));
874                 $m->connect('rss', array('action' => 'publicrss'));
875                 $m->connect('featuredrss', array('action' => 'featuredrss'));
876                 $m->connect('favoritedrss', array('action' => 'favoritedrss'));
877                 $m->connect('featured/', array('action' => 'featured'));
878                 $m->connect('featured', array('action' => 'featured'));
879                 $m->connect('favorited/', array('action' => 'favorited'));
880                 $m->connect('favorited', array('action' => 'favorited'));
881                 $m->connect('rsd.xml', array('action' => 'rsd'));
882
883                 foreach (array('subscriptions', 'subscribers',
884                                'nudge', 'all', 'foaf', 'xrds',
885                                'replies', 'inbox', 'outbox', 'microsummary', 'hcard') as $a) {
886                     $m->connect(':nickname/'.$a,
887                                 array('action' => $a),
888                                 array('nickname' => Nickname::DISPLAY_FMT));
889                 }
890
891                 foreach (array('subscriptions', 'subscribers') as $a) {
892                     $m->connect(':nickname/'.$a.'/:tag',
893                                 array('action' => $a),
894                                 array('tag' => '[a-zA-Z0-9]+',
895                                       'nickname' => Nickname::DISPLAY_FMT));
896                 }
897
898                 foreach (array('rss', 'groups') as $a) {
899                     $m->connect(':nickname/'.$a,
900                                 array('action' => 'user'.$a),
901                                 array('nickname' => Nickname::DISPLAY_FMT));
902                 }
903
904                 foreach (array('all', 'replies', 'favorites') as $a) {
905                     $m->connect(':nickname/'.$a.'/rss',
906                                 array('action' => $a.'rss'),
907                                 array('nickname' => Nickname::DISPLAY_FMT));
908                 }
909
910                 $m->connect(':nickname/favorites',
911                             array('action' => 'showfavorites'),
912                             array('nickname' => Nickname::DISPLAY_FMT));
913
914                 $m->connect(':nickname/avatar/:size',
915                             array('action' => 'avatarbynickname'),
916                             array('size' => '(original|96|48|24)',
917                                   'nickname' => Nickname::DISPLAY_FMT));
918
919                 $m->connect(':nickname/tag/:tag/rss',
920                             array('action' => 'userrss'),
921                             array('nickname' => Nickname::DISPLAY_FMT),
922                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
923
924                 $m->connect(':nickname/tag/:tag',
925                             array('action' => 'showstream'),
926                             array('nickname' => Nickname::DISPLAY_FMT),
927                             array('tag' => '[\pL\pN_\-\.]{1,64}'));
928
929                 $m->connect(':nickname/rsd.xml',
930                             array('action' => 'rsd'),
931                             array('nickname' => Nickname::DISPLAY_FMT));
932
933                 $m->connect(':nickname',
934                             array('action' => 'showstream'),
935                             array('nickname' => Nickname::DISPLAY_FMT));
936             }
937
938             // AtomPub API
939
940             $m->connect('api/statusnet/app/service/:id.xml',
941                         array('action' => 'ApiAtomService'),
942                         array('id' => Nickname::DISPLAY_FMT));
943
944             $m->connect('api/statusnet/app/service.xml',
945                         array('action' => 'ApiAtomService'));
946
947             $m->connect('api/statusnet/app/subscriptions/:subscriber/:subscribed.atom',
948                         array('action' => 'AtomPubShowSubscription'),
949                         array('subscriber' => '[0-9]+',
950                               'subscribed' => '[0-9]+'));
951
952             $m->connect('api/statusnet/app/subscriptions/:subscriber.atom',
953                         array('action' => 'AtomPubSubscriptionFeed'),
954                         array('subscriber' => '[0-9]+'));
955
956             $m->connect('api/statusnet/app/favorites/:profile/:notice.atom',
957                         array('action' => 'AtomPubShowFavorite'),
958                         array('profile' => '[0-9]+',
959                               'notice' => '[0-9]+'));
960
961             $m->connect('api/statusnet/app/favorites/:profile.atom',
962                         array('action' => 'AtomPubFavoriteFeed'),
963                         array('profile' => '[0-9]+'));
964
965             $m->connect('api/statusnet/app/memberships/:profile/:group.atom',
966                         array('action' => 'AtomPubShowMembership'),
967                         array('profile' => '[0-9]+',
968                               'group' => '[0-9]+'));
969
970             $m->connect('api/statusnet/app/memberships/:profile.atom',
971                         array('action' => 'AtomPubMembershipFeed'),
972                         array('profile' => '[0-9]+'));
973
974             // user stuff
975
976             Event::handle('RouterInitialized', array($m));
977         }
978
979         return $m;
980     }
981
982     function map($path)
983     {
984         try {
985             $match = $this->m->match($path);
986         } catch (Net_URL_Mapper_InvalidException $e) {
987             common_log(LOG_ERR, "Problem getting route for $path - " .
988                        $e->getMessage());
989             // TRANS: Client error on action trying to visit a non-existing page.
990             $cac = new ClientErrorAction(_('Page not found.'), 404);
991             $cac->showPage();
992         }
993
994         return $match;
995     }
996
997     function build($action, $args=null, $params=null, $fragment=null)
998     {
999         $action_arg = array('action' => $action);
1000
1001         if ($args) {
1002             $args = array_merge($action_arg, $args);
1003         } else {
1004             $args = $action_arg;
1005         }
1006
1007         $url = $this->m->generate($args, $params, $fragment);
1008
1009         // Due to a bug in the Net_URL_Mapper code, the returned URL may
1010         // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
1011         // repair that here rather than modifying the upstream code...
1012
1013         $qpos = strpos($url, '?');
1014         if ($qpos !== false) {
1015             $url = substr($url, 0, $qpos+1) .
1016                 str_replace('?', '&', substr($url, $qpos+1));
1017
1018             // @fixme this is a hacky workaround for http_build_query in the
1019             // lower-level code and bad configs that set the default separator
1020             // to &amp; instead of &. Encoded &s in parameters will not be
1021             // affected.
1022             $url = substr($url, 0, $qpos+1) .
1023                 str_replace('&amp;', '&', substr($url, $qpos+1));
1024
1025         }
1026
1027         return $url;
1028     }
1029 }