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