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