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