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