]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/router.php
Merge branch 'nightly' of https://git.gnu.io/gnu/gnu-social into nightly
[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/download',
227                         array('action' => 'attachment_download'),
228                         array('attachment' => '[0-9]+'));
229
230             $m->connect('attachment/:attachment/thumbnail',
231                         array('action' => 'attachment_thumbnail'),
232                         array('attachment' => '[0-9]+'));
233
234             $m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
235                         array('action' => 'newnotice'),
236                         array('replyto' => Nickname::DISPLAY_FMT),
237                         array('inreplyto' => '[0-9]+'));
238
239             $m->connect('notice/new?replyto=:replyto',
240                         array('action' => 'newnotice'),
241                         array('replyto' => Nickname::DISPLAY_FMT));
242
243             $m->connect('notice/new', array('action' => 'newnotice'));
244
245             $m->connect('notice/:notice',
246                         array('action' => 'shownotice'),
247                         array('notice' => '[0-9]+'));
248
249             $m->connect('notice/:notice/delete',
250                         array('action' => 'deletenotice'),
251                         array('notice' => '[0-9]+'));
252
253             // conversation
254
255             $m->connect('conversation/:id',
256                         array('action' => 'conversation'),
257                         array('id' => '[0-9]+'));
258
259             $m->connect('user/:id',
260                         array('action' => 'userbyid'),
261                         array('id' => '[0-9]+'));
262
263             $m->connect('tag/:tag/rss',
264                         array('action' => 'tagrss'),
265                         array('tag' => self::REGEX_TAG));
266             $m->connect('tag/:tag',
267                         array('action' => 'tag'),
268                         array('tag' => self::REGEX_TAG));
269
270             // groups
271
272             $m->connect('group/new', array('action' => 'newgroup'));
273
274             foreach (array('edit', 'join', 'leave', 'delete', 'cancel', 'approve') as $v) {
275                 $m->connect('group/:nickname/'.$v,
276                             array('action' => $v.'group'),
277                             array('nickname' => Nickname::DISPLAY_FMT));
278                 $m->connect('group/:id/id/'.$v,
279                             array('action' => $v.'group'),
280                             array('id' => '[0-9]+'));
281             }
282
283             foreach (array('members', 'logo', 'rss') as $n) {
284                 $m->connect('group/:nickname/'.$n,
285                             array('action' => 'group'.$n),
286                             array('nickname' => Nickname::DISPLAY_FMT));
287             }
288
289             $m->connect('group/:nickname/foaf',
290                         array('action' => 'foafgroup'),
291                         array('nickname' => Nickname::DISPLAY_FMT));
292
293             $m->connect('group/:nickname/blocked',
294                         array('action' => 'blockedfromgroup'),
295                         array('nickname' => Nickname::DISPLAY_FMT));
296
297             $m->connect('group/:nickname/makeadmin',
298                         array('action' => 'makeadmin'),
299                         array('nickname' => Nickname::DISPLAY_FMT));
300
301             $m->connect('group/:nickname/members/pending',
302                         array('action' => 'groupqueue'),
303                         array('nickname' => Nickname::DISPLAY_FMT));
304
305             $m->connect('group/:id/id',
306                         array('action' => 'groupbyid'),
307                         array('id' => '[0-9]+'));
308
309             $m->connect('group/:nickname',
310                         array('action' => 'showgroup'),
311                         array('nickname' => Nickname::DISPLAY_FMT));
312
313             $m->connect('group/:nickname/',
314                         array('action' => 'showgroup'),
315                         array('nickname' => Nickname::DISPLAY_FMT));
316
317             $m->connect('group/', array('action' => 'groups'));
318             $m->connect('group', array('action' => 'groups'));
319             $m->connect('groups/', array('action' => 'groups'));
320             $m->connect('groups', array('action' => 'groups'));
321
322             // Twitter-compatible API
323
324             // statuses API
325
326             $m->connect('api',
327                         array('action' => 'Redirect',
328                               'nextAction' => 'doc',
329                               'args' => array('title' => 'api')));
330
331             $m->connect('api/statuses/public_timeline.:format',
332                         array('action' => 'ApiTimelinePublic',
333                               'format' => '(xml|json|rss|atom|as)'));
334
335             // this is not part of the Twitter API. Also may require authentication depending on server config!
336             $m->connect('api/statuses/networkpublic_timeline.:format',
337                         array('action' => 'ApiTimelineNetworkPublic',
338                               'format' => '(xml|json|rss|atom|as)'));
339
340             $m->connect('api/statuses/friends_timeline/:id.:format',
341                         array('action' => 'ApiTimelineFriends',
342                               'id' => Nickname::INPUT_FMT,
343                               'format' => '(xml|json|rss|atom|as)'));
344
345             $m->connect('api/statuses/friends_timeline.:format',
346                         array('action' => 'ApiTimelineFriends',
347                               'format' => '(xml|json|rss|atom|as)'));
348
349             $m->connect('api/statuses/home_timeline/:id.:format',
350                         array('action' => 'ApiTimelineHome',
351                               'id' => Nickname::INPUT_FMT,
352                               'format' => '(xml|json|rss|atom|as)'));
353
354             $m->connect('api/statuses/home_timeline.:format',
355                         array('action' => 'ApiTimelineHome',
356                               'format' => '(xml|json|rss|atom|as)'));
357
358             $m->connect('api/statuses/user_timeline/:id.:format',
359                         array('action' => 'ApiTimelineUser',
360                               'id' => Nickname::INPUT_FMT,
361                               'format' => '(xml|json|rss|atom|as)'));
362
363             $m->connect('api/statuses/user_timeline.:format',
364                         array('action' => 'ApiTimelineUser',
365                               'format' => '(xml|json|rss|atom|as)'));
366
367             $m->connect('api/statuses/mentions/:id.:format',
368                         array('action' => 'ApiTimelineMentions',
369                               'id' => Nickname::INPUT_FMT,
370                               'format' => '(xml|json|rss|atom|as)'));
371
372             $m->connect('api/statuses/mentions.:format',
373                         array('action' => 'ApiTimelineMentions',
374                               'format' => '(xml|json|rss|atom|as)'));
375
376             $m->connect('api/statuses/replies/:id.:format',
377                         array('action' => 'ApiTimelineMentions',
378                               'id' => Nickname::INPUT_FMT,
379                               'format' => '(xml|json|rss|atom|as)'));
380
381             $m->connect('api/statuses/replies.:format',
382                         array('action' => 'ApiTimelineMentions',
383                               'format' => '(xml|json|rss|atom|as)'));
384  
385             $m->connect('api/statuses/mentions_timeline/:id.:format',
386                         array('action' => 'ApiTimelineMentions',
387                               'id' => Nickname::INPUT_FMT,
388                               'format' => '(xml|json|rss|atom|as)'));
389
390             $m->connect('api/statuses/mentions_timeline.:format',
391                         array('action' => 'ApiTimelineMentions',
392                               'format' => '(xml|json|rss|atom|as)'));
393
394             $m->connect('api/statuses/friends/:id.:format',
395                         array('action' => 'ApiUserFriends',
396                               'id' => Nickname::INPUT_FMT,
397                               'format' => '(xml|json)'));
398
399             $m->connect('api/statuses/friends.:format',
400                         array('action' => 'ApiUserFriends',
401                               'format' => '(xml|json)'));
402
403             $m->connect('api/statuses/followers/:id.:format',
404                         array('action' => 'ApiUserFollowers',
405                               'id' => Nickname::INPUT_FMT,
406                               'format' => '(xml|json)'));
407
408             $m->connect('api/statuses/followers.:format',
409                         array('action' => 'ApiUserFollowers',
410                               'format' => '(xml|json)'));
411
412             $m->connect('api/statuses/show/:id.:format',
413                         array('action' => 'ApiStatusesShow',
414                               'id' => '[0-9]+',
415                               'format' => '(xml|json|atom)'));
416
417             $m->connect('api/statuses/show.:format',
418                         array('action' => 'ApiStatusesShow',
419                               'format' => '(xml|json|atom)'));
420
421             $m->connect('api/statuses/update.:format',
422                         array('action' => 'ApiStatusesUpdate',
423                               'format' => '(xml|json)'));
424
425             $m->connect('api/statuses/destroy/:id.:format',
426                         array('action' => 'ApiStatusesDestroy',
427                               'id' => '[0-9]+',
428                               'format' => '(xml|json)'));
429
430             $m->connect('api/statuses/destroy.:format',
431                         array('action' => 'ApiStatusesDestroy',
432                               'format' => '(xml|json)'));
433
434             // START qvitter API additions
435             
436             $m->connect('api/attachment/:id.:format',
437                         array('action' => 'ApiAttachment',
438                               'id' => '[0-9]+',
439                               'format' => '(xml|json)'));
440             
441             $m->connect('api/checkhub.:format',
442                         array('action' => 'ApiCheckHub',
443                               'format' => '(xml|json)'));
444             
445             $m->connect('api/externalprofile/show.:format',
446                         array('action' => 'ApiExternalProfileShow',
447                               'format' => '(xml|json)'));
448
449             $m->connect('api/statusnet/groups/admins/:id.:format',
450                         array('action' => 'ApiGroupAdmins',
451                               'id' => Nickname::INPUT_FMT,
452                               'format' => '(xml|json)'));
453             
454             $m->connect('api/account/update_link_color.:format',
455                         array('action' => 'ApiAccountUpdateLinkColor',
456                               'format' => '(xml|json)'));
457                 
458             $m->connect('api/account/update_background_color.:format',
459                         array('action' => 'ApiAccountUpdateBackgroundColor',
460                               'format' => '(xml|json)'));
461
462             $m->connect('api/account/register.:format',
463                         array('action' => 'ApiAccountRegister',
464                               'format' => '(xml|json)'));
465             
466             $m->connect('api/check_nickname.:format',
467                         array('action' => 'ApiCheckNickname',
468                               'format' => '(xml|json)'));
469
470             // END qvitter API additions
471
472             // users
473
474             $m->connect('api/users/show/:id.:format',
475                         array('action' => 'ApiUserShow',
476                               'id' => Nickname::INPUT_FMT,
477                               'format' => '(xml|json)'));
478
479             $m->connect('api/users/show.:format',
480                         array('action' => 'ApiUserShow',
481                               'format' => '(xml|json)'));
482
483             $m->connect('api/users/profile_image/:screen_name.:format',
484                         array('action' => 'ApiUserProfileImage',
485                               'screen_name' => Nickname::DISPLAY_FMT,
486                               'format' => '(xml|json)'));
487
488             // friendships
489
490             $m->connect('api/friendships/show.:format',
491                         array('action' => 'ApiFriendshipsShow',
492                               'format' => '(xml|json)'));
493
494             $m->connect('api/friendships/exists.:format',
495                         array('action' => 'ApiFriendshipsExists',
496                               'format' => '(xml|json)'));
497
498             $m->connect('api/friendships/create/:id.:format',
499                         array('action' => 'ApiFriendshipsCreate',
500                               'id' => Nickname::INPUT_FMT,
501                               'format' => '(xml|json)'));
502
503             $m->connect('api/friendships/create.:format',
504                         array('action' => 'ApiFriendshipsCreate',
505                               'format' => '(xml|json)'));
506
507             $m->connect('api/friendships/destroy/:id.:format',
508                         array('action' => 'ApiFriendshipsDestroy',
509                               'id' => Nickname::INPUT_FMT,
510                               'format' => '(xml|json)'));
511
512             $m->connect('api/friendships/destroy.:format',
513                         array('action' => 'ApiFriendshipsDestroy',
514                               'format' => '(xml|json)'));
515
516             // Social graph
517
518             $m->connect('api/friends/ids/:id.:format',
519                         array('action' => 'ApiUserFriends',
520                               'ids_only' => true));
521
522             $m->connect('api/followers/ids/:id.:format',
523                         array('action' => 'ApiUserFollowers',
524                               'ids_only' => true));
525
526             $m->connect('api/friends/ids.:format',
527                         array('action' => 'ApiUserFriends',
528                               'ids_only' => true));
529
530             $m->connect('api/followers/ids.:format',
531                         array('action' => 'ApiUserFollowers',
532                               'ids_only' => true));
533
534             // account
535
536             $m->connect('api/account/verify_credentials.:format',
537                         array('action' => 'ApiAccountVerifyCredentials'));
538
539             $m->connect('api/account/update_profile.:format',
540                         array('action' => 'ApiAccountUpdateProfile'));
541
542             $m->connect('api/account/update_profile_image.:format',
543                         array('action' => 'ApiAccountUpdateProfileImage'));
544
545             $m->connect('api/account/update_delivery_device.:format',
546                         array('action' => 'ApiAccountUpdateDeliveryDevice'));
547
548             // special case where verify_credentials is called w/out a format
549
550             $m->connect('api/account/verify_credentials',
551                         array('action' => 'ApiAccountVerifyCredentials'));
552
553             $m->connect('api/account/rate_limit_status.:format',
554                         array('action' => 'ApiAccountRateLimitStatus'));
555
556             // blocks
557
558             $m->connect('api/blocks/create/:id.:format',
559                         array('action' => 'ApiBlockCreate',
560                               'id' => Nickname::INPUT_FMT,
561                               'format' => '(xml|json)'));
562
563             $m->connect('api/blocks/create.:format',
564                         array('action' => 'ApiBlockCreate',
565                               'format' => '(xml|json)'));
566
567             $m->connect('api/blocks/destroy/:id.:format',
568                         array('action' => 'ApiBlockDestroy',
569                               'id' => Nickname::INPUT_FMT,
570                               'format' => '(xml|json)'));
571
572             $m->connect('api/blocks/destroy.:format',
573                         array('action' => 'ApiBlockDestroy',
574                               'format' => '(xml|json)'));
575
576             // help
577
578             $m->connect('api/help/test.:format',
579                         array('action' => 'ApiHelpTest',
580                               'format' => '(xml|json)'));
581
582             // statusnet
583
584             $m->connect('api/statusnet/version.:format',
585                         array('action' => 'ApiGNUsocialVersion',
586                               'format' => '(xml|json)'));
587
588             $m->connect('api/statusnet/config.:format',
589                         array('action' => 'ApiGNUsocialConfig',
590                               'format' => '(xml|json)'));
591
592             // For our current software name, we provide "gnusocial" base action
593
594             $m->connect('api/gnusocial/version.:format',
595                         array('action' => 'ApiGNUsocialVersion',
596                               'format' => '(xml|json)'));
597
598             $m->connect('api/gnusocial/config.:format',
599                         array('action' => 'ApiGNUsocialConfig',
600                               'format' => '(xml|json)'));
601
602             // Groups and tags are newer than 0.8.1 so no backward-compatibility
603             // necessary
604
605             // Groups
606             //'list' has to be handled differently, as php will not allow a method to be named 'list'
607
608             $m->connect('api/statusnet/groups/timeline/:id.:format',
609                         array('action' => 'ApiTimelineGroup',
610                               'id' => Nickname::INPUT_FMT,
611                               'format' => '(xml|json|rss|atom|as)'));
612
613             $m->connect('api/statusnet/groups/show/:id.:format',
614                         array('action' => 'ApiGroupShow',
615                               'id' => Nickname::INPUT_FMT,
616                               'format' => '(xml|json)'));
617
618             $m->connect('api/statusnet/groups/show.:format',
619                         array('action' => 'ApiGroupShow',
620                               'format' => '(xml|json)'));
621
622             $m->connect('api/statusnet/groups/join/:id.:format',
623                         array('action' => 'ApiGroupJoin',
624                               'id' => Nickname::INPUT_FMT,
625                               'format' => '(xml|json)'));
626
627             $m->connect('api/statusnet/groups/join.:format',
628                         array('action' => 'ApiGroupJoin',
629                               'format' => '(xml|json)'));
630
631             $m->connect('api/statusnet/groups/leave/:id.:format',
632                         array('action' => 'ApiGroupLeave',
633                               'format' => '(xml|json)'));
634
635             $m->connect('api/statusnet/groups/leave.:format',
636                         array('action' => 'ApiGroupLeave',
637                               'id' => Nickname::INPUT_FMT,
638                               'format' => '(xml|json)'));
639
640             $m->connect('api/statusnet/groups/is_member.:format',
641                         array('action' => 'ApiGroupIsMember',
642                               'format' => '(xml|json)'));
643
644             $m->connect('api/statusnet/groups/list/:id.:format',
645                         array('action' => 'ApiGroupList',
646                               'id' => Nickname::INPUT_FMT,
647                               'format' => '(xml|json|rss|atom)'));
648
649             $m->connect('api/statusnet/groups/list.:format',
650                         array('action' => 'ApiGroupList',
651                               'format' => '(xml|json|rss|atom)'));
652
653             $m->connect('api/statusnet/groups/list_all.:format',
654                         array('action' => 'ApiGroupListAll',
655                               'format' => '(xml|json|rss|atom)'));
656
657             $m->connect('api/statusnet/groups/membership/:id.:format',
658                         array('action' => 'ApiGroupMembership',
659                               'id' => Nickname::INPUT_FMT,
660                               'format' => '(xml|json)'));
661
662             $m->connect('api/statusnet/groups/membership.:format',
663                         array('action' => 'ApiGroupMembership',
664                               'format' => '(xml|json)'));
665
666             $m->connect('api/statusnet/groups/create.:format',
667                         array('action' => 'ApiGroupCreate',
668                               'format' => '(xml|json)'));
669
670             $m->connect('api/statusnet/groups/update/:id.:format',
671                         array('action' => 'ApiGroupProfileUpdate',
672                               'id' => '[a-zA-Z0-9]+',
673                               'format' => '(xml|json)'));
674                               
675             $m->connect('api/statusnet/conversation/:id.:format',
676                         array('action' => 'apiconversation',
677                               'id' => '[0-9]+',
678                               'format' => '(xml|json|rss|atom|as)'));
679
680             // Lists (people tags)
681             $m->connect('api/lists/list.:format',
682                         array('action' => 'ApiListSubscriptions',
683                               'format' => '(xml|json)'));
684
685             $m->connect('api/lists/memberships.:format',
686                         array('action' => 'ApiListMemberships',
687                               'format' => '(xml|json)'));
688
689             $m->connect('api/:user/lists/memberships.:format',
690                         array('action' => 'ApiListMemberships',
691                               'user' => '[a-zA-Z0-9]+',
692                               'format' => '(xml|json)'));
693
694             $m->connect('api/lists/subscriptions.:format',
695                         array('action' => 'ApiListSubscriptions',
696                               'format' => '(xml|json)'));
697
698             $m->connect('api/:user/lists/subscriptions.:format',
699                         array('action' => 'ApiListSubscriptions',
700                               'user' => '[a-zA-Z0-9]+',
701                               'format' => '(xml|json)'));
702
703             $m->connect('api/lists.:format',
704                         array('action' => 'ApiLists',
705                               'format' => '(xml|json)'));
706
707             $m->connect('api/:user/lists/:id.:format',
708                         array('action' => 'ApiList',
709                               'user' => '[a-zA-Z0-9]+',
710                               'id' => '[a-zA-Z0-9]+',
711                               'format' => '(xml|json)'));
712
713             $m->connect('api/:user/lists.:format',
714                         array('action' => 'ApiLists',
715                               'user' => '[a-zA-Z0-9]+',
716                               'format' => '(xml|json)'));
717
718             $m->connect('api/:user/lists/:id/statuses.:format',
719                         array('action' => 'ApiTimelineList',
720                               'user' => '[a-zA-Z0-9]+',
721                               'id' => '[a-zA-Z0-9]+',
722                               'format' => '(xml|json|rss|atom)'));
723
724             $m->connect('api/:user/:list_id/members/:id.:format',
725                         array('action' => 'ApiListMember',
726                               'user' => '[a-zA-Z0-9]+',
727                               'list_id' => '[a-zA-Z0-9]+',
728                               'id' => '[a-zA-Z0-9]+',
729                               'format' => '(xml|json)'));
730
731             $m->connect('api/:user/:list_id/members.:format',
732                         array('action' => 'ApiListMembers',
733                               'user' => '[a-zA-Z0-9]+',
734                               'list_id' => '[a-zA-Z0-9]+',
735                               'format' => '(xml|json)'));
736
737             $m->connect('api/:user/:list_id/subscribers/:id.:format',
738                         array('action' => 'ApiListSubscriber',
739                               'user' => '[a-zA-Z0-9]+',
740                               'list_id' => '[a-zA-Z0-9]+',
741                               'id' => '[a-zA-Z0-9]+',
742                               'format' => '(xml|json)'));
743
744             $m->connect('api/:user/:list_id/subscribers.:format',
745                         array('action' => 'ApiListSubscribers',
746                               'user' => '[a-zA-Z0-9]+',
747                               'list_id' => '[a-zA-Z0-9]+',
748                               'format' => '(xml|json)'));
749
750             // Tags
751             $m->connect('api/statusnet/tags/timeline/:tag.:format',
752                         array('action' => 'ApiTimelineTag',
753                               'tag'    => self::REGEX_TAG,
754                               'format' => '(xml|json|rss|atom|as)'));
755
756             // media related
757             $m->connect(
758                 'api/statusnet/media/upload',
759                 array('action' => 'ApiMediaUpload')
760             );
761             $m->connect(
762                 'api/statuses/update_with_media.json',
763                 array('action' => 'ApiMediaUpload')
764             );
765             // Twitter Media upload API v1.1
766             $m->connect(
767                 'api/media/upload.:format',
768                 array('action' => 'ApiMediaUpload',
769                       'format' => '(xml|json)',
770                       )
771             );
772
773             // search
774             $m->connect('api/search.atom', array('action' => 'ApiSearchAtom'));
775             $m->connect('api/search.json', array('action' => 'ApiSearchJSON'));
776             $m->connect('api/trends.json', array('action' => 'ApiTrends'));
777
778             $m->connect('api/oauth/request_token',
779                         array('action' => 'ApiOAuthRequestToken'));
780
781             $m->connect('api/oauth/access_token',
782                         array('action' => 'ApiOAuthAccessToken'));
783
784             $m->connect('api/oauth/authorize',
785                         array('action' => 'ApiOAuthAuthorize'));
786
787             // Admin
788
789             $m->connect('panel/site', array('action' => 'siteadminpanel'));
790             $m->connect('panel/user', array('action' => 'useradminpanel'));
791             $m->connect('panel/access', array('action' => 'accessadminpanel'));
792             $m->connect('panel/paths', array('action' => 'pathsadminpanel'));
793             $m->connect('panel/sessions', array('action' => 'sessionsadminpanel'));
794             $m->connect('panel/sitenotice', array('action' => 'sitenoticeadminpanel'));
795             $m->connect('panel/license', array('action' => 'licenseadminpanel'));
796
797             $m->connect('panel/plugins', array('action' => 'pluginsadminpanel'));
798             $m->connect('panel/plugins/enable/:plugin',
799                         array('action' => 'pluginenable'),
800                         array('plugin' => '[A-Za-z0-9_]+'));
801             $m->connect('panel/plugins/disable/:plugin',
802                         array('action' => 'plugindisable'),
803                         array('plugin' => '[A-Za-z0-9_]+'));
804
805             $m->connect('getfile/:filename',
806                         array('action' => 'getfile'),
807                         array('filename' => '[A-Za-z0-9._-]+'));
808
809             // Common people-tag stuff
810
811             $m->connect('peopletag/:tag', array('action' => 'peopletag',
812                                                 'tag'    => self::REGEX_TAG));
813
814             $m->connect('selftag/:tag', array('action' => 'selftag',
815                                               'tag'    => self::REGEX_TAG));
816
817             $m->connect('main/addpeopletag', array('action' => 'addpeopletag'));
818
819             $m->connect('main/removepeopletag', array('action' => 'removepeopletag'));
820
821             $m->connect('main/profilecompletion', array('action' => 'profilecompletion'));
822
823             $m->connect('main/peopletagautocomplete', array('action' => 'peopletagautocomplete'));
824
825             // In the "root"
826
827             if (common_config('singleuser', 'enabled')) {
828
829                 $nickname = User::singleUserNickname();
830
831                 foreach (array('subscriptions', 'subscribers',
832                                'all', 'foaf', 'replies',
833                                ) as $a) {
834                     $m->connect($a,
835                                 array('action' => $a,
836                                       'nickname' => $nickname));
837                 }
838
839                 foreach (array('subscriptions', 'subscribers') as $a) {
840                     $m->connect($a.'/:tag',
841                                 array('action' => $a,
842                                       'nickname' => $nickname),
843                                 array('tag' => self::REGEX_TAG));
844                 }
845
846                 $m->connect('subscribers/pending',
847                             array('action' => 'subqueue',
848                                   'nickname' => $nickname));
849
850                 foreach (array('rss', 'groups') as $a) {
851                     $m->connect($a,
852                                 array('action' => 'user'.$a,
853                                       'nickname' => $nickname));
854                 }
855
856                 foreach (array('all', 'replies') as $a) {
857                     $m->connect($a.'/rss',
858                                 array('action' => $a.'rss',
859                                       'nickname' => $nickname));
860                 }
861
862                 $m->connect('avatar',
863                             array('action' => 'avatarbynickname',
864                                   'nickname' => $nickname));
865                 $m->connect('avatar/:size',
866                             array('action' => 'avatarbynickname',
867                                   'nickname' => $nickname),
868                             array('size' => '(|original|\d+)'));
869
870                 $m->connect('tag/:tag/rss',
871                             array('action' => 'userrss',
872                                   'nickname' => $nickname),
873                             array('tag' => self::REGEX_TAG));
874
875                 $m->connect('tag/:tag',
876                             array('action' => 'showstream',
877                                   'nickname' => $nickname),
878                             array('tag' => self::REGEX_TAG));
879
880                 $m->connect('rsd.xml',
881                             array('action' => 'rsd',
882                                   'nickname' => $nickname));
883
884                 // peopletags
885
886                 $m->connect('peopletags',
887                             array('action' => 'peopletagsbyuser'));
888
889                 $m->connect('peopletags/private',
890                             array('action' => 'peopletagsbyuser',
891                                   'private' => 1));
892
893                 $m->connect('peopletags/public',
894                             array('action' => 'peopletagsbyuser',
895                                   'public' => 1));
896
897                 $m->connect('othertags',
898                             array('action' => 'peopletagsforuser'));
899
900                 $m->connect('peopletagsubscriptions',
901                             array('action' => 'peopletagsubscriptions'));
902
903                 $m->connect('all/:tag/subscribers',
904                             array('action' => 'peopletagsubscribers',
905                                   'tag' => self::REGEX_TAG));
906
907                 $m->connect('all/:tag/tagged',
908                                 array('action' => 'peopletagged',
909                                       'tag' => self::REGEX_TAG));
910
911                 $m->connect('all/:tag/edit',
912                                 array('action' => 'editpeopletag',
913                                       'tag' => self::REGEX_TAG));
914
915                 foreach(array('subscribe', 'unsubscribe') as $v) {
916                     $m->connect('peopletag/:id/'.$v,
917                                     array('action' => $v.'peopletag',
918                                           'id' => '[0-9]{1,64}'));
919                 }
920                 $m->connect('user/:tagger_id/profiletag/:id/id',
921                                 array('action' => 'profiletagbyid',
922                                       'tagger_id' => '[0-9]+',
923                                       'id' => '[0-9]+'));
924
925                 $m->connect('all/:tag',
926                                 array('action' => 'showprofiletag',
927                                       'nickname' => $nickname,
928                                       'tag' => self::REGEX_TAG));
929
930                 foreach (array('subscriptions', 'subscribers') as $a) {
931                     $m->connect($a.'/:tag',
932                                 array('action' => $a),
933                                 array('tag' => self::REGEX_TAG));
934                 }
935             }
936
937             $m->connect('rss', array('action' => 'publicrss'));
938             $m->connect('featuredrss', array('action' => 'featuredrss'));
939             $m->connect('featured/', array('action' => 'featured'));
940             $m->connect('featured', array('action' => 'featured'));
941             $m->connect('rsd.xml', array('action' => 'rsd'));
942
943             foreach (array('subscriptions', 'subscribers',
944                            'nudge', 'all', 'foaf', 'replies',
945                            'inbox', 'outbox') as $a) {
946                 $m->connect(':nickname/'.$a,
947                             array('action' => $a),
948                             array('nickname' => Nickname::DISPLAY_FMT));
949             }
950             $m->connect(':nickname/subscribers/pending',
951                         array('action' => 'subqueue'),
952                         array('nickname' => Nickname::DISPLAY_FMT));
953
954             // some targeted RSS 1.0 actions (extends TargetedRss10Action)
955             foreach (array('all', 'replies') as $a) {
956                 $m->connect(':nickname/'.$a.'/rss',
957                             array('action' => $a.'rss'),
958                             array('nickname' => Nickname::DISPLAY_FMT));
959             }
960
961             // people tags
962
963             $m->connect(':nickname/peopletags',
964                             array('action' => 'peopletagsbyuser',
965                                   'nickname' => Nickname::DISPLAY_FMT));
966
967             $m->connect(':nickname/peopletags/private',
968                             array('action' => 'peopletagsbyuser',
969                                   'nickname' => Nickname::DISPLAY_FMT,
970                                   'private' => 1));
971
972             $m->connect(':nickname/peopletags/public',
973                             array('action' => 'peopletagsbyuser',
974                                   'nickname' => Nickname::DISPLAY_FMT,
975                                   'public' => 1));
976
977             $m->connect(':nickname/othertags',
978                             array('action' => 'peopletagsforuser',
979                                   'nickname' => Nickname::DISPLAY_FMT));
980
981             $m->connect(':nickname/peopletagsubscriptions',
982                             array('action' => 'peopletagsubscriptions',
983                                   'nickname' => Nickname::DISPLAY_FMT));
984
985             $m->connect(':tagger/all/:tag/subscribers',
986                             array('action' => 'peopletagsubscribers',
987                                   'tagger' => Nickname::DISPLAY_FMT,
988                                   'tag' => self::REGEX_TAG));
989
990             $m->connect(':tagger/all/:tag/tagged',
991                             array('action' => 'peopletagged',
992                                   'tagger' => Nickname::DISPLAY_FMT,
993                                   'tag' => self::REGEX_TAG));
994
995             $m->connect(':tagger/all/:tag/edit',
996                             array('action' => 'editpeopletag',
997                                   'tagger' => Nickname::DISPLAY_FMT,
998                                   'tag' => self::REGEX_TAG));
999
1000             foreach(array('subscribe', 'unsubscribe') as $v) {
1001                 $m->connect('peopletag/:id/'.$v,
1002                                 array('action' => $v.'peopletag',
1003                                       'id' => '[0-9]{1,64}'));
1004             }
1005             $m->connect('user/:tagger_id/profiletag/:id/id',
1006                             array('action' => 'profiletagbyid',
1007                                   'tagger_id' => '[0-9]+',
1008                                   'id' => '[0-9]+'));
1009
1010             $m->connect(':nickname/all/:tag',
1011                             array('action' => 'showprofiletag'),
1012                             array('nickname' => Nickname::DISPLAY_FMT,
1013                                   'tag' => self::REGEX_TAG));
1014
1015             foreach (array('subscriptions', 'subscribers') as $a) {
1016                 $m->connect(':nickname/'.$a.'/:tag',
1017                             array('action' => $a),
1018                             array('tag' => self::REGEX_TAG,
1019                                   'nickname' => Nickname::DISPLAY_FMT));
1020             }
1021
1022             foreach (array('rss', 'groups') as $a) {
1023                 $m->connect(':nickname/'.$a,
1024                             array('action' => 'user'.$a),
1025                             array('nickname' => Nickname::DISPLAY_FMT));
1026             }
1027
1028             $m->connect(':nickname/avatar',
1029                         array('action' => 'avatarbynickname'),
1030                         array('nickname' => Nickname::DISPLAY_FMT));
1031             $m->connect(':nickname/avatar/:size',
1032                         array('action' => 'avatarbynickname'),
1033                         array('size' => '(|original|\d+)',
1034                               'nickname' => Nickname::DISPLAY_FMT));
1035
1036             $m->connect(':nickname/tag/:tag/rss',
1037                         array('action' => 'userrss'),
1038                         array('nickname' => Nickname::DISPLAY_FMT),
1039                         array('tag' => self::REGEX_TAG));
1040
1041             $m->connect(':nickname/tag/:tag',
1042                         array('action' => 'showstream'),
1043                         array('nickname' => Nickname::DISPLAY_FMT),
1044                         array('tag' => self::REGEX_TAG));
1045
1046             $m->connect(':nickname/rsd.xml',
1047                         array('action' => 'rsd'),
1048                         array('nickname' => Nickname::DISPLAY_FMT));
1049
1050             $m->connect(':nickname',
1051                         array('action' => 'showstream'),
1052                         array('nickname' => Nickname::DISPLAY_FMT));
1053
1054             $m->connect(':nickname/',
1055                         array('action' => 'showstream'),
1056                         array('nickname' => Nickname::DISPLAY_FMT));
1057
1058             // AtomPub API
1059
1060             $m->connect('api/statusnet/app/service/:id.xml',
1061                         array('action' => 'ApiAtomService'),
1062                         array('id' => Nickname::DISPLAY_FMT));
1063
1064             $m->connect('api/statusnet/app/service.xml',
1065                         array('action' => 'ApiAtomService'));
1066
1067             $m->connect('api/statusnet/app/subscriptions/:subscriber/:subscribed.atom',
1068                         array('action' => 'AtomPubShowSubscription'),
1069                         array('subscriber' => '[0-9]+',
1070                               'subscribed' => '[0-9]+'));
1071
1072             $m->connect('api/statusnet/app/subscriptions/:subscriber.atom',
1073                         array('action' => 'AtomPubSubscriptionFeed'),
1074                         array('subscriber' => '[0-9]+'));
1075
1076             $m->connect('api/statusnet/app/memberships/:profile/:group.atom',
1077                         array('action' => 'AtomPubShowMembership'),
1078                         array('profile' => '[0-9]+',
1079                               'group' => '[0-9]+'));
1080
1081             $m->connect('api/statusnet/app/memberships/:profile.atom',
1082                         array('action' => 'AtomPubMembershipFeed'),
1083                         array('profile' => '[0-9]+'));
1084
1085             // URL shortening
1086
1087             $m->connect('url/:id',
1088                         array('action' => 'redirecturl',
1089                               'id' => '[0-9]+'));
1090
1091             // user stuff
1092
1093             Event::handle('RouterInitialized', array($m));
1094         }
1095
1096         return $m;
1097     }
1098
1099     function map($path)
1100     {
1101         try {
1102             return $this->m->match($path);
1103         } catch (NoRouteMapException $e) {
1104             common_debug($e->getMessage());
1105             // TRANS: Client error on action trying to visit a non-existing page.
1106             throw new ClientException(_('Page not found.'), 404);
1107         }
1108     }
1109
1110     function build($action, $args=null, $params=null, $fragment=null)
1111     {
1112         $action_arg = array('action' => $action);
1113
1114         if ($args) {
1115             $args = array_merge($action_arg, $args);
1116         } else {
1117             $args = $action_arg;
1118         }
1119
1120         $url = $this->m->generate($args, $params, $fragment);
1121         // Due to a bug in the Net_URL_Mapper code, the returned URL may
1122         // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
1123         // repair that here rather than modifying the upstream code...
1124
1125         $qpos = strpos($url, '?');
1126         if ($qpos !== false) {
1127             $url = substr($url, 0, $qpos+1) .
1128                 str_replace('?', '&', substr($url, $qpos+1));
1129
1130             // @fixme this is a hacky workaround for http_build_query in the
1131             // lower-level code and bad configs that set the default separator
1132             // to &amp; instead of &. Encoded &s in parameters will not be
1133             // affected.
1134             $url = substr($url, 0, $qpos+1) .
1135                 str_replace('&amp;', '&', substr($url, $qpos+1));
1136
1137         }
1138
1139         return $url;
1140     }
1141 }