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