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