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