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