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