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