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