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