]> 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     /**
130      * Clear the global singleton instance for this class.
131      * Needed to ensure reset when switching site configurations.
132      */
133     static function clear()
134     {
135         Router::$inst = null;
136     }
137
138     function __construct()
139     {
140         if (empty($this->m)) {
141             if (!common_config('router', 'cache')) {
142                 $this->m = $this->initialize();
143             } else {
144                 $k = self::cacheKey();
145                 $c = Cache::instance();
146                 $m = $c->get($k);
147                 if (!empty($m)) {
148                     $this->m = $m;
149                 } else {
150                     $this->m = $this->initialize();
151                     $c->set($k, $this->m);
152                 }
153             }
154         }
155     }
156
157     /**
158      * Create a unique hashkey for the router.
159      *
160      * The router's url map can change based on the version of the software
161      * you're running and the plugins that are enabled. To avoid having bad routes
162      * get stuck in the cache, the key includes a list of plugins and the software
163      * version.
164      *
165      * There can still be problems with a) differences in versions of the plugins and
166      * b) people running code between official versions, but these tend to be more
167      * sophisticated users who can grok what's going on and clear their caches.
168      *
169      * @return string cache key string that should uniquely identify a router
170      */
171
172     static function cacheKey()
173     {
174         $parts = array('router');
175
176         // Many router paths depend on this setting.
177         if (common_config('singleuser', 'enabled')) {
178             $parts[] = '1user';
179         } else {
180             $parts[] = 'multi';
181         }
182
183         return Cache::codeKey(implode(':', $parts));
184     }
185
186     function initialize()
187     {
188         $m = StatusNet_URL_Mapper::getInstance();
189
190         if (Event::handle('StartInitializeRouter', array(&$m))) {
191
192             $m->connect('robots.txt', array('action' => 'robotstxt'));
193
194             $m->connect('opensearch/people', array('action' => 'opensearch',
195                                                    'type' => 'people'));
196             $m->connect('opensearch/notice', array('action' => 'opensearch',
197                                                    'type' => 'notice'));
198
199             // docs
200
201             $m->connect('doc/:title', array('action' => 'doc'));
202
203             $m->connect('main/otp/:user_id/:token',
204                         array('action' => 'otp'),
205                         array('user_id' => '[0-9]+',
206                               'token' => '.+'));
207
208             // main stuff is repetitive
209
210             $main = array('login', 'logout', 'register', 'subscribe',
211                           'unsubscribe', 'cancelsubscription', 'approvesub',
212                           'confirmaddress', 'recoverpassword',
213                           'invite', 'favor', 'disfavor', 'sup',
214                           'block', 'unblock', 'subedit',
215                           'groupblock', 'groupunblock',
216                           'sandbox', 'unsandbox',
217                           'silence', 'unsilence',
218                           'grantrole', 'revokerole',
219                           'repeat',
220                           'deleteuser',
221                           'geocode',
222                           'version',
223                           'backupaccount',
224                           'deleteaccount',
225                           'restoreaccount',
226             );
227
228             foreach ($main as $a) {
229                 $m->connect('main/'.$a, array('action' => $a));
230             }
231
232             // Also need a block variant accepting ID on URL for mail links
233             $m->connect('main/block/:profileid',
234                         array('action' => 'block'),
235                         array('profileid' => '[0-9]+'));
236
237             $m->connect('main/sup/:seconds', array('action' => 'sup'),
238                         array('seconds' => '[0-9]+'));
239
240             $m->connect('main/tagprofile', array('action' => 'tagprofile'));
241             $m->connect('main/tagprofile/:id', array('action' => 'tagprofile'),
242                                                array('id' => '[0-9]+'));
243
244             $m->connect('main/oembed',
245                         array('action' => 'oembed'));
246
247             $m->connect('main/xrds',
248                         array('action' => 'publicxrds'));
249             $m->connect('.well-known/host-meta',
250                         array('action' => 'hostmeta'));
251             $m->connect('main/xrd',
252                         array('action' => 'userxrd'));
253
254             // these take a code
255
256             foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) {
257                 $m->connect('main/'.$c.'/:code', array('action' => $c));
258             }
259
260             // exceptional
261
262             $m->connect('main/remote', array('action' => 'remotesubscribe'));
263             $m->connect('main/remote?nickname=:nickname', array('action' => 'remotesubscribe'), array('nickname' => '[A-Za-z0-9_-]+'));
264
265             foreach (Router::$bare as $action) {
266                 $m->connect('index.php?action=' . $action, array('action' => $action));
267             }
268
269             // settings
270
271             foreach (array('profile', 'avatar', 'password', 'im', 'oauthconnections',
272                            'oauthapps', 'email', 'sms', 'userdesign', 'url') as $s) {
273                 $m->connect('settings/'.$s, array('action' => $s.'settings'));
274             }
275
276             $m->connect('settings/oauthapps/show/:id',
277                         array('action' => 'showapplication'),
278                         array('id' => '[0-9]+')
279             );
280             $m->connect('settings/oauthapps/new',
281                         array('action' => 'newapplication')
282             );
283             $m->connect('settings/oauthapps/edit/:id',
284                         array('action' => 'editapplication'),
285                         array('id' => '[0-9]+')
286             );
287             $m->connect('settings/oauthapps/delete/:id',
288                         array('action' => 'deleteapplication'),
289                         array('id' => '[0-9]+')
290             );
291
292             // search
293
294             foreach (array('group', 'people', 'notice') as $s) {
295                 $m->connect('search/'.$s, array('action' => $s.'search'));
296                 $m->connect('search/'.$s.'?q=:q',
297                             array('action' => $s.'search'),
298                             array('q' => '.+'));
299             }
300
301             // The second of these is needed to make the link work correctly
302             // when inserted into the page. The first is needed to match the
303             // route on the way in. Seems to be another Net_URL_Mapper bug to me.
304             $m->connect('search/notice/rss', array('action' => 'noticesearchrss'));
305             $m->connect('search/notice/rss?q=:q', array('action' => 'noticesearchrss'),
306                         array('q' => '.+'));
307
308             $m->connect('attachment/:attachment',
309                         array('action' => 'attachment'),
310                         array('attachment' => '[0-9]+'));
311
312             $m->connect('attachment/:attachment/ajax',
313                         array('action' => 'attachment_ajax'),
314                         array('attachment' => '[0-9]+'));
315
316             $m->connect('attachment/:attachment/thumbnail',
317                         array('action' => 'attachment_thumbnail'),
318                         array('attachment' => '[0-9]+'));
319
320             $m->connect('notice/new', array('action' => 'newnotice'));
321             $m->connect('notice/new?replyto=:replyto',
322                         array('action' => 'newnotice'),
323                         array('replyto' => Nickname::DISPLAY_FMT));
324             $m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
325                         array('action' => 'newnotice'),
326                         array('replyto' => Nickname::DISPLAY_FMT),
327                         array('inreplyto' => '[0-9]+'));
328
329             $m->connect('notice/:notice/file',
330                         array('action' => 'file'),
331                         array('notice' => '[0-9]+'));
332
333             $m->connect('notice/:notice',
334                         array('action' => 'shownotice'),
335                         array('notice' => '[0-9]+'));
336             $m->connect('notice/delete', array('action' => 'deletenotice'));
337             $m->connect('notice/delete/:notice',
338                         array('action' => 'deletenotice'),
339                         array('notice' => '[0-9]+'));
340
341             $m->connect('bookmarklet/new', array('action' => 'bookmarklet'));
342
343             // conversation
344
345             $m->connect('conversation/:id',
346                         array('action' => 'conversation'),
347                         array('id' => '[0-9]+'));
348             $m->connect('conversation/:id/replies',
349                         array('action' => 'conversationreplies'),
350                         array('id' => '[0-9]+'));
351
352             $m->connect('message/new', array('action' => 'newmessage'));
353             $m->connect('message/new?to=:to', array('action' => 'newmessage'), array('to' => Nickname::DISPLAY_FMT));
354             $m->connect('message/:message',
355                         array('action' => 'showmessage'),
356                         array('message' => '[0-9]+'));
357
358             $m->connect('user/:id',
359                         array('action' => 'userbyid'),
360                         array('id' => '[0-9]+'));
361
362             $m->connect('tags/', array('action' => 'publictagcloud'));
363             $m->connect('tag/', array('action' => 'publictagcloud'));
364             $m->connect('tags', array('action' => 'publictagcloud'));
365             $m->connect('tag', array('action' => 'publictagcloud'));
366             $m->connect('tag/:tag/rss',
367                         array('action' => 'tagrss'),
368                         array('tag' => self::REGEX_TAG));
369             $m->connect('tag/:tag',
370                         array('action' => 'tag'),
371                         array('tag' => self::REGEX_TAG));
372
373             // groups
374
375             $m->connect('group/new', array('action' => 'newgroup'));
376
377             foreach (array('edit', 'join', 'leave', 'delete', 'cancel', 'approve') as $v) {
378                 $m->connect('group/:nickname/'.$v,
379                             array('action' => $v.'group'),
380                             array('nickname' => Nickname::DISPLAY_FMT));
381                 $m->connect('group/:id/id/'.$v,
382                             array('action' => $v.'group'),
383                             array('id' => '[0-9]+'));
384             }
385
386             foreach (array('members', 'logo', 'rss', 'designsettings') as $n) {
387                 $m->connect('group/:nickname/'.$n,
388                             array('action' => 'group'.$n),
389                             array('nickname' => Nickname::DISPLAY_FMT));
390             }
391
392             $m->connect('group/:nickname/foaf',
393                         array('action' => 'foafgroup'),
394                         array('nickname' => Nickname::DISPLAY_FMT));
395
396             $m->connect('group/:nickname/blocked',
397                         array('action' => 'blockedfromgroup'),
398                         array('nickname' => Nickname::DISPLAY_FMT));
399
400             $m->connect('group/:nickname/makeadmin',
401                         array('action' => 'makeadmin'),
402                         array('nickname' => Nickname::DISPLAY_FMT));
403
404             $m->connect('group/:nickname/members/pending',
405                         array('action' => 'groupqueue'),
406                         array('nickname' => Nickname::DISPLAY_FMT));
407
408             $m->connect('group/:id/id',
409                         array('action' => 'groupbyid'),
410                         array('id' => '[0-9]+'));
411
412             $m->connect('group/:nickname',
413                         array('action' => 'showgroup'),
414                         array('nickname' => Nickname::DISPLAY_FMT));
415
416             $m->connect('group/', array('action' => 'groups'));
417             $m->connect('group', array('action' => 'groups'));
418             $m->connect('groups/', array('action' => 'groups'));
419             $m->connect('groups', array('action' => 'groups'));
420
421             // Twitter-compatible API
422
423             // statuses API
424
425             $m->connect('api',
426                         array('action' => 'Redirect',
427                               'nextAction' => 'doc',
428                               'args' => array('title' => 'api')));
429
430             $m->connect('api/statuses/public_timeline.:format',
431                         array('action' => 'ApiTimelinePublic',
432                               'format' => '(xml|json|rss|atom|as)'));
433
434             $m->connect('api/statuses/friends_timeline.:format',
435                         array('action' => 'ApiTimelineFriends',
436                               'format' => '(xml|json|rss|atom|as)'));
437
438             $m->connect('api/statuses/friends_timeline/:id.:format',
439                         array('action' => 'ApiTimelineFriends',
440                               'id' => Nickname::INPUT_FMT,
441                               'format' => '(xml|json|rss|atom|as)'));
442
443             $m->connect('api/statuses/home_timeline.:format',
444                         array('action' => 'ApiTimelineHome',
445                               'format' => '(xml|json|rss|atom|as)'));
446
447             $m->connect('api/statuses/home_timeline/:id.:format',
448                         array('action' => 'ApiTimelineHome',
449                               'id' => Nickname::INPUT_FMT,
450                               'format' => '(xml|json|rss|atom|as)'));
451
452             $m->connect('api/statuses/user_timeline.:format',
453                         array('action' => 'ApiTimelineUser',
454                               'format' => '(xml|json|rss|atom|as)'));
455
456             $m->connect('api/statuses/user_timeline/:id.:format',
457                         array('action' => 'ApiTimelineUser',
458                               'id' => Nickname::INPUT_FMT,
459                               'format' => '(xml|json|rss|atom|as)'));
460
461             $m->connect('api/statuses/mentions.:format',
462                         array('action' => 'ApiTimelineMentions',
463                               'format' => '(xml|json|rss|atom|as)'));
464
465             $m->connect('api/statuses/mentions/:id.:format',
466                         array('action' => 'ApiTimelineMentions',
467                               'id' => Nickname::INPUT_FMT,
468                               'format' => '(xml|json|rss|atom|as)'));
469
470             $m->connect('api/statuses/replies.:format',
471                         array('action' => 'ApiTimelineMentions',
472                               'format' => '(xml|json|rss|atom|as)'));
473
474             $m->connect('api/statuses/replies/:id.:format',
475                         array('action' => 'ApiTimelineMentions',
476                               'id' => Nickname::INPUT_FMT,
477                               'format' => '(xml|json|rss|atom|as)'));
478
479             $m->connect('api/statuses/retweeted_by_me.:format',
480                         array('action' => 'ApiTimelineRetweetedByMe',
481                               'format' => '(xml|json|atom|as)'));
482
483             $m->connect('api/statuses/retweeted_to_me.:format',
484                         array('action' => 'ApiTimelineRetweetedToMe',
485                               'format' => '(xml|json|atom|as)'));
486
487             $m->connect('api/statuses/retweets_of_me.:format',
488                         array('action' => 'ApiTimelineRetweetsOfMe',
489                               'format' => '(xml|json|atom|as)'));
490
491             $m->connect('api/statuses/friends.:format',
492                         array('action' => 'ApiUserFriends',
493                               'format' => '(xml|json)'));
494
495             $m->connect('api/statuses/friends/:id.:format',
496                         array('action' => 'ApiUserFriends',
497                               'id' => Nickname::INPUT_FMT,
498                               'format' => '(xml|json)'));
499
500             $m->connect('api/statuses/followers.:format',
501                         array('action' => 'ApiUserFollowers',
502                               'format' => '(xml|json)'));
503
504             $m->connect('api/statuses/followers/:id.:format',
505                         array('action' => 'ApiUserFollowers',
506                               'id' => Nickname::INPUT_FMT,
507                               'format' => '(xml|json)'));
508
509             $m->connect('api/statuses/show.:format',
510                         array('action' => 'ApiStatusesShow',
511                               'format' => '(xml|json|atom)'));
512
513             $m->connect('api/statuses/show/:id.:format',
514                         array('action' => 'ApiStatusesShow',
515                               'id' => '[0-9]+',
516                               'format' => '(xml|json|atom)'));
517
518             $m->connect('api/statuses/update.:format',
519                         array('action' => 'ApiStatusesUpdate',
520                               'format' => '(xml|json)'));
521
522             $m->connect('api/statuses/destroy.:format',
523                         array('action' => 'ApiStatusesDestroy',
524                               'format' => '(xml|json)'));
525
526             $m->connect('api/statuses/destroy/:id.:format',
527                         array('action' => 'ApiStatusesDestroy',
528                               'id' => '[0-9]+',
529                               'format' => '(xml|json)'));
530
531             $m->connect('api/statuses/retweet/:id.:format',
532                         array('action' => 'ApiStatusesRetweet',
533                               'id' => '[0-9]+',
534                               'format' => '(xml|json)'));
535
536             $m->connect('api/statuses/retweets/:id.:format',
537                         array('action' => 'ApiStatusesRetweets',
538                               'id' => '[0-9]+',
539                               'format' => '(xml|json)'));
540
541             // users
542
543             $m->connect('api/users/show.:format',
544                         array('action' => 'ApiUserShow',
545                               'format' => '(xml|json)'));
546
547             $m->connect('api/users/show/:id.:format',
548                         array('action' => 'ApiUserShow',
549                               'id' => Nickname::INPUT_FMT,
550                               'format' => '(xml|json)'));
551
552             $m->connect('api/users/profile_image/:screen_name.:format',
553                         array('action' => 'ApiUserProfileImage',
554                               'screen_name' => Nickname::DISPLAY_FMT,
555                               'format' => '(xml|json)'));
556
557             // direct messages
558
559             $m->connect('api/direct_messages.:format',
560                         array('action' => 'ApiDirectMessage',
561                               'format' => '(xml|json|rss|atom)'));
562
563             $m->connect('api/direct_messages/sent.:format',
564                         array('action' => 'ApiDirectMessage',
565                               'format' => '(xml|json|rss|atom)',
566                               'sent' => true));
567
568             $m->connect('api/direct_messages/new.:format',
569                         array('action' => 'ApiDirectMessageNew',
570                               'format' => '(xml|json)'));
571
572             // friendships
573
574             $m->connect('api/friendships/show.:format',
575                         array('action' => 'ApiFriendshipsShow',
576                               'format' => '(xml|json)'));
577
578             $m->connect('api/friendships/exists.:format',
579                         array('action' => 'ApiFriendshipsExists',
580                               'format' => '(xml|json)'));
581
582             $m->connect('api/friendships/create.:format',
583                         array('action' => 'ApiFriendshipsCreate',
584                               'format' => '(xml|json)'));
585
586             $m->connect('api/friendships/destroy.:format',
587                         array('action' => 'ApiFriendshipsDestroy',
588                               'format' => '(xml|json)'));
589
590             $m->connect('api/friendships/create/:id.:format',
591                         array('action' => 'ApiFriendshipsCreate',
592                               'id' => Nickname::INPUT_FMT,
593                               'format' => '(xml|json)'));
594
595             $m->connect('api/friendships/destroy/:id.:format',
596                         array('action' => 'ApiFriendshipsDestroy',
597                               'id' => Nickname::INPUT_FMT,
598                               'format' => '(xml|json)'));
599
600             // Social graph
601
602             $m->connect('api/friends/ids/:id.:format',
603                         array('action' => 'ApiUserFriends',
604                               'ids_only' => true));
605
606             $m->connect('api/followers/ids/:id.:format',
607                         array('action' => 'ApiUserFollowers',
608                               'ids_only' => true));
609
610             $m->connect('api/friends/ids.:format',
611                         array('action' => 'ApiUserFriends',
612                               'ids_only' => true));
613
614             $m->connect('api/followers/ids.:format',
615                         array('action' => 'ApiUserFollowers',
616                               'ids_only' => true));
617
618             // account
619
620             $m->connect('api/account/verify_credentials.:format',
621                         array('action' => 'ApiAccountVerifyCredentials'));
622
623             $m->connect('api/account/update_profile.:format',
624                         array('action' => 'ApiAccountUpdateProfile'));
625
626             $m->connect('api/account/update_profile_image.:format',
627                         array('action' => 'ApiAccountUpdateProfileImage'));
628
629             $m->connect('api/account/update_profile_background_image.:format',
630                         array('action' => 'ApiAccountUpdateProfileBackgroundImage'));
631
632             $m->connect('api/account/update_profile_colors.:format',
633                         array('action' => 'ApiAccountUpdateProfileColors'));
634
635             $m->connect('api/account/update_delivery_device.:format',
636                         array('action' => 'ApiAccountUpdateDeliveryDevice'));
637
638             // special case where verify_credentials is called w/out a format
639
640             $m->connect('api/account/verify_credentials',
641                         array('action' => 'ApiAccountVerifyCredentials'));
642
643             $m->connect('api/account/rate_limit_status.:format',
644                         array('action' => 'ApiAccountRateLimitStatus'));
645
646             // favorites
647
648             $m->connect('api/favorites.:format',
649                         array('action' => 'ApiTimelineFavorites',
650                               'format' => '(xml|json|rss|atom|as)'));
651
652             $m->connect('api/favorites/:id.:format',
653                         array('action' => 'ApiTimelineFavorites',
654                               'id' => Nickname::INPUT_FMT,
655                               'format' => '(xml|json|rss|atom|as)'));
656
657             $m->connect('api/favorites/create/:id.:format',
658                         array('action' => 'ApiFavoriteCreate',
659                               'id' => '[0-9]+',
660                               'format' => '(xml|json)'));
661
662             $m->connect('api/favorites/destroy/:id.:format',
663                         array('action' => 'ApiFavoriteDestroy',
664                               'id' => '[0-9]+',
665                               'format' => '(xml|json)'));
666             // blocks
667
668             $m->connect('api/blocks/create.:format',
669                         array('action' => 'ApiBlockCreate',
670                               'format' => '(xml|json)'));
671
672             $m->connect('api/blocks/create/:id.:format',
673                         array('action' => 'ApiBlockCreate',
674                               'id' => Nickname::INPUT_FMT,
675                               'format' => '(xml|json)'));
676
677             $m->connect('api/blocks/destroy.:format',
678                         array('action' => 'ApiBlockDestroy',
679                               'format' => '(xml|json)'));
680
681             $m->connect('api/blocks/destroy/:id.:format',
682                         array('action' => 'ApiBlockDestroy',
683                               'id' => Nickname::INPUT_FMT,
684                               'format' => '(xml|json)'));
685             // help
686
687             $m->connect('api/help/test.:format',
688                         array('action' => 'ApiHelpTest',
689                               'format' => '(xml|json)'));
690
691             // statusnet
692
693             $m->connect('api/statusnet/version.:format',
694                         array('action' => 'ApiStatusnetVersion',
695                               'format' => '(xml|json)'));
696
697             $m->connect('api/statusnet/config.:format',
698                         array('action' => 'ApiStatusnetConfig',
699                               'format' => '(xml|json)'));
700
701             // For older methods, we provide "laconica" base action
702
703             $m->connect('api/laconica/version.:format',
704                         array('action' => 'ApiStatusnetVersion',
705                               'format' => '(xml|json)'));
706
707             $m->connect('api/laconica/config.:format',
708                         array('action' => 'ApiStatusnetConfig',
709                               'format' => '(xml|json)'));
710
711             // Groups and tags are newer than 0.8.1 so no backward-compatibility
712             // necessary
713
714             // Groups
715             //'list' has to be handled differently, as php will not allow a method to be named 'list'
716
717             $m->connect('api/statusnet/groups/timeline/:id.:format',
718                         array('action' => 'ApiTimelineGroup',
719                               'id' => Nickname::INPUT_FMT,
720                               'format' => '(xml|json|rss|atom|as)'));
721
722             $m->connect('api/statusnet/groups/show.:format',
723                         array('action' => 'ApiGroupShow',
724                               'format' => '(xml|json)'));
725
726             $m->connect('api/statusnet/groups/show/:id.:format',
727                         array('action' => 'ApiGroupShow',
728                               'id' => Nickname::INPUT_FMT,
729                               'format' => '(xml|json)'));
730
731             $m->connect('api/statusnet/groups/join.:format',
732                         array('action' => 'ApiGroupJoin',
733                               'id' => Nickname::INPUT_FMT,
734                               'format' => '(xml|json)'));
735
736             $m->connect('api/statusnet/groups/join/:id.:format',
737                         array('action' => 'ApiGroupJoin',
738                               'format' => '(xml|json)'));
739
740             $m->connect('api/statusnet/groups/leave.:format',
741                         array('action' => 'ApiGroupLeave',
742                               'id' => Nickname::INPUT_FMT,
743                               'format' => '(xml|json)'));
744
745             $m->connect('api/statusnet/groups/leave/:id.:format',
746                         array('action' => 'ApiGroupLeave',
747                               'format' => '(xml|json)'));
748
749             $m->connect('api/statusnet/groups/is_member.:format',
750                         array('action' => 'ApiGroupIsMember',
751                               'format' => '(xml|json)'));
752
753             $m->connect('api/statusnet/groups/list.:format',
754                         array('action' => 'ApiGroupList',
755                               'format' => '(xml|json|rss|atom)'));
756
757             $m->connect('api/statusnet/groups/list/:id.:format',
758                         array('action' => 'ApiGroupList',
759                               'id' => Nickname::INPUT_FMT,
760                               'format' => '(xml|json|rss|atom)'));
761
762             $m->connect('api/statusnet/groups/list_all.:format',
763                         array('action' => 'ApiGroupListAll',
764                               'format' => '(xml|json|rss|atom)'));
765
766             $m->connect('api/statusnet/groups/membership.:format',
767                         array('action' => 'ApiGroupMembership',
768                               'format' => '(xml|json)'));
769
770             $m->connect('api/statusnet/groups/membership/:id.:format',
771                         array('action' => 'ApiGroupMembership',
772                               'id' => Nickname::INPUT_FMT,
773                               'format' => '(xml|json)'));
774
775             $m->connect('api/statusnet/groups/create.:format',
776                         array('action' => 'ApiGroupCreate',
777                               'format' => '(xml|json)'));
778
779             $m->connect('api/statusnet/groups/update/:id.:format',
780                         array('action' => 'ApiGroupProfileUpdate',
781                               'id' => '[a-zA-Z0-9]+',
782                               'format' => '(xml|json)'));
783
784             // Lists (people tags)
785
786             $m->connect('api/lists/memberships.:format',
787                         array('action' => 'ApiListMemberships',
788                               'format' => '(xml|json)'));
789
790             $m->connect('api/:user/lists/memberships.:format',
791                         array('action' => 'ApiListMemberships',
792                               'user' => '[a-zA-Z0-9]+',
793                               'format' => '(xml|json)'));
794
795             $m->connect('api/lists/subscriptions.:format',
796                         array('action' => 'ApiListSubscriptions',
797                               'format' => '(xml|json)'));
798
799             $m->connect('api/:user/lists/subscriptions.:format',
800                         array('action' => 'ApiListSubscriptions',
801                               'user' => '[a-zA-Z0-9]+',
802                               'format' => '(xml|json)'));
803             $m->connect('api/lists.:format',
804                         array('action' => 'ApiLists',
805                               'format' => '(xml|json)'));
806
807             $m->connect('api/:user/lists.:format',
808                         array('action' => 'ApiLists',
809                               'user' => '[a-zA-Z0-9]+',
810                               'format' => '(xml|json)'));
811
812             $m->connect('api/:user/lists/:id.:format',
813                         array('action' => 'ApiList',
814                               'user' => '[a-zA-Z0-9]+',
815                               'id' => '[a-zA-Z0-9]+',
816                               'format' => '(xml|json)'));
817
818             $m->connect('api/:user/lists/:id/statuses.:format',
819                         array('action' => 'ApiTimelineList',
820                               'user' => '[a-zA-Z0-9]+',
821                               'id' => '[a-zA-Z0-9]+',
822                               'format' => '(xml|json|rss|atom)'));
823
824             $m->connect('api/:user/:list_id/members.:format',
825                         array('action' => 'ApiListMembers',
826                               'user' => '[a-zA-Z0-9]+',
827                               'list_id' => '[a-zA-Z0-9]+',
828                               'format' => '(xml|json)'));
829
830             $m->connect('api/:user/:list_id/subscribers.:format',
831                         array('action' => 'ApiListSubscribers',
832                               'user' => '[a-zA-Z0-9]+',
833                               'list_id' => '[a-zA-Z0-9]+',
834                               'format' => '(xml|json)'));
835
836             $m->connect('api/:user/:list_id/members/:id.:format',
837                         array('action' => 'ApiListMember',
838                               'user' => '[a-zA-Z0-9]+',
839                               'list_id' => '[a-zA-Z0-9]+',
840                               'id' => '[a-zA-Z0-9]+',
841                               'format' => '(xml|json)'));
842
843             $m->connect('api/:user/:list_id/subscribers/:id.:format',
844                         array('action' => 'ApiListSubscriber',
845                               'user' => '[a-zA-Z0-9]+',
846                               'list_id' => '[a-zA-Z0-9]+',
847                               'id' => '[a-zA-Z0-9]+',
848                               'format' => '(xml|json)'));
849
850             // Tags
851             $m->connect('api/statusnet/tags/timeline/:tag.:format',
852                         array('action' => 'ApiTimelineTag',
853                               'format' => '(xml|json|rss|atom|as)'));
854
855             // media related
856             $m->connect(
857                 'api/statusnet/media/upload',
858                 array('action' => 'ApiMediaUpload')
859             );
860
861             // search
862             $m->connect('api/search.atom', array('action' => 'ApiSearchAtom'));
863             $m->connect('api/search.json', array('action' => 'ApiSearchJSON'));
864             $m->connect('api/trends.json', array('action' => 'ApiTrends'));
865
866             $m->connect('api/oauth/request_token',
867                         array('action' => 'ApiOauthRequestToken'));
868
869             $m->connect('api/oauth/access_token',
870                         array('action' => 'ApiOauthAccessToken'));
871
872             $m->connect('api/oauth/authorize',
873                         array('action' => 'ApiOauthAuthorize'));
874
875             // Admin
876
877             $m->connect('panel/site', array('action' => 'siteadminpanel'));
878             $m->connect('panel/design', array('action' => 'designadminpanel'));
879             $m->connect('panel/user', array('action' => 'useradminpanel'));
880                 $m->connect('panel/access', array('action' => 'accessadminpanel'));
881             $m->connect('panel/paths', array('action' => 'pathsadminpanel'));
882             $m->connect('panel/sessions', array('action' => 'sessionsadminpanel'));
883             $m->connect('panel/sitenotice', array('action' => 'sitenoticeadminpanel'));
884             $m->connect('panel/snapshot', array('action' => 'snapshotadminpanel'));
885             $m->connect('panel/license', array('action' => 'licenseadminpanel'));
886
887             $m->connect('panel/plugins', array('action' => 'pluginsadminpanel'));
888             $m->connect('panel/plugins/enable/:plugin',
889                         array('action' => 'pluginenable'),
890                         array('plugin' => '[A-Za-z0-9_]+'));
891             $m->connect('panel/plugins/disable/:plugin',
892                         array('action' => 'plugindisable'),
893                         array('plugin' => '[A-Za-z0-9_]+'));
894
895             $m->connect('getfile/:filename',
896                         array('action' => 'getfile'),
897                         array('filename' => '[A-Za-z0-9._-]+'));
898
899             // In the "root"
900
901             if (common_config('singleuser', 'enabled')) {
902
903                 $nickname = User::singleUserNickname();
904
905                 foreach (array('subscriptions', 'subscribers',
906                                'all', 'foaf', 'xrds',
907                                'replies', 'microsummary', 'hcard') as $a) {
908                     $m->connect($a,
909                                 array('action' => $a,
910                                       'nickname' => $nickname));
911                 }
912
913                 foreach (array('subscriptions', 'subscribers') as $a) {
914                     $m->connect($a.'/:tag',
915                                 array('action' => $a,
916                                       'nickname' => $nickname),
917                                 array('tag' => self::REGEX_TAG));
918                 }
919
920                 $m->connect('subscribers/pending',
921                             array('action' => 'subqueue',
922                                   'nickname' => $nickname));
923
924                 foreach (array('rss', 'groups') as $a) {
925                     $m->connect($a,
926                                 array('action' => 'user'.$a,
927                                       'nickname' => $nickname));
928                 }
929
930                 foreach (array('all', 'replies', 'favorites') as $a) {
931                     $m->connect($a.'/rss',
932                                 array('action' => $a.'rss',
933                                       'nickname' => $nickname));
934                 }
935
936                 $m->connect('favorites',
937                             array('action' => 'showfavorites',
938                                   'nickname' => $nickname));
939
940                 $m->connect('avatar/:size',
941                             array('action' => 'avatarbynickname',
942                                   'nickname' => $nickname),
943                             array('size' => '(original|96|48|24)'));
944
945                 $m->connect('tag/:tag/rss',
946                             array('action' => 'userrss',
947                                   'nickname' => $nickname),
948                             array('tag' => self::REGEX_TAG));
949
950                 $m->connect('tag/:tag',
951                             array('action' => 'showstream',
952                                   'nickname' => $nickname),
953                             array('tag' => self::REGEX_TAG));
954
955                 $m->connect('rsd.xml',
956                             array('action' => 'rsd',
957                                   'nickname' => $nickname));
958
959                 $m->connect('',
960                             array('action' => 'showstream',
961                                   'nickname' => $nickname));
962             } else {
963                 $m->connect('', array('action' => 'public'));
964                 $m->connect('rss', array('action' => 'publicrss'));
965                 $m->connect('featuredrss', array('action' => 'featuredrss'));
966                 $m->connect('favoritedrss', array('action' => 'favoritedrss'));
967                 $m->connect('featured/', array('action' => 'featured'));
968                 $m->connect('featured', array('action' => 'featured'));
969                 $m->connect('favorited/', array('action' => 'favorited'));
970                 $m->connect('favorited', array('action' => 'favorited'));
971                 $m->connect('rsd.xml', array('action' => 'rsd'));
972
973                 foreach (array('subscriptions', 'subscribers',
974                                'nudge', 'all', 'foaf', 'xrds',
975                                'replies', 'inbox', 'outbox', 'microsummary', 'hcard') as $a) {
976                     $m->connect(':nickname/'.$a,
977                                 array('action' => $a),
978                                 array('nickname' => Nickname::DISPLAY_FMT));
979                 }
980                 $m->connect(':nickname/subscribers/pending',
981                             array('action' => 'subqueue'),
982                             array('nickname' => Nickname::DISPLAY_FMT));
983
984                 // people tags
985
986                 $m->connect('peopletags', array('action' => 'publicpeopletagcloud'));
987
988                 $m->connect('peopletag/:tag', array('action' => 'peopletag',
989                                                     'tag'    => self::REGEX_TAG));
990
991                 $m->connect('selftag/:tag', array('action' => 'selftag',
992                                                   'tag'    => self::REGEX_TAG));
993
994                 $m->connect('main/addpeopletag', array('action' => 'addpeopletag'));
995
996                 $m->connect('main/removepeopletag', array('action' => 'removepeopletag'));
997
998                 $m->connect('main/profilecompletion', array('action' => 'profilecompletion'));
999
1000                 $m->connect('main/peopletagautocomplete', array('action' => 'peopletagautocomplete'));
1001
1002                 $m->connect(':nickname/peopletags',
1003                                 array('action' => 'peopletagsbyuser',
1004                                       'nickname' => Nickname::DISPLAY_FMT));
1005
1006                 $m->connect(':nickname/peopletags/private',
1007                                 array('action' => 'peopletagsbyuser',
1008                                       'nickname' => Nickname::DISPLAY_FMT,
1009                                       'private' => 1));
1010
1011                 $m->connect(':nickname/peopletags/public',
1012                                 array('action' => 'peopletagsbyuser',
1013                                       'nickname' => Nickname::DISPLAY_FMT,
1014                                       'public' => 1));
1015
1016                 $m->connect(':nickname/othertags',
1017                                 array('action' => 'peopletagsforuser',
1018                                       'nickname' => Nickname::DISPLAY_FMT));
1019
1020                 $m->connect(':nickname/peopletagsubscriptions',
1021                                 array('action' => 'peopletagsubscriptions',
1022                                       'nickname' => Nickname::DISPLAY_FMT));
1023
1024                 $m->connect(':tagger/all/:tag/subscribers',
1025                                 array('action' => 'peopletagsubscribers',
1026                                       'tagger' => Nickname::DISPLAY_FMT,
1027                                       'tag' => self::REGEX_TAG));
1028
1029                 $m->connect(':tagger/all/:tag/tagged',
1030                                 array('action' => 'peopletagged',
1031                                       'tagger' => Nickname::DISPLAY_FMT,
1032                                       'tag' => self::REGEX_TAG));
1033
1034                 $m->connect(':tagger/all/:tag/edit',
1035                                 array('action' => 'editpeopletag',
1036                                       'tagger' => Nickname::DISPLAY_FMT,
1037                                       'tag' => self::REGEX_TAG));
1038
1039                 foreach(array('subscribe', 'unsubscribe') as $v) {
1040                     $m->connect('peopletag/:id/'.$v,
1041                                     array('action' => $v.'peopletag',
1042                                           'id' => '[0-9]{1,64}'));
1043                 }
1044                 $m->connect('user/:tagger_id/profiletag/:id/id',
1045                                 array('action' => 'profiletagbyid',
1046                                       'tagger_id' => '[0-9]+',
1047                                       'id' => '[0-9]+'));
1048
1049                 $m->connect(':tagger/all/:tag',
1050                                 array('action' => 'showprofiletag',
1051                                       'tagger' => Nickname::DISPLAY_FMT,
1052                                       'tag' => self::REGEX_TAG));
1053
1054                 foreach (array('subscriptions', 'subscribers') as $a) {
1055                     $m->connect(':nickname/'.$a.'/:tag',
1056                                 array('action' => $a),
1057                                 array('tag' => self::REGEX_TAG,
1058                                       'nickname' => Nickname::DISPLAY_FMT));
1059                 }
1060
1061                 foreach (array('rss', 'groups') as $a) {
1062                     $m->connect(':nickname/'.$a,
1063                                 array('action' => 'user'.$a),
1064                                 array('nickname' => Nickname::DISPLAY_FMT));
1065                 }
1066
1067                 foreach (array('all', 'replies', 'favorites') as $a) {
1068                     $m->connect(':nickname/'.$a.'/rss',
1069                                 array('action' => $a.'rss'),
1070                                 array('nickname' => Nickname::DISPLAY_FMT));
1071                 }
1072
1073                 $m->connect(':nickname/favorites',
1074                             array('action' => 'showfavorites'),
1075                             array('nickname' => Nickname::DISPLAY_FMT));
1076
1077                 $m->connect(':nickname/avatar/:size',
1078                             array('action' => 'avatarbynickname'),
1079                             array('size' => '(original|96|48|24)',
1080                                   'nickname' => Nickname::DISPLAY_FMT));
1081
1082                 $m->connect(':nickname/tag/:tag/rss',
1083                             array('action' => 'userrss'),
1084                             array('nickname' => Nickname::DISPLAY_FMT),
1085                             array('tag' => self::REGEX_TAG));
1086
1087                 $m->connect(':nickname/tag/:tag',
1088                             array('action' => 'showstream'),
1089                             array('nickname' => Nickname::DISPLAY_FMT),
1090                             array('tag' => self::REGEX_TAG));
1091
1092                 $m->connect(':nickname/rsd.xml',
1093                             array('action' => 'rsd'),
1094                             array('nickname' => Nickname::DISPLAY_FMT));
1095
1096                 $m->connect(':nickname',
1097                             array('action' => 'showstream'),
1098                             array('nickname' => Nickname::DISPLAY_FMT));
1099             }
1100
1101             // AtomPub API
1102
1103             $m->connect('api/statusnet/app/service/:id.xml',
1104                         array('action' => 'ApiAtomService'),
1105                         array('id' => Nickname::DISPLAY_FMT));
1106
1107             $m->connect('api/statusnet/app/service.xml',
1108                         array('action' => 'ApiAtomService'));
1109
1110             $m->connect('api/statusnet/app/subscriptions/:subscriber/:subscribed.atom',
1111                         array('action' => 'AtomPubShowSubscription'),
1112                         array('subscriber' => '[0-9]+',
1113                               'subscribed' => '[0-9]+'));
1114
1115             $m->connect('api/statusnet/app/subscriptions/:subscriber.atom',
1116                         array('action' => 'AtomPubSubscriptionFeed'),
1117                         array('subscriber' => '[0-9]+'));
1118
1119             $m->connect('api/statusnet/app/favorites/:profile/:notice.atom',
1120                         array('action' => 'AtomPubShowFavorite'),
1121                         array('profile' => '[0-9]+',
1122                               'notice' => '[0-9]+'));
1123
1124             $m->connect('api/statusnet/app/favorites/:profile.atom',
1125                         array('action' => 'AtomPubFavoriteFeed'),
1126                         array('profile' => '[0-9]+'));
1127
1128             $m->connect('api/statusnet/app/memberships/:profile/:group.atom',
1129                         array('action' => 'AtomPubShowMembership'),
1130                         array('profile' => '[0-9]+',
1131                               'group' => '[0-9]+'));
1132
1133             $m->connect('api/statusnet/app/memberships/:profile.atom',
1134                         array('action' => 'AtomPubMembershipFeed'),
1135                         array('profile' => '[0-9]+'));
1136
1137             // URL shortening
1138
1139             $m->connect('url/:id',
1140                         array('action' => 'redirecturl',
1141                               'id' => '[0-9]+'));
1142
1143             // user stuff
1144
1145             Event::handle('RouterInitialized', array($m));
1146         }
1147
1148         return $m;
1149     }
1150
1151     function map($path)
1152     {
1153         try {
1154             $match = $this->m->match($path);
1155         } catch (Net_URL_Mapper_InvalidException $e) {
1156             common_log(LOG_ERR, "Problem getting route for $path - " .
1157                        $e->getMessage());
1158             // TRANS: Client error on action trying to visit a non-existing page.
1159             $cac = new ClientErrorAction(_('Page not found.'), 404);
1160             $cac->showPage();
1161         }
1162
1163         return $match;
1164     }
1165
1166     function build($action, $args=null, $params=null, $fragment=null)
1167     {
1168         $action_arg = array('action' => $action);
1169
1170         if ($args) {
1171             $args = array_merge($action_arg, $args);
1172         } else {
1173             $args = $action_arg;
1174         }
1175
1176         $url = $this->m->generate($args, $params, $fragment);
1177
1178         // Due to a bug in the Net_URL_Mapper code, the returned URL may
1179         // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
1180         // repair that here rather than modifying the upstream code...
1181
1182         $qpos = strpos($url, '?');
1183         if ($qpos !== false) {
1184             $url = substr($url, 0, $qpos+1) .
1185                 str_replace('?', '&', substr($url, $qpos+1));
1186
1187             // @fixme this is a hacky workaround for http_build_query in the
1188             // lower-level code and bad configs that set the default separator
1189             // to &amp; instead of &. Encoded &s in parameters will not be
1190             // affected.
1191             $url = substr($url, 0, $qpos+1) .
1192                 str_replace('&amp;', '&', substr($url, $qpos+1));
1193
1194         }
1195
1196         return $url;
1197     }
1198 }