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