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