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