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