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