]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/router.php
Merge commit 'refs/merge-requests/157' of git://gitorious.org/statusnet/mainline...
[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     /**
130      * Clear the global singleton instance for this class.
131      * Needed to ensure reset when switching site configurations.
132      */
133     static function clear()
134     {
135         Router::$inst = null;
136     }
137
138     function __construct()
139     {
140         if (empty($this->m)) {
141             if (!common_config('router', 'cache')) {
142                 $this->m = $this->initialize();
143             } else {
144                 $k = self::cacheKey();
145                 $c = Cache::instance();
146                 $m = $c->get($k);
147                 if (!empty($m)) {
148                     $this->m = $m;
149                 } else {
150                     $this->m = $this->initialize();
151                     $c->set($k, $this->m);
152                 }
153             }
154         }
155     }
156
157     /**
158      * Create a unique hashkey for the router.
159      *
160      * The router's url map can change based on the version of the software
161      * you're running and the plugins that are enabled. To avoid having bad routes
162      * get stuck in the cache, the key includes a list of plugins and the software
163      * version.
164      *
165      * There can still be problems with a) differences in versions of the plugins and
166      * b) people running code between official versions, but these tend to be more
167      * sophisticated users who can grok what's going on and clear their caches.
168      *
169      * @return string cache key string that should uniquely identify a router
170      */
171
172     static function cacheKey()
173     {
174         $parts = array('router');
175
176         // Many router paths depend on this setting.
177         if (common_config('singleuser', 'enabled')) {
178             $parts[] = '1user';
179         } else {
180             $parts[] = 'multi';
181         }
182
183         return Cache::codeKey(implode(':', $parts));
184     }
185
186     function initialize()
187     {
188         $m = StatusNet_URL_Mapper::getInstance();
189
190         if (Event::handle('StartInitializeRouter', array(&$m))) {
191
192             $m->connect('robots.txt', array('action' => 'robotstxt'));
193
194             $m->connect('opensearch/people', array('action' => 'opensearch',
195                                                    'type' => 'people'));
196             $m->connect('opensearch/notice', array('action' => 'opensearch',
197                                                    'type' => 'notice'));
198
199             // docs
200
201             $m->connect('doc/:title', array('action' => 'doc'));
202
203             $m->connect('main/otp/:user_id/:token',
204                         array('action' => 'otp'),
205                         array('user_id' => '[0-9]+',
206                               'token' => '.+'));
207
208             // main stuff is repetitive
209
210             $main = array('login', 'logout', 'register', 'subscribe',
211                           'unsubscribe', 'cancelsubscription', 'approvesub',
212                           'confirmaddress', 'recoverpassword',
213                           'invite', 'favor', 'disfavor', 'sup',
214                           'block', 'unblock', 'subedit',
215                           'groupblock', 'groupunblock',
216                           'sandbox', 'unsandbox',
217                           'silence', 'unsilence',
218                           'grantrole', 'revokerole',
219                           'repeat',
220                           'deleteuser',
221                           'geocode',
222                           'version',
223                           'backupaccount',
224                           'deleteaccount',
225                           'restoreaccount',
226             );
227
228             foreach ($main as $a) {
229                 $m->connect('main/'.$a, array('action' => $a));
230             }
231
232             // Also need a block variant accepting ID on URL for mail links
233             $m->connect('main/block/:profileid',
234                         array('action' => 'block'),
235                         array('profileid' => '[0-9]+'));
236
237             $m->connect('main/sup/:seconds', array('action' => 'sup'),
238                         array('seconds' => '[0-9]+'));
239
240             $m->connect('main/tagprofile', array('action' => 'tagprofile'));
241             $m->connect('main/tagprofile/:id', array('action' => 'tagprofile'),
242                                                array('id' => '[0-9]+'));
243
244             $m->connect('main/oembed',
245                         array('action' => 'oembed'));
246
247             $m->connect('main/xrds',
248                         array('action' => 'publicxrds'));
249             $m->connect('.well-known/host-meta',
250                         array('action' => 'hostmeta'));
251             $m->connect('main/xrd',
252                         array('action' => 'userxrd'));
253
254             // these take a code
255
256             foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) {
257                 $m->connect('main/'.$c.'/:code', array('action' => $c));
258             }
259
260             // exceptional
261
262             $m->connect('main/remote', array('action' => 'remotesubscribe'));
263             $m->connect('main/remote?nickname=:nickname', array('action' => 'remotesubscribe'), array('nickname' => '[A-Za-z0-9_-]+'));
264
265             foreach (Router::$bare as $action) {
266                 $m->connect('index.php?action=' . $action, array('action' => $action));
267             }
268
269             // settings
270
271             foreach (array('profile', 'avatar', 'password', 'im', 'oauthconnections',
272                            'oauthapps', 'email', 'sms', 'url') as $s) {
273                 $m->connect('settings/'.$s, array('action' => $s.'settings'));
274             }
275
276             $m->connect('settings/oauthapps/show/:id',
277                         array('action' => 'showapplication'),
278                         array('id' => '[0-9]+')
279             );
280             $m->connect('settings/oauthapps/new',
281                         array('action' => 'newapplication')
282             );
283             $m->connect('settings/oauthapps/edit/:id',
284                         array('action' => 'editapplication'),
285                         array('id' => '[0-9]+')
286             );
287             $m->connect('settings/oauthapps/delete/:id',
288                         array('action' => 'deleteapplication'),
289                         array('id' => '[0-9]+')
290             );
291
292             // search
293
294             foreach (array('group', 'people', 'notice') as $s) {
295                 $m->connect('search/'.$s, array('action' => $s.'search'));
296                 $m->connect('search/'.$s.'?q=:q',
297                             array('action' => $s.'search'),
298                             array('q' => '.+'));
299             }
300
301             // The second of these is needed to make the link work correctly
302             // when inserted into the page. The first is needed to match the
303             // route on the way in. Seems to be another Net_URL_Mapper bug to me.
304             $m->connect('search/notice/rss', array('action' => 'noticesearchrss'));
305             $m->connect('search/notice/rss?q=:q', array('action' => 'noticesearchrss'),
306                         array('q' => '.+'));
307
308             $m->connect('attachment/:attachment',
309                         array('action' => 'attachment'),
310                         array('attachment' => '[0-9]+'));
311
312             $m->connect('attachment/:attachment/ajax',
313                         array('action' => 'attachment_ajax'),
314                         array('attachment' => '[0-9]+'));
315
316             $m->connect('attachment/:attachment/thumbnail',
317                         array('action' => 'attachment_thumbnail'),
318                         array('attachment' => '[0-9]+'));
319
320             $m->connect('notice/new', array('action' => 'newnotice'));
321             $m->connect('notice/new?replyto=:replyto',
322                         array('action' => 'newnotice'),
323                         array('replyto' => Nickname::DISPLAY_FMT));
324             $m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
325                         array('action' => 'newnotice'),
326                         array('replyto' => Nickname::DISPLAY_FMT),
327                         array('inreplyto' => '[0-9]+'));
328
329             $m->connect('notice/:notice/file',
330                         array('action' => 'file'),
331                         array('notice' => '[0-9]+'));
332
333             $m->connect('notice/:notice',
334                         array('action' => 'shownotice'),
335                         array('notice' => '[0-9]+'));
336             $m->connect('notice/delete', array('action' => 'deletenotice'));
337             $m->connect('notice/delete/:notice',
338                         array('action' => 'deletenotice'),
339                         array('notice' => '[0-9]+'));
340
341             $m->connect('bookmarklet/new', array('action' => 'bookmarklet'));
342
343             // conversation
344
345             $m->connect('conversation/:id',
346                         array('action' => 'conversation'),
347                         array('id' => '[0-9]+'));
348             $m->connect('conversation/:id/replies',
349                         array('action' => 'conversationreplies'),
350                         array('id' => '[0-9]+'));
351
352             $m->connect('message/new', array('action' => 'newmessage'));
353             $m->connect('message/new?to=:to', array('action' => 'newmessage'), array('to' => Nickname::DISPLAY_FMT));
354             $m->connect('message/:message',
355                         array('action' => 'showmessage'),
356                         array('message' => '[0-9]+'));
357
358             $m->connect('user/:id',
359                         array('action' => 'userbyid'),
360                         array('id' => '[0-9]+'));
361
362             $m->connect('tags/', array('action' => 'publictagcloud'));
363             $m->connect('tag/', array('action' => 'publictagcloud'));
364             $m->connect('tags', array('action' => 'publictagcloud'));
365             $m->connect('tag', array('action' => 'publictagcloud'));
366             $m->connect('tag/:tag/rss',
367                         array('action' => 'tagrss'),
368                         array('tag' => self::REGEX_TAG));
369             $m->connect('tag/:tag',
370                         array('action' => 'tag'),
371                         array('tag' => self::REGEX_TAG));
372
373             // groups
374
375             $m->connect('group/new', array('action' => 'newgroup'));
376
377             foreach (array('edit', 'join', 'leave', 'delete', 'cancel', 'approve') as $v) {
378                 $m->connect('group/:nickname/'.$v,
379                             array('action' => $v.'group'),
380                             array('nickname' => Nickname::DISPLAY_FMT));
381                 $m->connect('group/:id/id/'.$v,
382                             array('action' => $v.'group'),
383                             array('id' => '[0-9]+'));
384             }
385
386             foreach (array('members', 'logo', 'rss') as $n) {
387                 $m->connect('group/:nickname/'.$n,
388                             array('action' => 'group'.$n),
389                             array('nickname' => Nickname::DISPLAY_FMT));
390             }
391
392             $m->connect('group/:nickname/foaf',
393                         array('action' => 'foafgroup'),
394                         array('nickname' => Nickname::DISPLAY_FMT));
395
396             $m->connect('group/:nickname/blocked',
397                         array('action' => 'blockedfromgroup'),
398                         array('nickname' => Nickname::DISPLAY_FMT));
399
400             $m->connect('group/:nickname/makeadmin',
401                         array('action' => 'makeadmin'),
402                         array('nickname' => Nickname::DISPLAY_FMT));
403
404             $m->connect('group/:nickname/members/pending',
405                         array('action' => 'groupqueue'),
406                         array('nickname' => Nickname::DISPLAY_FMT));
407
408             $m->connect('group/:id/id',
409                         array('action' => 'groupbyid'),
410                         array('id' => '[0-9]+'));
411
412             $m->connect('group/:nickname',
413                         array('action' => 'showgroup'),
414                         array('nickname' => Nickname::DISPLAY_FMT));
415
416             $m->connect('group/', array('action' => 'groups'));
417             $m->connect('group', array('action' => 'groups'));
418             $m->connect('groups/', array('action' => 'groups'));
419             $m->connect('groups', array('action' => 'groups'));
420
421             // Twitter-compatible API
422
423             // statuses API
424
425             $m->connect('api',
426                         array('action' => 'Redirect',
427                               'nextAction' => 'doc',
428                               'args' => array('title' => 'api')));
429
430             $m->connect('api/statuses/public_timeline.:format',
431                         array('action' => 'ApiTimelinePublic',
432                               'format' => '(xml|json|rss|atom|as)'));
433
434             $m->connect('api/statuses/friends_timeline.:format',
435                         array('action' => 'ApiTimelineFriends',
436                               'format' => '(xml|json|rss|atom|as)'));
437
438             $m->connect('api/statuses/friends_timeline/:id.:format',
439                         array('action' => 'ApiTimelineFriends',
440                               'id' => Nickname::INPUT_FMT,
441                               'format' => '(xml|json|rss|atom|as)'));
442
443             $m->connect('api/statuses/home_timeline.:format',
444                         array('action' => 'ApiTimelineHome',
445                               'format' => '(xml|json|rss|atom|as)'));
446
447             $m->connect('api/statuses/home_timeline/:id.:format',
448                         array('action' => 'ApiTimelineHome',
449                               'id' => Nickname::INPUT_FMT,
450                               'format' => '(xml|json|rss|atom|as)'));
451
452             $m->connect('api/statuses/user_timeline.:format',
453                         array('action' => 'ApiTimelineUser',
454                               'format' => '(xml|json|rss|atom|as)'));
455
456             $m->connect('api/statuses/user_timeline/:id.:format',
457                         array('action' => 'ApiTimelineUser',
458                               'id' => Nickname::INPUT_FMT,
459                               'format' => '(xml|json|rss|atom|as)'));
460
461             $m->connect('api/statuses/mentions.:format',
462                         array('action' => 'ApiTimelineMentions',
463                               'format' => '(xml|json|rss|atom|as)'));
464
465             $m->connect('api/statuses/mentions/:id.:format',
466                         array('action' => 'ApiTimelineMentions',
467                               'id' => Nickname::INPUT_FMT,
468                               'format' => '(xml|json|rss|atom|as)'));
469
470             $m->connect('api/statuses/replies.:format',
471                         array('action' => 'ApiTimelineMentions',
472                               'format' => '(xml|json|rss|atom|as)'));
473
474             $m->connect('api/statuses/replies/:id.:format',
475                         array('action' => 'ApiTimelineMentions',
476                               'id' => Nickname::INPUT_FMT,
477                               'format' => '(xml|json|rss|atom|as)'));
478
479             $m->connect('api/statuses/retweeted_by_me.:format',
480                         array('action' => 'ApiTimelineRetweetedByMe',
481                               'format' => '(xml|json|atom|as)'));
482
483             $m->connect('api/statuses/retweeted_to_me.:format',
484                         array('action' => 'ApiTimelineRetweetedToMe',
485                               'format' => '(xml|json|atom|as)'));
486
487             $m->connect('api/statuses/retweets_of_me.:format',
488                         array('action' => 'ApiTimelineRetweetsOfMe',
489                               'format' => '(xml|json|atom|as)'));
490
491             $m->connect('api/statuses/friends.:format',
492                         array('action' => 'ApiUserFriends',
493                               'format' => '(xml|json)'));
494
495             $m->connect('api/statuses/friends/:id.:format',
496                         array('action' => 'ApiUserFriends',
497                               'id' => Nickname::INPUT_FMT,
498                               'format' => '(xml|json)'));
499
500             $m->connect('api/statuses/followers.:format',
501                         array('action' => 'ApiUserFollowers',
502                               'format' => '(xml|json)'));
503
504             $m->connect('api/statuses/followers/:id.:format',
505                         array('action' => 'ApiUserFollowers',
506                               'id' => Nickname::INPUT_FMT,
507                               'format' => '(xml|json)'));
508
509             $m->connect('api/statuses/show.:format',
510                         array('action' => 'ApiStatusesShow',
511                               'format' => '(xml|json|atom)'));
512
513             $m->connect('api/statuses/show/:id.:format',
514                         array('action' => 'ApiStatusesShow',
515                               'id' => '[0-9]+',
516                               'format' => '(xml|json|atom)'));
517
518             $m->connect('api/statuses/update.:format',
519                         array('action' => 'ApiStatusesUpdate',
520                               'format' => '(xml|json)'));
521
522             $m->connect('api/statuses/destroy.:format',
523                         array('action' => 'ApiStatusesDestroy',
524                               'format' => '(xml|json)'));
525
526             $m->connect('api/statuses/destroy/:id.:format',
527                         array('action' => 'ApiStatusesDestroy',
528                               'id' => '[0-9]+',
529                               'format' => '(xml|json)'));
530
531             $m->connect('api/statuses/retweet/:id.:format',
532                         array('action' => 'ApiStatusesRetweet',
533                               'id' => '[0-9]+',
534                               'format' => '(xml|json)'));
535
536             $m->connect('api/statuses/retweets/:id.:format',
537                         array('action' => 'ApiStatusesRetweets',
538                               'id' => '[0-9]+',
539                               'format' => '(xml|json)'));
540
541             // users
542
543             $m->connect('api/users/show.:format',
544                         array('action' => 'ApiUserShow',
545                               'format' => '(xml|json)'));
546
547             $m->connect('api/users/show/:id.:format',
548                         array('action' => 'ApiUserShow',
549                               'id' => Nickname::INPUT_FMT,
550                               'format' => '(xml|json)'));
551
552             $m->connect('api/users/profile_image/:screen_name.:format',
553                         array('action' => 'ApiUserProfileImage',
554                               'screen_name' => Nickname::DISPLAY_FMT,
555                               'format' => '(xml|json)'));
556
557             // direct messages
558
559             $m->connect('api/direct_messages.:format',
560                         array('action' => 'ApiDirectMessage',
561                               'format' => '(xml|json|rss|atom)'));
562
563             $m->connect('api/direct_messages/sent.:format',
564                         array('action' => 'ApiDirectMessage',
565                               'format' => '(xml|json|rss|atom)',
566                               'sent' => true));
567
568             $m->connect('api/direct_messages/new.:format',
569                         array('action' => 'ApiDirectMessageNew',
570                               'format' => '(xml|json)'));
571
572             // friendships
573
574             $m->connect('api/friendships/show.:format',
575                         array('action' => 'ApiFriendshipsShow',
576                               'format' => '(xml|json)'));
577
578             $m->connect('api/friendships/exists.:format',
579                         array('action' => 'ApiFriendshipsExists',
580                               'format' => '(xml|json)'));
581
582             $m->connect('api/friendships/create.:format',
583                         array('action' => 'ApiFriendshipsCreate',
584                               'format' => '(xml|json)'));
585
586             $m->connect('api/friendships/destroy.:format',
587                         array('action' => 'ApiFriendshipsDestroy',
588                               'format' => '(xml|json)'));
589
590             $m->connect('api/friendships/create/:id.:format',
591                         array('action' => 'ApiFriendshipsCreate',
592                               'id' => Nickname::INPUT_FMT,
593                               'format' => '(xml|json)'));
594
595             $m->connect('api/friendships/destroy/:id.:format',
596                         array('action' => 'ApiFriendshipsDestroy',
597                               'id' => Nickname::INPUT_FMT,
598                               'format' => '(xml|json)'));
599
600             // Social graph
601
602             $m->connect('api/friends/ids/:id.:format',
603                         array('action' => 'ApiUserFriends',
604                               'ids_only' => true));
605
606             $m->connect('api/followers/ids/:id.:format',
607                         array('action' => 'ApiUserFollowers',
608                               'ids_only' => true));
609
610             $m->connect('api/friends/ids.:format',
611                         array('action' => 'ApiUserFriends',
612                               'ids_only' => true));
613
614             $m->connect('api/followers/ids.:format',
615                         array('action' => 'ApiUserFollowers',
616                               'ids_only' => true));
617
618             // account
619
620             $m->connect('api/account/verify_credentials.:format',
621                         array('action' => 'ApiAccountVerifyCredentials'));
622
623             $m->connect('api/account/update_profile.:format',
624                         array('action' => 'ApiAccountUpdateProfile'));
625
626             $m->connect('api/account/update_profile_image.:format',
627                         array('action' => 'ApiAccountUpdateProfileImage'));
628
629             $m->connect('api/account/update_delivery_device.:format',
630                         array('action' => 'ApiAccountUpdateDeliveryDevice'));
631
632             // special case where verify_credentials is called w/out a format
633
634             $m->connect('api/account/verify_credentials',
635                         array('action' => 'ApiAccountVerifyCredentials'));
636
637             $m->connect('api/account/rate_limit_status.:format',
638                         array('action' => 'ApiAccountRateLimitStatus'));
639
640             // favorites
641
642             $m->connect('api/favorites.:format',
643                         array('action' => 'ApiTimelineFavorites',
644                               'format' => '(xml|json|rss|atom|as)'));
645
646             $m->connect('api/favorites/:id.:format',
647                         array('action' => 'ApiTimelineFavorites',
648                               'id' => Nickname::INPUT_FMT,
649                               'format' => '(xml|json|rss|atom|as)'));
650
651             $m->connect('api/favorites/create/:id.:format',
652                         array('action' => 'ApiFavoriteCreate',
653                               'id' => '[0-9]+',
654                               'format' => '(xml|json)'));
655
656             $m->connect('api/favorites/destroy/:id.:format',
657                         array('action' => 'ApiFavoriteDestroy',
658                               'id' => '[0-9]+',
659                               'format' => '(xml|json)'));
660             // blocks
661
662             $m->connect('api/blocks/create.:format',
663                         array('action' => 'ApiBlockCreate',
664                               'format' => '(xml|json)'));
665
666             $m->connect('api/blocks/create/:id.:format',
667                         array('action' => 'ApiBlockCreate',
668                               'id' => Nickname::INPUT_FMT,
669                               'format' => '(xml|json)'));
670
671             $m->connect('api/blocks/destroy.:format',
672                         array('action' => 'ApiBlockDestroy',
673                               'format' => '(xml|json)'));
674
675             $m->connect('api/blocks/destroy/:id.:format',
676                         array('action' => 'ApiBlockDestroy',
677                               'id' => Nickname::INPUT_FMT,
678                               'format' => '(xml|json)'));
679             // help
680
681             $m->connect('api/help/test.:format',
682                         array('action' => 'ApiHelpTest',
683                               'format' => '(xml|json)'));
684
685             // statusnet
686
687             $m->connect('api/statusnet/version.:format',
688                         array('action' => 'ApiStatusnetVersion',
689                               'format' => '(xml|json)'));
690
691             $m->connect('api/statusnet/config.:format',
692                         array('action' => 'ApiStatusnetConfig',
693                               'format' => '(xml|json)'));
694
695             // For older methods, we provide "laconica" base action
696
697             $m->connect('api/laconica/version.:format',
698                         array('action' => 'ApiStatusnetVersion',
699                               'format' => '(xml|json)'));
700
701             $m->connect('api/laconica/config.:format',
702                         array('action' => 'ApiStatusnetConfig',
703                               'format' => '(xml|json)'));
704
705             // Groups and tags are newer than 0.8.1 so no backward-compatibility
706             // necessary
707
708             // Groups
709             //'list' has to be handled differently, as php will not allow a method to be named 'list'
710
711             $m->connect('api/statusnet/groups/timeline/:id.:format',
712                         array('action' => 'ApiTimelineGroup',
713                               'id' => Nickname::INPUT_FMT,
714                               'format' => '(xml|json|rss|atom|as)'));
715
716             $m->connect('api/statusnet/groups/show.:format',
717                         array('action' => 'ApiGroupShow',
718                               'format' => '(xml|json)'));
719
720             $m->connect('api/statusnet/groups/show/:id.:format',
721                         array('action' => 'ApiGroupShow',
722                               'id' => Nickname::INPUT_FMT,
723                               'format' => '(xml|json)'));
724
725             $m->connect('api/statusnet/groups/join.:format',
726                         array('action' => 'ApiGroupJoin',
727                               'id' => Nickname::INPUT_FMT,
728                               'format' => '(xml|json)'));
729
730             $m->connect('api/statusnet/groups/join/:id.:format',
731                         array('action' => 'ApiGroupJoin',
732                               'format' => '(xml|json)'));
733
734             $m->connect('api/statusnet/groups/leave.:format',
735                         array('action' => 'ApiGroupLeave',
736                               'id' => Nickname::INPUT_FMT,
737                               'format' => '(xml|json)'));
738
739             $m->connect('api/statusnet/groups/leave/:id.:format',
740                         array('action' => 'ApiGroupLeave',
741                               'format' => '(xml|json)'));
742
743             $m->connect('api/statusnet/groups/is_member.:format',
744                         array('action' => 'ApiGroupIsMember',
745                               'format' => '(xml|json)'));
746
747             $m->connect('api/statusnet/groups/list.:format',
748                         array('action' => 'ApiGroupList',
749                               'format' => '(xml|json|rss|atom)'));
750
751             $m->connect('api/statusnet/groups/list/:id.:format',
752                         array('action' => 'ApiGroupList',
753                               'id' => Nickname::INPUT_FMT,
754                               'format' => '(xml|json|rss|atom)'));
755
756             $m->connect('api/statusnet/groups/list_all.:format',
757                         array('action' => 'ApiGroupListAll',
758                               'format' => '(xml|json|rss|atom)'));
759
760             $m->connect('api/statusnet/groups/membership.:format',
761                         array('action' => 'ApiGroupMembership',
762                               'format' => '(xml|json)'));
763
764             $m->connect('api/statusnet/groups/membership/:id.:format',
765                         array('action' => 'ApiGroupMembership',
766                               'id' => Nickname::INPUT_FMT,
767                               'format' => '(xml|json)'));
768
769             $m->connect('api/statusnet/groups/create.:format',
770                         array('action' => 'ApiGroupCreate',
771                               'format' => '(xml|json)'));
772
773             $m->connect('api/statusnet/groups/update/:id.:format',
774                         array('action' => 'ApiGroupProfileUpdate',
775                               'id' => '[a-zA-Z0-9]+',
776                               'format' => '(xml|json)'));
777
778             // Lists (people tags)
779
780             $m->connect('api/lists/memberships.:format',
781                         array('action' => 'ApiListMemberships',
782                               'format' => '(xml|json)'));
783
784             $m->connect('api/:user/lists/memberships.:format',
785                         array('action' => 'ApiListMemberships',
786                               'user' => '[a-zA-Z0-9]+',
787                               'format' => '(xml|json)'));
788
789             $m->connect('api/lists/subscriptions.:format',
790                         array('action' => 'ApiListSubscriptions',
791                               'format' => '(xml|json)'));
792
793             $m->connect('api/:user/lists/subscriptions.:format',
794                         array('action' => 'ApiListSubscriptions',
795                               'user' => '[a-zA-Z0-9]+',
796                               'format' => '(xml|json)'));
797             $m->connect('api/lists.:format',
798                         array('action' => 'ApiLists',
799                               'format' => '(xml|json)'));
800
801             $m->connect('api/:user/lists.:format',
802                         array('action' => 'ApiLists',
803                               'user' => '[a-zA-Z0-9]+',
804                               'format' => '(xml|json)'));
805
806             $m->connect('api/:user/lists/:id.:format',
807                         array('action' => 'ApiList',
808                               'user' => '[a-zA-Z0-9]+',
809                               'id' => '[a-zA-Z0-9]+',
810                               'format' => '(xml|json)'));
811
812             $m->connect('api/:user/lists/:id/statuses.:format',
813                         array('action' => 'ApiTimelineList',
814                               'user' => '[a-zA-Z0-9]+',
815                               'id' => '[a-zA-Z0-9]+',
816                               'format' => '(xml|json|rss|atom)'));
817
818             $m->connect('api/:user/:list_id/members.:format',
819                         array('action' => 'ApiListMembers',
820                               'user' => '[a-zA-Z0-9]+',
821                               'list_id' => '[a-zA-Z0-9]+',
822                               'format' => '(xml|json)'));
823
824             $m->connect('api/:user/:list_id/subscribers.:format',
825                         array('action' => 'ApiListSubscribers',
826                               'user' => '[a-zA-Z0-9]+',
827                               'list_id' => '[a-zA-Z0-9]+',
828                               'format' => '(xml|json)'));
829
830             $m->connect('api/:user/:list_id/members/:id.:format',
831                         array('action' => 'ApiListMember',
832                               'user' => '[a-zA-Z0-9]+',
833                               'list_id' => '[a-zA-Z0-9]+',
834                               'id' => '[a-zA-Z0-9]+',
835                               'format' => '(xml|json)'));
836
837             $m->connect('api/:user/:list_id/subscribers/:id.:format',
838                         array('action' => 'ApiListSubscriber',
839                               'user' => '[a-zA-Z0-9]+',
840                               'list_id' => '[a-zA-Z0-9]+',
841                               'id' => '[a-zA-Z0-9]+',
842                               'format' => '(xml|json)'));
843
844             // Tags
845             $m->connect('api/statusnet/tags/timeline/:tag.:format',
846                         array('action' => 'ApiTimelineTag',
847                               'format' => '(xml|json|rss|atom|as)'));
848
849             // media related
850             $m->connect(
851                 'api/statusnet/media/upload',
852                 array('action' => 'ApiMediaUpload')
853             );
854
855             // search
856             $m->connect('api/search.atom', array('action' => 'ApiSearchAtom'));
857             $m->connect('api/search.json', array('action' => 'ApiSearchJSON'));
858             $m->connect('api/trends.json', array('action' => 'ApiTrends'));
859
860             $m->connect('api/oauth/request_token',
861                         array('action' => 'ApiOauthRequestToken'));
862
863             $m->connect('api/oauth/access_token',
864                         array('action' => 'ApiOauthAccessToken'));
865
866             $m->connect('api/oauth/authorize',
867                         array('action' => 'ApiOauthAuthorize'));
868
869             // Admin
870
871             $m->connect('panel/site', array('action' => 'siteadminpanel'));
872             $m->connect('panel/user', array('action' => 'useradminpanel'));
873                 $m->connect('panel/access', array('action' => 'accessadminpanel'));
874             $m->connect('panel/paths', array('action' => 'pathsadminpanel'));
875             $m->connect('panel/sessions', array('action' => 'sessionsadminpanel'));
876             $m->connect('panel/sitenotice', array('action' => 'sitenoticeadminpanel'));
877             $m->connect('panel/snapshot', array('action' => 'snapshotadminpanel'));
878             $m->connect('panel/license', array('action' => 'licenseadminpanel'));
879
880             $m->connect('panel/plugins', array('action' => 'pluginsadminpanel'));
881             $m->connect('panel/plugins/enable/:plugin',
882                         array('action' => 'pluginenable'),
883                         array('plugin' => '[A-Za-z0-9_]+'));
884             $m->connect('panel/plugins/disable/:plugin',
885                         array('action' => 'plugindisable'),
886                         array('plugin' => '[A-Za-z0-9_]+'));
887
888             $m->connect('getfile/:filename',
889                         array('action' => 'getfile'),
890                         array('filename' => '[A-Za-z0-9._-]+'));
891
892             // In the "root"
893
894             if (common_config('singleuser', 'enabled')) {
895
896                 $nickname = User::singleUserNickname();
897
898                 foreach (array('subscriptions', 'subscribers',
899                                'all', 'foaf', 'xrds',
900                                'replies', 'microsummary', 'hcard') as $a) {
901                     $m->connect($a,
902                                 array('action' => $a,
903                                       'nickname' => $nickname));
904                 }
905
906                 foreach (array('subscriptions', 'subscribers') as $a) {
907                     $m->connect($a.'/:tag',
908                                 array('action' => $a,
909                                       'nickname' => $nickname),
910                                 array('tag' => self::REGEX_TAG));
911                 }
912
913                 $m->connect('subscribers/pending',
914                             array('action' => 'subqueue',
915                                   'nickname' => $nickname));
916
917                 foreach (array('rss', 'groups') as $a) {
918                     $m->connect($a,
919                                 array('action' => 'user'.$a,
920                                       'nickname' => $nickname));
921                 }
922
923                 foreach (array('all', 'replies', 'favorites') as $a) {
924                     $m->connect($a.'/rss',
925                                 array('action' => $a.'rss',
926                                       'nickname' => $nickname));
927                 }
928
929                 $m->connect('favorites',
930                             array('action' => 'showfavorites',
931                                   'nickname' => $nickname));
932
933                 $m->connect('avatar/:size',
934                             array('action' => 'avatarbynickname',
935                                   'nickname' => $nickname),
936                             array('size' => '(original|96|48|24)'));
937
938                 $m->connect('tag/:tag/rss',
939                             array('action' => 'userrss',
940                                   'nickname' => $nickname),
941                             array('tag' => self::REGEX_TAG));
942
943                 $m->connect('tag/:tag',
944                             array('action' => 'showstream',
945                                   'nickname' => $nickname),
946                             array('tag' => self::REGEX_TAG));
947
948                 $m->connect('rsd.xml',
949                             array('action' => 'rsd',
950                                   'nickname' => $nickname));
951
952                 $m->connect('',
953                             array('action' => 'showstream',
954                                   'nickname' => $nickname));
955             } else {
956                 $m->connect('', array('action' => 'public'));
957                 $m->connect('rss', array('action' => 'publicrss'));
958                 $m->connect('featuredrss', array('action' => 'featuredrss'));
959                 $m->connect('favoritedrss', array('action' => 'favoritedrss'));
960                 $m->connect('featured/', array('action' => 'featured'));
961                 $m->connect('featured', array('action' => 'featured'));
962                 $m->connect('favorited/', array('action' => 'favorited'));
963                 $m->connect('favorited', array('action' => 'favorited'));
964                 $m->connect('rsd.xml', array('action' => 'rsd'));
965
966                 foreach (array('subscriptions', 'subscribers',
967                                'nudge', 'all', 'foaf', 'xrds',
968                                'replies', 'inbox', 'outbox', 'microsummary', 'hcard') as $a) {
969                     $m->connect(':nickname/'.$a,
970                                 array('action' => $a),
971                                 array('nickname' => Nickname::DISPLAY_FMT));
972                 }
973                 $m->connect(':nickname/subscribers/pending',
974                             array('action' => 'subqueue'),
975                             array('nickname' => Nickname::DISPLAY_FMT));
976
977                 // people tags
978
979                 $m->connect('peopletags', array('action' => 'publicpeopletagcloud'));
980
981                 $m->connect('peopletag/:tag', array('action' => 'peopletag',
982                                                     'tag'    => self::REGEX_TAG));
983
984                 $m->connect('selftag/:tag', array('action' => 'selftag',
985                                                   'tag'    => self::REGEX_TAG));
986
987                 $m->connect('main/addpeopletag', array('action' => 'addpeopletag'));
988
989                 $m->connect('main/removepeopletag', array('action' => 'removepeopletag'));
990
991                 $m->connect('main/profilecompletion', array('action' => 'profilecompletion'));
992
993                 $m->connect('main/peopletagautocomplete', array('action' => 'peopletagautocomplete'));
994
995                 $m->connect(':nickname/peopletags',
996                                 array('action' => 'peopletagsbyuser',
997                                       'nickname' => Nickname::DISPLAY_FMT));
998
999                 $m->connect(':nickname/peopletags/private',
1000                                 array('action' => 'peopletagsbyuser',
1001                                       'nickname' => Nickname::DISPLAY_FMT,
1002                                       'private' => 1));
1003
1004                 $m->connect(':nickname/peopletags/public',
1005                                 array('action' => 'peopletagsbyuser',
1006                                       'nickname' => Nickname::DISPLAY_FMT,
1007                                       'public' => 1));
1008
1009                 $m->connect(':nickname/othertags',
1010                                 array('action' => 'peopletagsforuser',
1011                                       'nickname' => Nickname::DISPLAY_FMT));
1012
1013                 $m->connect(':nickname/peopletagsubscriptions',
1014                                 array('action' => 'peopletagsubscriptions',
1015                                       'nickname' => Nickname::DISPLAY_FMT));
1016
1017                 $m->connect(':tagger/all/:tag/subscribers',
1018                                 array('action' => 'peopletagsubscribers',
1019                                       'tagger' => Nickname::DISPLAY_FMT,
1020                                       'tag' => self::REGEX_TAG));
1021
1022                 $m->connect(':tagger/all/:tag/tagged',
1023                                 array('action' => 'peopletagged',
1024                                       'tagger' => Nickname::DISPLAY_FMT,
1025                                       'tag' => self::REGEX_TAG));
1026
1027                 $m->connect(':tagger/all/:tag/edit',
1028                                 array('action' => 'editpeopletag',
1029                                       'tagger' => Nickname::DISPLAY_FMT,
1030                                       'tag' => self::REGEX_TAG));
1031
1032                 foreach(array('subscribe', 'unsubscribe') as $v) {
1033                     $m->connect('peopletag/:id/'.$v,
1034                                     array('action' => $v.'peopletag',
1035                                           'id' => '[0-9]{1,64}'));
1036                 }
1037                 $m->connect('user/:tagger_id/profiletag/:id/id',
1038                                 array('action' => 'profiletagbyid',
1039                                       'tagger_id' => '[0-9]+',
1040                                       'id' => '[0-9]+'));
1041
1042                 $m->connect(':tagger/all/:tag',
1043                                 array('action' => 'showprofiletag',
1044                                       'tagger' => Nickname::DISPLAY_FMT,
1045                                       'tag' => self::REGEX_TAG));
1046
1047                 foreach (array('subscriptions', 'subscribers') as $a) {
1048                     $m->connect(':nickname/'.$a.'/:tag',
1049                                 array('action' => $a),
1050                                 array('tag' => self::REGEX_TAG,
1051                                       'nickname' => Nickname::DISPLAY_FMT));
1052                 }
1053
1054                 foreach (array('rss', 'groups') as $a) {
1055                     $m->connect(':nickname/'.$a,
1056                                 array('action' => 'user'.$a),
1057                                 array('nickname' => Nickname::DISPLAY_FMT));
1058                 }
1059
1060                 foreach (array('all', 'replies', 'favorites') as $a) {
1061                     $m->connect(':nickname/'.$a.'/rss',
1062                                 array('action' => $a.'rss'),
1063                                 array('nickname' => Nickname::DISPLAY_FMT));
1064                 }
1065
1066                 $m->connect(':nickname/favorites',
1067                             array('action' => 'showfavorites'),
1068                             array('nickname' => Nickname::DISPLAY_FMT));
1069
1070                 $m->connect(':nickname/avatar/:size',
1071                             array('action' => 'avatarbynickname'),
1072                             array('size' => '(original|96|48|24)',
1073                                   'nickname' => Nickname::DISPLAY_FMT));
1074
1075                 $m->connect(':nickname/tag/:tag/rss',
1076                             array('action' => 'userrss'),
1077                             array('nickname' => Nickname::DISPLAY_FMT),
1078                             array('tag' => self::REGEX_TAG));
1079
1080                 $m->connect(':nickname/tag/:tag',
1081                             array('action' => 'showstream'),
1082                             array('nickname' => Nickname::DISPLAY_FMT),
1083                             array('tag' => self::REGEX_TAG));
1084
1085                 $m->connect(':nickname/rsd.xml',
1086                             array('action' => 'rsd'),
1087                             array('nickname' => Nickname::DISPLAY_FMT));
1088
1089                 $m->connect(':nickname',
1090                             array('action' => 'showstream'),
1091                             array('nickname' => Nickname::DISPLAY_FMT));
1092             }
1093
1094             // AtomPub API
1095
1096             $m->connect('api/statusnet/app/service/:id.xml',
1097                         array('action' => 'ApiAtomService'),
1098                         array('id' => Nickname::DISPLAY_FMT));
1099
1100             $m->connect('api/statusnet/app/service.xml',
1101                         array('action' => 'ApiAtomService'));
1102
1103             $m->connect('api/statusnet/app/subscriptions/:subscriber/:subscribed.atom',
1104                         array('action' => 'AtomPubShowSubscription'),
1105                         array('subscriber' => '[0-9]+',
1106                               'subscribed' => '[0-9]+'));
1107
1108             $m->connect('api/statusnet/app/subscriptions/:subscriber.atom',
1109                         array('action' => 'AtomPubSubscriptionFeed'),
1110                         array('subscriber' => '[0-9]+'));
1111
1112             $m->connect('api/statusnet/app/favorites/:profile/:notice.atom',
1113                         array('action' => 'AtomPubShowFavorite'),
1114                         array('profile' => '[0-9]+',
1115                               'notice' => '[0-9]+'));
1116
1117             $m->connect('api/statusnet/app/favorites/:profile.atom',
1118                         array('action' => 'AtomPubFavoriteFeed'),
1119                         array('profile' => '[0-9]+'));
1120
1121             $m->connect('api/statusnet/app/memberships/:profile/:group.atom',
1122                         array('action' => 'AtomPubShowMembership'),
1123                         array('profile' => '[0-9]+',
1124                               'group' => '[0-9]+'));
1125
1126             $m->connect('api/statusnet/app/memberships/:profile.atom',
1127                         array('action' => 'AtomPubMembershipFeed'),
1128                         array('profile' => '[0-9]+'));
1129
1130             // URL shortening
1131
1132             $m->connect('url/:id',
1133                         array('action' => 'redirecturl',
1134                               'id' => '[0-9]+'));
1135
1136             // user stuff
1137
1138             Event::handle('RouterInitialized', array($m));
1139         }
1140
1141         return $m;
1142     }
1143
1144     function map($path)
1145     {
1146         try {
1147             $match = $this->m->match($path);
1148         } catch (Net_URL_Mapper_InvalidException $e) {
1149             common_log(LOG_ERR, "Problem getting route for $path - " .
1150                        $e->getMessage());
1151             // TRANS: Client error on action trying to visit a non-existing page.
1152             $cac = new ClientErrorAction(_('Page not found.'), 404);
1153             $cac->showPage();
1154         }
1155
1156         return $match;
1157     }
1158
1159     function build($action, $args=null, $params=null, $fragment=null)
1160     {
1161         $action_arg = array('action' => $action);
1162
1163         if ($args) {
1164             $args = array_merge($action_arg, $args);
1165         } else {
1166             $args = $action_arg;
1167         }
1168
1169         $url = $this->m->generate($args, $params, $fragment);
1170
1171         // Due to a bug in the Net_URL_Mapper code, the returned URL may
1172         // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
1173         // repair that here rather than modifying the upstream code...
1174
1175         $qpos = strpos($url, '?');
1176         if ($qpos !== false) {
1177             $url = substr($url, 0, $qpos+1) .
1178                 str_replace('?', '&', substr($url, $qpos+1));
1179
1180             // @fixme this is a hacky workaround for http_build_query in the
1181             // lower-level code and bad configs that set the default separator
1182             // to &amp; instead of &. Encoded &s in parameters will not be
1183             // affected.
1184             $url = substr($url, 0, $qpos+1) .
1185                 str_replace('&amp;', '&', substr($url, $qpos+1));
1186
1187         }
1188
1189         return $url;
1190     }
1191 }