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