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