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('STATUSNET') && !defined('LACONICA')) {
  30. exit(1);
  31. }
  32. /**
  33. * URL Router
  34. *
  35. * Cheap wrapper around Net_URL_Mapper
  36. *
  37. * @category URL
  38. * @package StatusNet
  39. * @author Evan Prodromou <evan@status.net>
  40. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  41. * @link http://status.net/
  42. */
  43. class Router
  44. {
  45. var $m = null;
  46. static $inst = null;
  47. const REGEX_TAG = '[^\/]+'; // [\pL\pN_\-\.]{1,64} better if we can do unicode regexes
  48. static function get()
  49. {
  50. if (!Router::$inst) {
  51. Router::$inst = new Router();
  52. }
  53. return Router::$inst;
  54. }
  55. /**
  56. * Clear the global singleton instance for this class.
  57. * Needed to ensure reset when switching site configurations.
  58. */
  59. static function clear()
  60. {
  61. Router::$inst = null;
  62. }
  63. function __construct()
  64. {
  65. if (empty($this->m)) {
  66. $this->m = $this->initialize();
  67. }
  68. }
  69. /**
  70. * Create a unique hashkey for the router.
  71. *
  72. * The router's url map can change based on the version of the software
  73. * you're running and the plugins that are enabled. To avoid having bad routes
  74. * get stuck in the cache, the key includes a list of plugins and the software
  75. * version.
  76. *
  77. * There can still be problems with a) differences in versions of the plugins and
  78. * b) people running code between official versions, but these tend to be more
  79. * sophisticated users who can grok what's going on and clear their caches.
  80. *
  81. * @return string cache key string that should uniquely identify a router
  82. */
  83. static function cacheKey()
  84. {
  85. $parts = array('router');
  86. // Many router paths depend on this setting.
  87. if (common_config('singleuser', 'enabled')) {
  88. $parts[] = '1user';
  89. } else {
  90. $parts[] = 'multi';
  91. }
  92. return Cache::codeKey(implode(':', $parts));
  93. }
  94. function initialize()
  95. {
  96. $m = new URLMapper();
  97. if (Event::handle('StartInitializeRouter', array(&$m))) {
  98. $m->connect('robots.txt', array('action' => 'robotstxt'));
  99. $m->connect('opensearch/people', array('action' => 'opensearch',
  100. 'type' => 'people'));
  101. $m->connect('opensearch/notice', array('action' => 'opensearch',
  102. 'type' => 'notice'));
  103. // docs
  104. $m->connect('doc/:title', array('action' => 'doc'));
  105. $m->connect('main/otp/:user_id/:token',
  106. array('action' => 'otp'),
  107. array('user_id' => '[0-9]+',
  108. 'token' => '.+'));
  109. // these take a code; before the main part
  110. foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) {
  111. $m->connect('main/'.$c.'/:code', array('action' => $c));
  112. }
  113. // Also need a block variant accepting ID on URL for mail links
  114. $m->connect('main/block/:profileid',
  115. array('action' => 'block'),
  116. array('profileid' => '[0-9]+'));
  117. $m->connect('main/sup/:seconds', array('action' => 'sup'),
  118. array('seconds' => '[0-9]+'));
  119. // main stuff is repetitive
  120. $main = array('login', 'logout', 'register', 'subscribe',
  121. 'unsubscribe', 'cancelsubscription', 'approvesub',
  122. 'confirmaddress', 'recoverpassword',
  123. 'invite', 'sup',
  124. 'block', 'unblock', 'subedit',
  125. 'groupblock', 'groupunblock',
  126. 'sandbox', 'unsandbox',
  127. 'silence', 'unsilence',
  128. 'grantrole', 'revokerole',
  129. 'deleteuser',
  130. 'geocode',
  131. 'version',
  132. 'backupaccount',
  133. 'deleteaccount',
  134. 'restoreaccount',
  135. 'top',
  136. );
  137. foreach ($main as $a) {
  138. $m->connect('main/'.$a, array('action' => $a));
  139. }
  140. $m->connect('main/public', array('action' => 'public'));
  141. $m->connect('main/all', array('action' => 'networkpublic'));
  142. $m->connect('main/tagprofile/:id', array('action' => 'tagprofile'),
  143. array('id' => '[0-9]+'));
  144. $m->connect('main/tagprofile', array('action' => 'tagprofile'));
  145. $m->connect('main/xrds',
  146. array('action' => 'publicxrds'));
  147. // settings
  148. foreach (array('profile', 'avatar', 'password', 'im', 'oauthconnections',
  149. 'oauthapps', 'email', 'sms', 'url') as $s) {
  150. $m->connect('settings/'.$s, array('action' => $s.'settings'));
  151. }
  152. if (common_config('oldschool', 'enabled')) {
  153. $m->connect('settings/oldschool', array('action' => 'oldschoolsettings'));
  154. }
  155. $m->connect('settings/oauthapps/show/:id',
  156. array('action' => 'showapplication'),
  157. array('id' => '[0-9]+')
  158. );
  159. $m->connect('settings/oauthapps/new',
  160. array('action' => 'newapplication')
  161. );
  162. $m->connect('settings/oauthapps/edit/:id',
  163. array('action' => 'editapplication'),
  164. array('id' => '[0-9]+')
  165. );
  166. $m->connect('settings/oauthapps/delete/:id',
  167. array('action' => 'deleteapplication'),
  168. array('id' => '[0-9]+')
  169. );
  170. // search
  171. foreach (array('group', 'people', 'notice') as $s) {
  172. $m->connect('search/'.$s.'?q=:q',
  173. array('action' => $s.'search'),
  174. array('q' => '.+'));
  175. $m->connect('search/'.$s, array('action' => $s.'search'));
  176. }
  177. // The second of these is needed to make the link work correctly
  178. // when inserted into the page. The first is needed to match the
  179. // route on the way in. Seems to be another Net_URL_Mapper bug to me.
  180. $m->connect('search/notice/rss?q=:q', array('action' => 'noticesearchrss'),
  181. array('q' => '.+'));
  182. $m->connect('search/notice/rss', array('action' => 'noticesearchrss'));
  183. $m->connect('attachment/:attachment',
  184. array('action' => 'attachment'),
  185. array('attachment' => '[0-9]+'));
  186. $m->connect('attachment/:attachment/thumbnail',
  187. array('action' => 'attachment_thumbnail'),
  188. array('attachment' => '[0-9]+'));
  189. $m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
  190. array('action' => 'newnotice'),
  191. array('replyto' => Nickname::DISPLAY_FMT),
  192. array('inreplyto' => '[0-9]+'));
  193. $m->connect('notice/new?replyto=:replyto',
  194. array('action' => 'newnotice'),
  195. array('replyto' => Nickname::DISPLAY_FMT));
  196. $m->connect('notice/new', array('action' => 'newnotice'));
  197. $m->connect('notice/:notice',
  198. array('action' => 'shownotice'),
  199. array('notice' => '[0-9]+'));
  200. $m->connect('notice/delete/:notice',
  201. array('action' => 'deletenotice'),
  202. array('notice' => '[0-9]+'));
  203. $m->connect('notice/delete', array('action' => 'deletenotice'));
  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. // search
  602. $m->connect('api/search.atom', array('action' => 'ApiSearchAtom'));
  603. $m->connect('api/search.json', array('action' => 'ApiSearchJSON'));
  604. $m->connect('api/trends.json', array('action' => 'ApiTrends'));
  605. $m->connect('api/oauth/request_token',
  606. array('action' => 'ApiOAuthRequestToken'));
  607. $m->connect('api/oauth/access_token',
  608. array('action' => 'ApiOAuthAccessToken'));
  609. $m->connect('api/oauth/authorize',
  610. array('action' => 'ApiOAuthAuthorize'));
  611. // Admin
  612. $m->connect('panel/site', array('action' => 'siteadminpanel'));
  613. $m->connect('panel/user', array('action' => 'useradminpanel'));
  614. $m->connect('panel/access', array('action' => 'accessadminpanel'));
  615. $m->connect('panel/paths', array('action' => 'pathsadminpanel'));
  616. $m->connect('panel/sessions', array('action' => 'sessionsadminpanel'));
  617. $m->connect('panel/sitenotice', array('action' => 'sitenoticeadminpanel'));
  618. $m->connect('panel/license', array('action' => 'licenseadminpanel'));
  619. $m->connect('panel/plugins', array('action' => 'pluginsadminpanel'));
  620. $m->connect('panel/plugins/enable/:plugin',
  621. array('action' => 'pluginenable'),
  622. array('plugin' => '[A-Za-z0-9_]+'));
  623. $m->connect('panel/plugins/disable/:plugin',
  624. array('action' => 'plugindisable'),
  625. array('plugin' => '[A-Za-z0-9_]+'));
  626. $m->connect('getfile/:filename',
  627. array('action' => 'getfile'),
  628. array('filename' => '[A-Za-z0-9._-]+'));
  629. // Common people-tag stuff
  630. $m->connect('peopletag/:tag', array('action' => 'peopletag',
  631. 'tag' => self::REGEX_TAG));
  632. $m->connect('selftag/:tag', array('action' => 'selftag',
  633. 'tag' => self::REGEX_TAG));
  634. $m->connect('main/addpeopletag', array('action' => 'addpeopletag'));
  635. $m->connect('main/removepeopletag', array('action' => 'removepeopletag'));
  636. $m->connect('main/profilecompletion', array('action' => 'profilecompletion'));
  637. $m->connect('main/peopletagautocomplete', array('action' => 'peopletagautocomplete'));
  638. // In the "root"
  639. if (common_config('singleuser', 'enabled')) {
  640. $nickname = User::singleUserNickname();
  641. foreach (array('subscriptions', 'subscribers',
  642. 'all', 'foaf', 'replies',
  643. 'microsummary') as $a) {
  644. $m->connect($a,
  645. array('action' => $a,
  646. 'nickname' => $nickname));
  647. }
  648. foreach (array('subscriptions', 'subscribers') as $a) {
  649. $m->connect($a.'/:tag',
  650. array('action' => $a,
  651. 'nickname' => $nickname),
  652. array('tag' => self::REGEX_TAG));
  653. }
  654. $m->connect('subscribers/pending',
  655. array('action' => 'subqueue',
  656. 'nickname' => $nickname));
  657. foreach (array('rss', 'groups') as $a) {
  658. $m->connect($a,
  659. array('action' => 'user'.$a,
  660. 'nickname' => $nickname));
  661. }
  662. foreach (array('all', 'replies') as $a) {
  663. $m->connect($a.'/rss',
  664. array('action' => $a.'rss',
  665. 'nickname' => $nickname));
  666. }
  667. $m->connect('avatar',
  668. array('action' => 'avatarbynickname',
  669. 'nickname' => $nickname));
  670. $m->connect('avatar/:size',
  671. array('action' => 'avatarbynickname',
  672. 'nickname' => $nickname),
  673. array('size' => '(|original|\d+)'));
  674. $m->connect('tag/:tag/rss',
  675. array('action' => 'userrss',
  676. 'nickname' => $nickname),
  677. array('tag' => self::REGEX_TAG));
  678. $m->connect('tag/:tag',
  679. array('action' => 'showstream',
  680. 'nickname' => $nickname),
  681. array('tag' => self::REGEX_TAG));
  682. $m->connect('rsd.xml',
  683. array('action' => 'rsd',
  684. 'nickname' => $nickname));
  685. $m->connect('',
  686. array('action' => 'startpage'));
  687. // peopletags
  688. $m->connect('peopletags',
  689. array('action' => 'peopletagsbyuser'));
  690. $m->connect('peopletags/private',
  691. array('action' => 'peopletagsbyuser',
  692. 'private' => 1));
  693. $m->connect('peopletags/public',
  694. array('action' => 'peopletagsbyuser',
  695. 'public' => 1));
  696. $m->connect('othertags',
  697. array('action' => 'peopletagsforuser'));
  698. $m->connect('peopletagsubscriptions',
  699. array('action' => 'peopletagsubscriptions'));
  700. $m->connect('all/:tag/subscribers',
  701. array('action' => 'peopletagsubscribers',
  702. 'tag' => self::REGEX_TAG));
  703. $m->connect('all/:tag/tagged',
  704. array('action' => 'peopletagged',
  705. 'tag' => self::REGEX_TAG));
  706. $m->connect('all/:tag/edit',
  707. array('action' => 'editpeopletag',
  708. 'tag' => self::REGEX_TAG));
  709. foreach(array('subscribe', 'unsubscribe') as $v) {
  710. $m->connect('peopletag/:id/'.$v,
  711. array('action' => $v.'peopletag',
  712. 'id' => '[0-9]{1,64}'));
  713. }
  714. $m->connect('user/:tagger_id/profiletag/:id/id',
  715. array('action' => 'profiletagbyid',
  716. 'tagger_id' => '[0-9]+',
  717. 'id' => '[0-9]+'));
  718. $m->connect('all/:tag',
  719. array('action' => 'showprofiletag',
  720. 'tag' => self::REGEX_TAG));
  721. foreach (array('subscriptions', 'subscribers') as $a) {
  722. $m->connect($a.'/:tag',
  723. array('action' => $a),
  724. array('tag' => self::REGEX_TAG));
  725. }
  726. }
  727. $m->connect('', array('action' => 'startpage'));
  728. $m->connect('main/public', array('action' => 'public'));
  729. $m->connect('main/all', array('action' => 'networkpublic'));
  730. $m->connect('rss', array('action' => 'publicrss'));
  731. $m->connect('featuredrss', array('action' => 'featuredrss'));
  732. $m->connect('featured/', array('action' => 'featured'));
  733. $m->connect('featured', array('action' => 'featured'));
  734. $m->connect('rsd.xml', array('action' => 'rsd'));
  735. foreach (array('subscriptions', 'subscribers',
  736. 'nudge', 'all', 'foaf', 'replies',
  737. 'inbox', 'outbox', 'microsummary') as $a) {
  738. $m->connect(':nickname/'.$a,
  739. array('action' => $a),
  740. array('nickname' => Nickname::DISPLAY_FMT));
  741. }
  742. $m->connect(':nickname/subscribers/pending',
  743. array('action' => 'subqueue'),
  744. array('nickname' => Nickname::DISPLAY_FMT));
  745. // people tags
  746. $m->connect(':nickname/peopletags',
  747. array('action' => 'peopletagsbyuser',
  748. 'nickname' => Nickname::DISPLAY_FMT));
  749. $m->connect(':nickname/peopletags/private',
  750. array('action' => 'peopletagsbyuser',
  751. 'nickname' => Nickname::DISPLAY_FMT,
  752. 'private' => 1));
  753. $m->connect(':nickname/peopletags/public',
  754. array('action' => 'peopletagsbyuser',
  755. 'nickname' => Nickname::DISPLAY_FMT,
  756. 'public' => 1));
  757. $m->connect(':nickname/othertags',
  758. array('action' => 'peopletagsforuser',
  759. 'nickname' => Nickname::DISPLAY_FMT));
  760. $m->connect(':nickname/peopletagsubscriptions',
  761. array('action' => 'peopletagsubscriptions',
  762. 'nickname' => Nickname::DISPLAY_FMT));
  763. $m->connect(':tagger/all/:tag/subscribers',
  764. array('action' => 'peopletagsubscribers',
  765. 'tagger' => Nickname::DISPLAY_FMT,
  766. 'tag' => self::REGEX_TAG));
  767. $m->connect(':tagger/all/:tag/tagged',
  768. array('action' => 'peopletagged',
  769. 'tagger' => Nickname::DISPLAY_FMT,
  770. 'tag' => self::REGEX_TAG));
  771. $m->connect(':tagger/all/:tag/edit',
  772. array('action' => 'editpeopletag',
  773. 'tagger' => Nickname::DISPLAY_FMT,
  774. 'tag' => self::REGEX_TAG));
  775. foreach(array('subscribe', 'unsubscribe') as $v) {
  776. $m->connect('peopletag/:id/'.$v,
  777. array('action' => $v.'peopletag',
  778. 'id' => '[0-9]{1,64}'));
  779. }
  780. $m->connect('user/:tagger_id/profiletag/:id/id',
  781. array('action' => 'profiletagbyid',
  782. 'tagger_id' => '[0-9]+',
  783. 'id' => '[0-9]+'));
  784. $m->connect(':tagger/all/:tag',
  785. array('action' => 'showprofiletag',
  786. 'tagger' => Nickname::DISPLAY_FMT,
  787. 'tag' => self::REGEX_TAG));
  788. foreach (array('subscriptions', 'subscribers') as $a) {
  789. $m->connect(':nickname/'.$a.'/:tag',
  790. array('action' => $a),
  791. array('tag' => self::REGEX_TAG,
  792. 'nickname' => Nickname::DISPLAY_FMT));
  793. }
  794. foreach (array('rss', 'groups') as $a) {
  795. $m->connect(':nickname/'.$a,
  796. array('action' => 'user'.$a),
  797. array('nickname' => Nickname::DISPLAY_FMT));
  798. }
  799. foreach (array('all', 'replies') as $a) {
  800. $m->connect(':nickname/'.$a.'/rss',
  801. array('action' => $a.'rss'),
  802. array('nickname' => Nickname::DISPLAY_FMT));
  803. }
  804. $m->connect(':nickname/avatar',
  805. array('action' => 'avatarbynickname'),
  806. array('nickname' => Nickname::DISPLAY_FMT));
  807. $m->connect(':nickname/avatar/:size',
  808. array('action' => 'avatarbynickname'),
  809. array('size' => '(|original|\d+)',
  810. 'nickname' => Nickname::DISPLAY_FMT));
  811. $m->connect(':nickname/tag/:tag/rss',
  812. array('action' => 'userrss'),
  813. array('nickname' => Nickname::DISPLAY_FMT),
  814. array('tag' => self::REGEX_TAG));
  815. $m->connect(':nickname/tag/:tag',
  816. array('action' => 'showstream'),
  817. array('nickname' => Nickname::DISPLAY_FMT),
  818. array('tag' => self::REGEX_TAG));
  819. $m->connect(':nickname/rsd.xml',
  820. array('action' => 'rsd'),
  821. array('nickname' => Nickname::DISPLAY_FMT));
  822. $m->connect(':nickname',
  823. array('action' => 'showstream'),
  824. array('nickname' => Nickname::DISPLAY_FMT));
  825. $m->connect(':nickname/',
  826. array('action' => 'showstream'),
  827. array('nickname' => Nickname::DISPLAY_FMT));
  828. // AtomPub API
  829. $m->connect('api/statusnet/app/service/:id.xml',
  830. array('action' => 'ApiAtomService'),
  831. array('id' => Nickname::DISPLAY_FMT));
  832. $m->connect('api/statusnet/app/service.xml',
  833. array('action' => 'ApiAtomService'));
  834. $m->connect('api/statusnet/app/subscriptions/:subscriber/:subscribed.atom',
  835. array('action' => 'AtomPubShowSubscription'),
  836. array('subscriber' => '[0-9]+',
  837. 'subscribed' => '[0-9]+'));
  838. $m->connect('api/statusnet/app/subscriptions/:subscriber.atom',
  839. array('action' => 'AtomPubSubscriptionFeed'),
  840. array('subscriber' => '[0-9]+'));
  841. $m->connect('api/statusnet/app/memberships/:profile/:group.atom',
  842. array('action' => 'AtomPubShowMembership'),
  843. array('profile' => '[0-9]+',
  844. 'group' => '[0-9]+'));
  845. $m->connect('api/statusnet/app/memberships/:profile.atom',
  846. array('action' => 'AtomPubMembershipFeed'),
  847. array('profile' => '[0-9]+'));
  848. // URL shortening
  849. $m->connect('url/:id',
  850. array('action' => 'redirecturl',
  851. 'id' => '[0-9]+'));
  852. // user stuff
  853. Event::handle('RouterInitialized', array($m));
  854. }
  855. return $m;
  856. }
  857. function map($path)
  858. {
  859. try {
  860. $match = $this->m->match($path);
  861. } catch (Exception $e) {
  862. common_log(LOG_ERR, "Problem getting route for $path - " .
  863. $e->getMessage());
  864. // TRANS: Client error on action trying to visit a non-existing page.
  865. $cac = new ClientErrorAction(_('Page not found.'), 404);
  866. $cac->showPage();
  867. }
  868. return $match;
  869. }
  870. function build($action, $args=null, $params=null, $fragment=null)
  871. {
  872. $action_arg = array('action' => $action);
  873. if ($args) {
  874. $args = array_merge($action_arg, $args);
  875. } else {
  876. $args = $action_arg;
  877. }
  878. $url = $this->m->generate($args, $params, $fragment);
  879. // Due to a bug in the Net_URL_Mapper code, the returned URL may
  880. // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
  881. // repair that here rather than modifying the upstream code...
  882. $qpos = strpos($url, '?');
  883. if ($qpos !== false) {
  884. $url = substr($url, 0, $qpos+1) .
  885. str_replace('?', '&', substr($url, $qpos+1));
  886. // @fixme this is a hacky workaround for http_build_query in the
  887. // lower-level code and bad configs that set the default separator
  888. // to &amp; instead of &. Encoded &s in parameters will not be
  889. // affected.
  890. $url = substr($url, 0, $qpos+1) .
  891. str_replace('&amp;', '&', substr($url, $qpos+1));
  892. }
  893. return $url;
  894. }
  895. }