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