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