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