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