router.php 48 KB

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