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