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