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