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