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