]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/router.php
Merge branch 'master' of git://gitorious.org/+socialites/statusnet/gnu-social
[quix0rs-gnu-social.git] / lib / router.php
1 <?php
2 /**
3  * StatusNet, the distributed open-source microblogging tool
4  *
5  * URL routing utilities
6  *
7  * PHP version 5
8  *
9  * LICENCE: This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU Affero General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Affero General Public License for more details.
18  *
19  * You should have received a copy of the GNU Affero General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  * @category  URL
23  * @package   StatusNet
24  * @author    Evan Prodromou <evan@status.net>
25  * @copyright 2009 StatusNet, Inc.
26  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
27  * @link      http://status.net/
28  */
29
30 if (!defined('STATUSNET') && !defined('LACONICA')) {
31     exit(1);
32 }
33
34 /**
35  * URL Router
36  *
37  * Cheap wrapper around Net_URL_Mapper
38  *
39  * @category URL
40  * @package  StatusNet
41  * @author   Evan Prodromou <evan@status.net>
42  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
43  * @link     http://status.net/
44  */
45 class Router
46 {
47     var $m = null;
48     static $inst = null;
49
50     const REGEX_TAG = '[^\/]+'; // [\pL\pN_\-\.]{1,64} better if we can do unicode regexes
51
52     static function get()
53     {
54         if (!Router::$inst) {
55             Router::$inst = new Router();
56         }
57         return Router::$inst;
58     }
59
60     /**
61      * Clear the global singleton instance for this class.
62      * Needed to ensure reset when switching site configurations.
63      */
64     static function clear()
65     {
66         Router::$inst = null;
67     }
68
69     function __construct()
70     {
71         if (empty($this->m)) {
72             $this->m = $this->initialize();
73         }
74     }
75
76     /**
77      * Create a unique hashkey for the router.
78      *
79      * The router's url map can change based on the version of the software
80      * you're running and the plugins that are enabled. To avoid having bad routes
81      * get stuck in the cache, the key includes a list of plugins and the software
82      * version.
83      * 
84     * There can still be problems with a) differences in versions of the plugins and
85      * b) people running code between official versions, but these tend to be more
86      * sophisticated users who can grok what's going on and clear their caches.
87      *
88      * @return string cache key string that should uniquely identify a router
89      */
90
91     static function cacheKey()
92     {
93         $parts = array('router');
94
95         // Many router paths depend on this setting.
96         if (common_config('singleuser', 'enabled')) {
97             $parts[] = '1user';
98         } else {
99             $parts[] = 'multi';
100         }
101
102         return Cache::codeKey(implode(':', $parts));
103     }
104
105     function initialize()
106     {
107         $m = new URLMapper();
108
109         if (Event::handle('StartInitializeRouter', array(&$m))) {
110
111             $m->connect('robots.txt', array('action' => 'robotstxt'));
112
113             $m->connect('opensearch/people', array('action' => 'opensearch',
114                                                    'type' => 'people'));
115             $m->connect('opensearch/notice', array('action' => 'opensearch',
116                                                    'type' => 'notice'));
117
118             // docs
119
120             $m->connect('doc/:title', array('action' => 'doc'));
121
122             $m->connect('main/otp/:user_id/:token',
123                         array('action' => 'otp'),
124                         array('user_id' => '[0-9]+',
125                               'token' => '.+'));
126
127             // these take a code; before the main part
128
129             foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) {
130                 $m->connect('main/'.$c.'/:code', array('action' => $c));
131             }
132
133             // Also need a block variant accepting ID on URL for mail links
134             $m->connect('main/block/:profileid',
135                         array('action' => 'block'),
136                         array('profileid' => '[0-9]+'));
137
138             $m->connect('main/sup/:seconds', array('action' => 'sup'),
139                         array('seconds' => '[0-9]+'));
140
141             // main stuff is repetitive
142
143             $main = array('login', 'logout', 'register', 'subscribe',
144                           'unsubscribe', 'cancelsubscription', 'approvesub',
145                           'confirmaddress', 'recoverpassword',
146                           'invite', 'favor', 'disfavor', 'sup',
147                           'block', 'unblock', 'subedit',
148                           'groupblock', 'groupunblock',
149                           'sandbox', 'unsandbox',
150                           'silence', 'unsilence',
151                           'grantrole', 'revokerole',
152                           'repeat',
153                           'deleteuser',
154                           'geocode',
155                           'version',
156                           'backupaccount',
157                           'deleteaccount',
158                           'restoreaccount',
159                           'top',
160             );
161
162             foreach ($main as $a) {
163                 $m->connect('main/'.$a, array('action' => $a));
164             }
165
166             $m->connect('main/tagprofile/:id', array('action' => 'tagprofile'),
167                                                array('id' => '[0-9]+'));
168
169             $m->connect('main/tagprofile', array('action' => 'tagprofile'));
170
171             $m->connect('main/oembed',
172                         array('action' => 'oembed'));
173
174             $m->connect('main/xrds',
175                         array('action' => 'publicxrds'));
176
177             // settings
178
179             foreach (array('profile', 'avatar', 'password', 'im', 'oauthconnections',
180                            'oauthapps', 'email', 'sms', 'url') as $s) {
181                 $m->connect('settings/'.$s, array('action' => $s.'settings'));
182             }
183
184             if (common_config('oldschool', 'enabled')) {
185                 $m->connect('settings/oldschool', array('action' => 'oldschoolsettings'));
186             }
187
188             $m->connect('settings/oauthapps/show/:id',
189                         array('action' => 'showapplication'),
190                         array('id' => '[0-9]+')
191             );
192             $m->connect('settings/oauthapps/new',
193                         array('action' => 'newapplication')
194             );
195             $m->connect('settings/oauthapps/edit/:id',
196                         array('action' => 'editapplication'),
197                         array('id' => '[0-9]+')
198             );
199             $m->connect('settings/oauthapps/delete/:id',
200                         array('action' => 'deleteapplication'),
201                         array('id' => '[0-9]+')
202             );
203
204             // search
205
206             foreach (array('group', 'people', 'notice') as $s) {
207                 $m->connect('search/'.$s.'?q=:q',
208                             array('action' => $s.'search'),
209                             array('q' => '.+'));
210                 $m->connect('search/'.$s, array('action' => $s.'search'));
211             }
212
213             // The second of these is needed to make the link work correctly
214             // when inserted into the page. The first is needed to match the
215             // route on the way in. Seems to be another Net_URL_Mapper bug to me.
216             $m->connect('search/notice/rss?q=:q', array('action' => 'noticesearchrss'),
217                         array('q' => '.+'));
218             $m->connect('search/notice/rss', array('action' => 'noticesearchrss'));
219
220             $m->connect('attachment/:attachment',
221                         array('action' => 'attachment'),
222                         array('attachment' => '[0-9]+'));
223
224             $m->connect('attachment/:attachment/ajax',
225                         array('action' => 'attachment_ajax'),
226                         array('attachment' => '[0-9]+'));
227
228             $m->connect('attachment/:attachment/thumbnail',
229                         array('action' => 'attachment_thumbnail'),
230                         array('attachment' => '[0-9]+'));
231
232             $m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
233                         array('action' => 'newnotice'),
234                         array('replyto' => Nickname::DISPLAY_FMT),
235                         array('inreplyto' => '[0-9]+'));
236
237             $m->connect('notice/new?replyto=:replyto',
238                         array('action' => 'newnotice'),
239                         array('replyto' => Nickname::DISPLAY_FMT));
240
241             $m->connect('notice/new', array('action' => 'newnotice'));
242
243             $m->connect('notice/:notice/file',
244                         array('action' => 'file'),
245                         array('notice' => '[0-9]+'));
246
247             $m->connect('notice/:notice',
248                         array('action' => 'shownotice'),
249                         array('notice' => '[0-9]+'));
250
251             $m->connect('notice/delete/:notice',
252                         array('action' => 'deletenotice'),
253                         array('notice' => '[0-9]+'));
254
255             $m->connect('notice/delete', array('action' => 'deletenotice'));
256
257             $m->connect('bookmarklet/new', array('action' => 'bookmarklet'));
258
259             // conversation
260
261             $m->connect('conversation/:id',
262                         array('action' => 'conversation'),
263                         array('id' => '[0-9]+'));
264             $m->connect('conversation/:id/replies',
265                         array('action' => 'conversationreplies'),
266                         array('id' => '[0-9]+'));
267
268             $m->connect('message/new', array('action' => 'newmessage'));
269             $m->connect('message/new?to=:to', array('action' => 'newmessage'), array('to' => Nickname::DISPLAY_FMT));
270             $m->connect('message/:message',
271                         array('action' => 'showmessage'),
272                         array('message' => '[0-9]+'));
273
274             $m->connect('user/:id',
275                         array('action' => 'userbyid'),
276                         array('id' => '[0-9]+'));
277
278             if (!common_config('performance', 'high')) {
279                 $m->connect('tags/', array('action' => 'publictagcloud'));
280                 $m->connect('tag/', array('action' => 'publictagcloud'));
281                 $m->connect('tags', array('action' => 'publictagcloud'));
282                 $m->connect('tag', array('action' => 'publictagcloud'));
283             }
284             $m->connect('tag/:tag/rss',
285                         array('action' => 'tagrss'),
286                         array('tag' => self::REGEX_TAG));
287             $m->connect('tag/:tag',
288                         array('action' => 'tag'),
289                         array('tag' => self::REGEX_TAG));
290
291             // groups
292
293             $m->connect('group/new', array('action' => 'newgroup'));
294
295             foreach (array('edit', 'join', 'leave', 'delete', 'cancel', 'approve') as $v) {
296                 $m->connect('group/:nickname/'.$v,
297                             array('action' => $v.'group'),
298                             array('nickname' => Nickname::DISPLAY_FMT));
299                 $m->connect('group/:id/id/'.$v,
300                             array('action' => $v.'group'),
301                             array('id' => '[0-9]+'));
302             }
303
304             foreach (array('members', 'logo', 'rss') as $n) {
305                 $m->connect('group/:nickname/'.$n,
306                             array('action' => 'group'.$n),
307                             array('nickname' => Nickname::DISPLAY_FMT));
308             }
309
310             $m->connect('group/:nickname/foaf',
311                         array('action' => 'foafgroup'),
312                         array('nickname' => Nickname::DISPLAY_FMT));
313
314             $m->connect('group/:nickname/blocked',
315                         array('action' => 'blockedfromgroup'),
316                         array('nickname' => Nickname::DISPLAY_FMT));
317
318             $m->connect('group/:nickname/makeadmin',
319                         array('action' => 'makeadmin'),
320                         array('nickname' => Nickname::DISPLAY_FMT));
321
322             $m->connect('group/:nickname/members/pending',
323                         array('action' => 'groupqueue'),
324                         array('nickname' => Nickname::DISPLAY_FMT));
325
326             $m->connect('group/:id/id',
327                         array('action' => 'groupbyid'),
328                         array('id' => '[0-9]+'));
329
330             $m->connect('group/:nickname',
331                         array('action' => 'showgroup'),
332                         array('nickname' => Nickname::DISPLAY_FMT));
333
334             $m->connect('group/:nickname/',
335                         array('action' => 'showgroup'),
336                         array('nickname' => Nickname::DISPLAY_FMT));
337
338             $m->connect('group/', array('action' => 'groups'));
339             $m->connect('group', array('action' => 'groups'));
340             $m->connect('groups/', array('action' => 'groups'));
341             $m->connect('groups', array('action' => 'groups'));
342
343             // Twitter-compatible API
344
345             // statuses API
346
347             $m->connect('api',
348                         array('action' => 'Redirect',
349                               'nextAction' => 'doc',
350                               'args' => array('title' => 'api')));
351
352             $m->connect('api/statuses/public_timeline.:format',
353                         array('action' => 'ApiTimelinePublic',
354                               'format' => '(xml|json|rss|atom|as)'));
355
356             $m->connect('api/statuses/friends_timeline/:id.:format',
357                         array('action' => 'ApiTimelineFriends',
358                               'id' => Nickname::INPUT_FMT,
359                               'format' => '(xml|json|rss|atom|as)'));
360
361             $m->connect('api/statuses/friends_timeline.:format',
362                         array('action' => 'ApiTimelineFriends',
363                               'format' => '(xml|json|rss|atom|as)'));
364
365             $m->connect('api/statuses/home_timeline/:id.:format',
366                         array('action' => 'ApiTimelineHome',
367                               'id' => Nickname::INPUT_FMT,
368                               'format' => '(xml|json|rss|atom|as)'));
369
370             $m->connect('api/statuses/home_timeline.:format',
371                         array('action' => 'ApiTimelineHome',
372                               'format' => '(xml|json|rss|atom|as)'));
373
374             $m->connect('api/statuses/user_timeline/:id.:format',
375                         array('action' => 'ApiTimelineUser',
376                               'id' => Nickname::INPUT_FMT,
377                               'format' => '(xml|json|rss|atom|as)'));
378
379             $m->connect('api/statuses/user_timeline.:format',
380                         array('action' => 'ApiTimelineUser',
381                               'format' => '(xml|json|rss|atom|as)'));
382
383             $m->connect('api/statuses/mentions/:id.:format',
384                         array('action' => 'ApiTimelineMentions',
385                               'id' => Nickname::INPUT_FMT,
386                               'format' => '(xml|json|rss|atom|as)'));
387
388             $m->connect('api/statuses/mentions.:format',
389                         array('action' => 'ApiTimelineMentions',
390                               'format' => '(xml|json|rss|atom|as)'));
391
392             $m->connect('api/statuses/replies/:id.:format',
393                         array('action' => 'ApiTimelineMentions',
394                               'id' => Nickname::INPUT_FMT,
395                               'format' => '(xml|json|rss|atom|as)'));
396
397             $m->connect('api/statuses/replies.:format',
398                         array('action' => 'ApiTimelineMentions',
399                               'format' => '(xml|json|rss|atom|as)'));
400
401             $m->connect('api/statuses/retweeted_by_me.:format',
402                         array('action' => 'ApiTimelineRetweetedByMe',
403                               'format' => '(xml|json|atom|as)'));
404
405             $m->connect('api/statuses/retweeted_to_me.:format',
406                         array('action' => 'ApiTimelineRetweetedToMe',
407                               'format' => '(xml|json|atom|as)'));
408
409             $m->connect('api/statuses/retweets_of_me.:format',
410                         array('action' => 'ApiTimelineRetweetsOfMe',
411                               'format' => '(xml|json|atom|as)'));
412
413             $m->connect('api/statuses/friends/:id.:format',
414                         array('action' => 'ApiUserFriends',
415                               'id' => Nickname::INPUT_FMT,
416                               'format' => '(xml|json)'));
417
418             $m->connect('api/statuses/friends.:format',
419                         array('action' => 'ApiUserFriends',
420                               'format' => '(xml|json)'));
421
422             $m->connect('api/statuses/followers/:id.:format',
423                         array('action' => 'ApiUserFollowers',
424                               'id' => Nickname::INPUT_FMT,
425                               'format' => '(xml|json)'));
426
427             $m->connect('api/statuses/followers.:format',
428                         array('action' => 'ApiUserFollowers',
429                               'format' => '(xml|json)'));
430
431             $m->connect('api/statuses/show/:id.:format',
432                         array('action' => 'ApiStatusesShow',
433                               'id' => '[0-9]+',
434                               'format' => '(xml|json|atom)'));
435
436             $m->connect('api/statuses/show.:format',
437                         array('action' => 'ApiStatusesShow',
438                               'format' => '(xml|json|atom)'));
439
440             $m->connect('api/statuses/update.:format',
441                         array('action' => 'ApiStatusesUpdate',
442                               'format' => '(xml|json)'));
443
444             $m->connect('api/statuses/destroy/:id.:format',
445                         array('action' => 'ApiStatusesDestroy',
446                               'id' => '[0-9]+',
447                               'format' => '(xml|json)'));
448
449             $m->connect('api/statuses/destroy.:format',
450                         array('action' => 'ApiStatusesDestroy',
451                               'format' => '(xml|json)'));
452
453             $m->connect('api/statuses/retweet/:id.:format',
454                         array('action' => 'ApiStatusesRetweet',
455                               'id' => '[0-9]+',
456                               'format' => '(xml|json)'));
457
458             $m->connect('api/statuses/retweets/:id.:format',
459                         array('action' => 'ApiStatusesRetweets',
460                               'id' => '[0-9]+',
461                               'format' => '(xml|json)'));
462
463             // START qvitter API additions
464
465             $m->connect('api/statuses/favs/:id.:format',
466                         array('action' => 'ApiStatusesFavs',
467                               'id' => '[0-9]+',
468                               'format' => '(xml|json)'));
469             
470             $m->connect('api/attachment/:id.:format',
471                         array('action' => 'ApiAttachment',
472                               'id' => '[0-9]+',
473                               'format' => '(xml|json)'));
474             
475             $m->connect('api/checkhub.:format',
476                         array('action' => 'ApiCheckHub',
477                               'format' => '(xml|json)'));
478             
479             $m->connect('api/externalprofile/show.:format',
480                         array('action' => 'ApiExternalProfileShow',
481                               'format' => '(xml|json)'));
482
483             $m->connect('api/statusnet/groups/admins/:id.:format',
484                         array('action' => 'ApiGroupAdmins',
485                               'id' => Nickname::INPUT_FMT,
486                               'format' => '(xml|json)'));
487             
488             $m->connect('api/account/update_link_color.:format',
489                         array('action' => 'ApiAccountUpdateLinkColor',
490                               'format' => '(xml|json)'));
491                 
492             $m->connect('api/account/update_background_color.:format',
493                         array('action' => 'ApiAccountUpdateBackgroundColor',
494                               'format' => '(xml|json)'));
495
496             $m->connect('api/account/register.:format',
497                         array('action' => 'ApiAccountRegister',
498                               'format' => '(xml|json)'));
499             
500             $m->connect('api/check_nickname.:format',
501                         array('action' => 'ApiCheckNickname',
502                               'format' => '(xml|json)'));
503
504             // END qvitter API additions
505
506             // users
507
508             $m->connect('api/users/show/:id.:format',
509                         array('action' => 'ApiUserShow',
510                               'id' => Nickname::INPUT_FMT,
511                               'format' => '(xml|json)'));
512
513             $m->connect('api/users/show.:format',
514                         array('action' => 'ApiUserShow',
515                               'format' => '(xml|json)'));
516
517             $m->connect('api/users/profile_image/:screen_name.:format',
518                         array('action' => 'ApiUserProfileImage',
519                               'screen_name' => Nickname::DISPLAY_FMT,
520                               'format' => '(xml|json)'));
521
522             // direct messages
523
524             $m->connect('api/direct_messages.:format',
525                         array('action' => 'ApiDirectMessage',
526                               'format' => '(xml|json|rss|atom)'));
527
528             $m->connect('api/direct_messages/sent.:format',
529                         array('action' => 'ApiDirectMessage',
530                               'format' => '(xml|json|rss|atom)',
531                               'sent' => true));
532
533             $m->connect('api/direct_messages/new.:format',
534                         array('action' => 'ApiDirectMessageNew',
535                               'format' => '(xml|json)'));
536
537             // friendships
538
539             $m->connect('api/friendships/show.:format',
540                         array('action' => 'ApiFriendshipsShow',
541                               'format' => '(xml|json)'));
542
543             $m->connect('api/friendships/exists.:format',
544                         array('action' => 'ApiFriendshipsExists',
545                               'format' => '(xml|json)'));
546
547             $m->connect('api/friendships/create/:id.:format',
548                         array('action' => 'ApiFriendshipsCreate',
549                               'id' => Nickname::INPUT_FMT,
550                               'format' => '(xml|json)'));
551
552             $m->connect('api/friendships/create.:format',
553                         array('action' => 'ApiFriendshipsCreate',
554                               'format' => '(xml|json)'));
555
556             $m->connect('api/friendships/destroy/:id.:format',
557                         array('action' => 'ApiFriendshipsDestroy',
558                               'id' => Nickname::INPUT_FMT,
559                               'format' => '(xml|json)'));
560
561             $m->connect('api/friendships/destroy.:format',
562                         array('action' => 'ApiFriendshipsDestroy',
563                               'format' => '(xml|json)'));
564
565             // Social graph
566
567             $m->connect('api/friends/ids/:id.:format',
568                         array('action' => 'ApiUserFriends',
569                               'ids_only' => true));
570
571             $m->connect('api/followers/ids/:id.:format',
572                         array('action' => 'ApiUserFollowers',
573                               'ids_only' => true));
574
575             $m->connect('api/friends/ids.:format',
576                         array('action' => 'ApiUserFriends',
577                               'ids_only' => true));
578
579             $m->connect('api/followers/ids.:format',
580                         array('action' => 'ApiUserFollowers',
581                               'ids_only' => true));
582
583             // account
584
585             $m->connect('api/account/verify_credentials.:format',
586                         array('action' => 'ApiAccountVerifyCredentials'));
587
588             $m->connect('api/account/update_profile.:format',
589                         array('action' => 'ApiAccountUpdateProfile'));
590
591             $m->connect('api/account/update_profile_image.:format',
592                         array('action' => 'ApiAccountUpdateProfileImage'));
593
594             $m->connect('api/account/update_delivery_device.:format',
595                         array('action' => 'ApiAccountUpdateDeliveryDevice'));
596
597             // special case where verify_credentials is called w/out a format
598
599             $m->connect('api/account/verify_credentials',
600                         array('action' => 'ApiAccountVerifyCredentials'));
601
602             $m->connect('api/account/rate_limit_status.:format',
603                         array('action' => 'ApiAccountRateLimitStatus'));
604
605             // favorites
606
607             $m->connect('api/favorites/:id.:format',
608                         array('action' => 'ApiTimelineFavorites',
609                               'id' => Nickname::INPUT_FMT,
610                               'format' => '(xml|json|rss|atom|as)'));
611
612             $m->connect('api/favorites.:format',
613                         array('action' => 'ApiTimelineFavorites',
614                               'format' => '(xml|json|rss|atom|as)'));
615
616             $m->connect('api/favorites/create/:id.:format',
617                         array('action' => 'ApiFavoriteCreate',
618                               'id' => '[0-9]+',
619                               'format' => '(xml|json)'));
620
621             $m->connect('api/favorites/destroy/:id.:format',
622                         array('action' => 'ApiFavoriteDestroy',
623                               'id' => '[0-9]+',
624                               'format' => '(xml|json)'));
625             // blocks
626
627             $m->connect('api/blocks/create/:id.:format',
628                         array('action' => 'ApiBlockCreate',
629                               'id' => Nickname::INPUT_FMT,
630                               'format' => '(xml|json)'));
631
632             $m->connect('api/blocks/create.:format',
633                         array('action' => 'ApiBlockCreate',
634                               'format' => '(xml|json)'));
635
636             $m->connect('api/blocks/destroy/:id.:format',
637                         array('action' => 'ApiBlockDestroy',
638                               'id' => Nickname::INPUT_FMT,
639                               'format' => '(xml|json)'));
640
641             $m->connect('api/blocks/destroy.:format',
642                         array('action' => 'ApiBlockDestroy',
643                               'format' => '(xml|json)'));
644
645             // help
646
647             $m->connect('api/help/test.:format',
648                         array('action' => 'ApiHelpTest',
649                               'format' => '(xml|json)'));
650
651             // statusnet
652
653             $m->connect('api/statusnet/version.:format',
654                         array('action' => 'ApiGNUsocialVersion',
655                               'format' => '(xml|json)'));
656
657             $m->connect('api/statusnet/config.:format',
658                         array('action' => 'ApiGNUsocialConfig',
659                               'format' => '(xml|json)'));
660
661             // For our current software name, we provide "gnusocial" base action
662
663             $m->connect('api/gnusocial/version.:format',
664                         array('action' => 'ApiGNUsocialVersion',
665                               'format' => '(xml|json)'));
666
667             $m->connect('api/gnusocial/config.:format',
668                         array('action' => 'ApiGNUsocialConfig',
669                               'format' => '(xml|json)'));
670
671             // Groups and tags are newer than 0.8.1 so no backward-compatibility
672             // necessary
673
674             // Groups
675             //'list' has to be handled differently, as php will not allow a method to be named 'list'
676
677             $m->connect('api/statusnet/groups/timeline/:id.:format',
678                         array('action' => 'ApiTimelineGroup',
679                               'id' => Nickname::INPUT_FMT,
680                               'format' => '(xml|json|rss|atom|as)'));
681
682             $m->connect('api/statusnet/groups/show/:id.:format',
683                         array('action' => 'ApiGroupShow',
684                               'id' => Nickname::INPUT_FMT,
685                               'format' => '(xml|json)'));
686
687             $m->connect('api/statusnet/groups/show.:format',
688                         array('action' => 'ApiGroupShow',
689                               'format' => '(xml|json)'));
690
691             $m->connect('api/statusnet/groups/join/:id.:format',
692                         array('action' => 'ApiGroupJoin',
693                               'format' => '(xml|json)'));
694
695             $m->connect('api/statusnet/groups/join.:format',
696                         array('action' => 'ApiGroupJoin',
697                               'id' => Nickname::INPUT_FMT,
698                               'format' => '(xml|json)'));
699
700             $m->connect('api/statusnet/groups/leave/:id.:format',
701                         array('action' => 'ApiGroupLeave',
702                               'format' => '(xml|json)'));
703
704             $m->connect('api/statusnet/groups/leave.:format',
705                         array('action' => 'ApiGroupLeave',
706                               'id' => Nickname::INPUT_FMT,
707                               'format' => '(xml|json)'));
708
709             $m->connect('api/statusnet/groups/is_member.:format',
710                         array('action' => 'ApiGroupIsMember',
711                               'format' => '(xml|json)'));
712
713             $m->connect('api/statusnet/groups/list/:id.:format',
714                         array('action' => 'ApiGroupList',
715                               'id' => Nickname::INPUT_FMT,
716                               'format' => '(xml|json|rss|atom)'));
717
718             $m->connect('api/statusnet/groups/list.:format',
719                         array('action' => 'ApiGroupList',
720                               'format' => '(xml|json|rss|atom)'));
721
722             $m->connect('api/statusnet/groups/list_all.:format',
723                         array('action' => 'ApiGroupListAll',
724                               'format' => '(xml|json|rss|atom)'));
725
726             $m->connect('api/statusnet/groups/membership/:id.:format',
727                         array('action' => 'ApiGroupMembership',
728                               'id' => Nickname::INPUT_FMT,
729                               'format' => '(xml|json)'));
730
731             $m->connect('api/statusnet/groups/membership.:format',
732                         array('action' => 'ApiGroupMembership',
733                               'format' => '(xml|json)'));
734
735             $m->connect('api/statusnet/groups/create.:format',
736                         array('action' => 'ApiGroupCreate',
737                               'format' => '(xml|json)'));
738
739             $m->connect('api/statusnet/groups/update/:id.:format',
740                         array('action' => 'ApiGroupProfileUpdate',
741                               'id' => '[a-zA-Z0-9]+',
742                               'format' => '(xml|json)'));
743                               
744             $m->connect('api/statusnet/conversation/:id.:format',
745                         array('action' => 'apiconversation',
746                               'id' => '[0-9]+',
747                               'format' => '(xml|json|rss|atom|as)'));
748
749             // Lists (people tags)
750
751             $m->connect('api/lists/memberships.:format',
752                         array('action' => 'ApiListMemberships',
753                               'format' => '(xml|json)'));
754
755             $m->connect('api/:user/lists/memberships.:format',
756                         array('action' => 'ApiListMemberships',
757                               'user' => '[a-zA-Z0-9]+',
758                               'format' => '(xml|json)'));
759
760             $m->connect('api/lists/subscriptions.:format',
761                         array('action' => 'ApiListSubscriptions',
762                               'format' => '(xml|json)'));
763
764             $m->connect('api/:user/lists/subscriptions.:format',
765                         array('action' => 'ApiListSubscriptions',
766                               'user' => '[a-zA-Z0-9]+',
767                               'format' => '(xml|json)'));
768
769             $m->connect('api/lists.:format',
770                         array('action' => 'ApiLists',
771                               'format' => '(xml|json)'));
772
773             $m->connect('api/:user/lists/:id.:format',
774                         array('action' => 'ApiList',
775                               'user' => '[a-zA-Z0-9]+',
776                               'id' => '[a-zA-Z0-9]+',
777                               'format' => '(xml|json)'));
778
779             $m->connect('api/:user/lists.:format',
780                         array('action' => 'ApiLists',
781                               'user' => '[a-zA-Z0-9]+',
782                               'format' => '(xml|json)'));
783
784             $m->connect('api/:user/lists/:id/statuses.:format',
785                         array('action' => 'ApiTimelineList',
786                               'user' => '[a-zA-Z0-9]+',
787                               'id' => '[a-zA-Z0-9]+',
788                               'format' => '(xml|json|rss|atom)'));
789
790             $m->connect('api/:user/:list_id/members/:id.:format',
791                         array('action' => 'ApiListMember',
792                               'user' => '[a-zA-Z0-9]+',
793                               'list_id' => '[a-zA-Z0-9]+',
794                               'id' => '[a-zA-Z0-9]+',
795                               'format' => '(xml|json)'));
796
797             $m->connect('api/:user/:list_id/members.:format',
798                         array('action' => 'ApiListMembers',
799                               'user' => '[a-zA-Z0-9]+',
800                               'list_id' => '[a-zA-Z0-9]+',
801                               'format' => '(xml|json)'));
802
803             $m->connect('api/:user/:list_id/subscribers/:id.:format',
804                         array('action' => 'ApiListSubscriber',
805                               'user' => '[a-zA-Z0-9]+',
806                               'list_id' => '[a-zA-Z0-9]+',
807                               'id' => '[a-zA-Z0-9]+',
808                               'format' => '(xml|json)'));
809
810             $m->connect('api/:user/:list_id/subscribers.:format',
811                         array('action' => 'ApiListSubscribers',
812                               'user' => '[a-zA-Z0-9]+',
813                               'list_id' => '[a-zA-Z0-9]+',
814                               'format' => '(xml|json)'));
815
816             // Tags
817             $m->connect('api/statusnet/tags/timeline/:tag.:format',
818                         array('action' => 'ApiTimelineTag',
819                               'tag'    => self::REGEX_TAG,
820                               'format' => '(xml|json|rss|atom|as)'));
821
822             // media related
823             $m->connect(
824                 'api/statusnet/media/upload',
825                 array('action' => 'ApiMediaUpload')
826             );
827
828             // search
829             $m->connect('api/search.atom', array('action' => 'ApiSearchAtom'));
830             $m->connect('api/search.json', array('action' => 'ApiSearchJSON'));
831             $m->connect('api/trends.json', array('action' => 'ApiTrends'));
832
833             $m->connect('api/oauth/request_token',
834                         array('action' => 'ApiOAuthRequestToken'));
835
836             $m->connect('api/oauth/access_token',
837                         array('action' => 'ApiOAuthAccessToken'));
838
839             $m->connect('api/oauth/authorize',
840                         array('action' => 'ApiOAuthAuthorize'));
841
842             // Admin
843
844             $m->connect('panel/site', array('action' => 'siteadminpanel'));
845             $m->connect('panel/user', array('action' => 'useradminpanel'));
846                 $m->connect('panel/access', array('action' => 'accessadminpanel'));
847             $m->connect('panel/paths', array('action' => 'pathsadminpanel'));
848             $m->connect('panel/sessions', array('action' => 'sessionsadminpanel'));
849             $m->connect('panel/sitenotice', array('action' => 'sitenoticeadminpanel'));
850             $m->connect('panel/license', array('action' => 'licenseadminpanel'));
851
852             $m->connect('panel/plugins', array('action' => 'pluginsadminpanel'));
853             $m->connect('panel/plugins/enable/:plugin',
854                         array('action' => 'pluginenable'),
855                         array('plugin' => '[A-Za-z0-9_]+'));
856             $m->connect('panel/plugins/disable/:plugin',
857                         array('action' => 'plugindisable'),
858                         array('plugin' => '[A-Za-z0-9_]+'));
859
860             $m->connect('getfile/:filename',
861                         array('action' => 'getfile'),
862                         array('filename' => '[A-Za-z0-9._-]+'));
863
864             // Common people-tag stuff
865
866             $m->connect('peopletag/:tag', array('action' => 'peopletag',
867                                                 'tag'    => self::REGEX_TAG));
868
869             $m->connect('selftag/:tag', array('action' => 'selftag',
870                                               'tag'    => self::REGEX_TAG));
871
872             $m->connect('main/addpeopletag', array('action' => 'addpeopletag'));
873
874             $m->connect('main/removepeopletag', array('action' => 'removepeopletag'));
875
876             $m->connect('main/profilecompletion', array('action' => 'profilecompletion'));
877
878             $m->connect('main/peopletagautocomplete', array('action' => 'peopletagautocomplete'));
879
880             // In the "root"
881
882             if (common_config('singleuser', 'enabled')) {
883
884                 $nickname = User::singleUserNickname();
885
886                 foreach (array('subscriptions', 'subscribers',
887                                'all', 'foaf', 'replies',
888                                'microsummary') as $a) {
889                     $m->connect($a,
890                                 array('action' => $a,
891                                       'nickname' => $nickname));
892                 }
893
894                 foreach (array('subscriptions', 'subscribers') as $a) {
895                     $m->connect($a.'/:tag',
896                                 array('action' => $a,
897                                       'nickname' => $nickname),
898                                 array('tag' => self::REGEX_TAG));
899                 }
900
901                 $m->connect('subscribers/pending',
902                             array('action' => 'subqueue',
903                                   'nickname' => $nickname));
904
905                 foreach (array('rss', 'groups') as $a) {
906                     $m->connect($a,
907                                 array('action' => 'user'.$a,
908                                       'nickname' => $nickname));
909                 }
910
911                 foreach (array('all', 'replies', 'favorites') as $a) {
912                     $m->connect($a.'/rss',
913                                 array('action' => $a.'rss',
914                                       'nickname' => $nickname));
915                 }
916
917                 $m->connect('favorites',
918                             array('action' => 'showfavorites',
919                                   'nickname' => $nickname));
920
921                 $m->connect('avatar',
922                             array('action' => 'avatarbynickname',
923                                   'nickname' => $nickname));
924                 $m->connect('avatar/:size',
925                             array('action' => 'avatarbynickname',
926                                   'nickname' => $nickname),
927                             array('size' => '(|original|\d+)'));
928
929                 $m->connect('tag/:tag/rss',
930                             array('action' => 'userrss',
931                                   'nickname' => $nickname),
932                             array('tag' => self::REGEX_TAG));
933
934                 $m->connect('tag/:tag',
935                             array('action' => 'showstream',
936                                   'nickname' => $nickname),
937                             array('tag' => self::REGEX_TAG));
938
939                 $m->connect('rsd.xml',
940                             array('action' => 'rsd',
941                                   'nickname' => $nickname));
942
943                 $m->connect('',
944                             array('action' => 'showstream',
945                                   'nickname' => $nickname));
946
947                 // peopletags
948
949                 $m->connect('peopletags',
950                             array('action' => 'peopletagsbyuser'));
951
952                 $m->connect('peopletags/private',
953                             array('action' => 'peopletagsbyuser',
954                                   'private' => 1));
955
956                 $m->connect('peopletags/public',
957                             array('action' => 'peopletagsbyuser',
958                                   'public' => 1));
959
960                 $m->connect('othertags',
961                             array('action' => 'peopletagsforuser'));
962
963                 $m->connect('peopletagsubscriptions',
964                             array('action' => 'peopletagsubscriptions'));
965
966                 $m->connect('all/:tag/subscribers',
967                             array('action' => 'peopletagsubscribers',
968                                   'tag' => self::REGEX_TAG));
969
970                 $m->connect('all/:tag/tagged',
971                                 array('action' => 'peopletagged',
972                                       'tag' => self::REGEX_TAG));
973
974                 $m->connect('all/:tag/edit',
975                                 array('action' => 'editpeopletag',
976                                       'tag' => self::REGEX_TAG));
977
978                 foreach(array('subscribe', 'unsubscribe') as $v) {
979                     $m->connect('peopletag/:id/'.$v,
980                                     array('action' => $v.'peopletag',
981                                           'id' => '[0-9]{1,64}'));
982                 }
983                 $m->connect('user/:tagger_id/profiletag/:id/id',
984                                 array('action' => 'profiletagbyid',
985                                       'tagger_id' => '[0-9]+',
986                                       'id' => '[0-9]+'));
987
988                 $m->connect('all/:tag',
989                                 array('action' => 'showprofiletag',
990                                       'tag' => self::REGEX_TAG));
991
992                 foreach (array('subscriptions', 'subscribers') as $a) {
993                     $m->connect($a.'/:tag',
994                                 array('action' => $a),
995                                 array('tag' => self::REGEX_TAG));
996                 }
997             } else {
998                 $m->connect('', array('action' => 'public'));
999                 $m->connect('rss', array('action' => 'publicrss'));
1000                 $m->connect('featuredrss', array('action' => 'featuredrss'));
1001                 $m->connect('favoritedrss', array('action' => 'favoritedrss'));
1002                 $m->connect('featured/', array('action' => 'featured'));
1003                 $m->connect('featured', array('action' => 'featured'));
1004                 $m->connect('favorited/', array('action' => 'favorited'));
1005                 $m->connect('favorited', array('action' => 'favorited'));
1006                 $m->connect('rsd.xml', array('action' => 'rsd'));
1007
1008                 foreach (array('subscriptions', 'subscribers',
1009                                'nudge', 'all', 'foaf', 'replies',
1010                                'inbox', 'outbox', 'microsummary') as $a) {
1011                     $m->connect(':nickname/'.$a,
1012                                 array('action' => $a),
1013                                 array('nickname' => Nickname::DISPLAY_FMT));
1014                 }
1015                 $m->connect(':nickname/subscribers/pending',
1016                             array('action' => 'subqueue'),
1017                             array('nickname' => Nickname::DISPLAY_FMT));
1018
1019                 // people tags
1020
1021                 $m->connect(':nickname/peopletags',
1022                                 array('action' => 'peopletagsbyuser',
1023                                       'nickname' => Nickname::DISPLAY_FMT));
1024
1025                 $m->connect(':nickname/peopletags/private',
1026                                 array('action' => 'peopletagsbyuser',
1027                                       'nickname' => Nickname::DISPLAY_FMT,
1028                                       'private' => 1));
1029
1030                 $m->connect(':nickname/peopletags/public',
1031                                 array('action' => 'peopletagsbyuser',
1032                                       'nickname' => Nickname::DISPLAY_FMT,
1033                                       'public' => 1));
1034
1035                 $m->connect(':nickname/othertags',
1036                                 array('action' => 'peopletagsforuser',
1037                                       'nickname' => Nickname::DISPLAY_FMT));
1038
1039                 $m->connect(':nickname/peopletagsubscriptions',
1040                                 array('action' => 'peopletagsubscriptions',
1041                                       'nickname' => Nickname::DISPLAY_FMT));
1042
1043                 $m->connect(':tagger/all/:tag/subscribers',
1044                                 array('action' => 'peopletagsubscribers',
1045                                       'tagger' => Nickname::DISPLAY_FMT,
1046                                       'tag' => self::REGEX_TAG));
1047
1048                 $m->connect(':tagger/all/:tag/tagged',
1049                                 array('action' => 'peopletagged',
1050                                       'tagger' => Nickname::DISPLAY_FMT,
1051                                       'tag' => self::REGEX_TAG));
1052
1053                 $m->connect(':tagger/all/:tag/edit',
1054                                 array('action' => 'editpeopletag',
1055                                       'tagger' => Nickname::DISPLAY_FMT,
1056                                       'tag' => self::REGEX_TAG));
1057
1058                 foreach(array('subscribe', 'unsubscribe') as $v) {
1059                     $m->connect('peopletag/:id/'.$v,
1060                                     array('action' => $v.'peopletag',
1061                                           'id' => '[0-9]{1,64}'));
1062                 }
1063                 $m->connect('user/:tagger_id/profiletag/:id/id',
1064                                 array('action' => 'profiletagbyid',
1065                                       'tagger_id' => '[0-9]+',
1066                                       'id' => '[0-9]+'));
1067
1068                 $m->connect(':tagger/all/:tag',
1069                                 array('action' => 'showprofiletag',
1070                                       'tagger' => Nickname::DISPLAY_FMT,
1071                                       'tag' => self::REGEX_TAG));
1072
1073                 foreach (array('subscriptions', 'subscribers') as $a) {
1074                     $m->connect(':nickname/'.$a.'/:tag',
1075                                 array('action' => $a),
1076                                 array('tag' => self::REGEX_TAG,
1077                                       'nickname' => Nickname::DISPLAY_FMT));
1078                 }
1079
1080                 foreach (array('rss', 'groups') as $a) {
1081                     $m->connect(':nickname/'.$a,
1082                                 array('action' => 'user'.$a),
1083                                 array('nickname' => Nickname::DISPLAY_FMT));
1084                 }
1085
1086                 foreach (array('all', 'replies', 'favorites') as $a) {
1087                     $m->connect(':nickname/'.$a.'/rss',
1088                                 array('action' => $a.'rss'),
1089                                 array('nickname' => Nickname::DISPLAY_FMT));
1090                 }
1091
1092                 $m->connect(':nickname/favorites',
1093                             array('action' => 'showfavorites'),
1094                             array('nickname' => Nickname::DISPLAY_FMT));
1095
1096                 $m->connect(':nickname/avatar',
1097                             array('action' => 'avatarbynickname'),
1098                             array('nickname' => Nickname::DISPLAY_FMT));
1099                 $m->connect(':nickname/avatar/:size',
1100                             array('action' => 'avatarbynickname'),
1101                             array('size' => '(|original|\d+)',
1102                                   'nickname' => Nickname::DISPLAY_FMT));
1103
1104                 $m->connect(':nickname/tag/:tag/rss',
1105                             array('action' => 'userrss'),
1106                             array('nickname' => Nickname::DISPLAY_FMT),
1107                             array('tag' => self::REGEX_TAG));
1108
1109                 $m->connect(':nickname/tag/:tag',
1110                             array('action' => 'showstream'),
1111                             array('nickname' => Nickname::DISPLAY_FMT),
1112                             array('tag' => self::REGEX_TAG));
1113
1114                 $m->connect(':nickname/rsd.xml',
1115                             array('action' => 'rsd'),
1116                             array('nickname' => Nickname::DISPLAY_FMT));
1117
1118                 $m->connect(':nickname',
1119                             array('action' => 'showstream'),
1120                             array('nickname' => Nickname::DISPLAY_FMT));
1121
1122                 $m->connect(':nickname/',
1123                             array('action' => 'showstream'),
1124                             array('nickname' => Nickname::DISPLAY_FMT));
1125             }
1126
1127             // AtomPub API
1128
1129             $m->connect('api/statusnet/app/service/:id.xml',
1130                         array('action' => 'ApiAtomService'),
1131                         array('id' => Nickname::DISPLAY_FMT));
1132
1133             $m->connect('api/statusnet/app/service.xml',
1134                         array('action' => 'ApiAtomService'));
1135
1136             $m->connect('api/statusnet/app/subscriptions/:subscriber/:subscribed.atom',
1137                         array('action' => 'AtomPubShowSubscription'),
1138                         array('subscriber' => '[0-9]+',
1139                               'subscribed' => '[0-9]+'));
1140
1141             $m->connect('api/statusnet/app/subscriptions/:subscriber.atom',
1142                         array('action' => 'AtomPubSubscriptionFeed'),
1143                         array('subscriber' => '[0-9]+'));
1144
1145             $m->connect('api/statusnet/app/favorites/:profile/:notice.atom',
1146                         array('action' => 'AtomPubShowFavorite'),
1147                         array('profile' => '[0-9]+',
1148                               'notice' => '[0-9]+'));
1149
1150             $m->connect('api/statusnet/app/favorites/:profile.atom',
1151                         array('action' => 'AtomPubFavoriteFeed'),
1152                         array('profile' => '[0-9]+'));
1153
1154             $m->connect('api/statusnet/app/memberships/:profile/:group.atom',
1155                         array('action' => 'AtomPubShowMembership'),
1156                         array('profile' => '[0-9]+',
1157                               'group' => '[0-9]+'));
1158
1159             $m->connect('api/statusnet/app/memberships/:profile.atom',
1160                         array('action' => 'AtomPubMembershipFeed'),
1161                         array('profile' => '[0-9]+'));
1162
1163             // URL shortening
1164
1165             $m->connect('url/:id',
1166                         array('action' => 'redirecturl',
1167                               'id' => '[0-9]+'));
1168
1169             // user stuff
1170
1171             Event::handle('RouterInitialized', array($m));
1172         }
1173
1174         return $m;
1175     }
1176
1177     function map($path)
1178     {
1179         try {
1180             $match = $this->m->match($path);
1181         } catch (Exception $e) {
1182             common_log(LOG_ERR, "Problem getting route for $path - " .
1183                        $e->getMessage());
1184             // TRANS: Client error on action trying to visit a non-existing page.
1185             $cac = new ClientErrorAction(_('Page not found.'), 404);
1186             $cac->showPage();
1187         }
1188
1189         return $match;
1190     }
1191
1192     function build($action, $args=null, $params=null, $fragment=null)
1193     {
1194         $action_arg = array('action' => $action);
1195
1196         if ($args) {
1197             $args = array_merge($action_arg, $args);
1198         } else {
1199             $args = $action_arg;
1200         }
1201
1202         $url = $this->m->generate($args, $params, $fragment);
1203         // Due to a bug in the Net_URL_Mapper code, the returned URL may
1204         // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
1205         // repair that here rather than modifying the upstream code...
1206
1207         $qpos = strpos($url, '?');
1208         if ($qpos !== false) {
1209             $url = substr($url, 0, $qpos+1) .
1210                 str_replace('?', '&', substr($url, $qpos+1));
1211
1212             // @fixme this is a hacky workaround for http_build_query in the
1213             // lower-level code and bad configs that set the default separator
1214             // to &amp; instead of &. Encoded &s in parameters will not be
1215             // affected.
1216             $url = substr($url, 0, $qpos+1) .
1217                 str_replace('&amp;', '&', substr($url, $qpos+1));
1218
1219         }
1220
1221         return $url;
1222     }
1223 }