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