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