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