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