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