router.php 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138
  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. foreach (['' => 'attachment',
  185. '/view' => 'attachment_view',
  186. '/download' => 'attachment_download',
  187. '/thumbnail' => 'attachment_thumbnail'] as $postfix => $action) {
  188. foreach (['attachment' => '[0-9]+',
  189. 'filehash' => '[A-Za-z0-9._-]+'] as $type => $match) {
  190. $m->connect("attachment/:{$type}{$postfix}",
  191. ['action' => $action],
  192. [$type => $match]);
  193. }
  194. }
  195. $m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
  196. array('action' => 'newnotice'),
  197. array('replyto' => Nickname::DISPLAY_FMT),
  198. array('inreplyto' => '[0-9]+'));
  199. $m->connect('notice/new?replyto=:replyto',
  200. array('action' => 'newnotice'),
  201. array('replyto' => Nickname::DISPLAY_FMT));
  202. $m->connect('notice/new', array('action' => 'newnotice'));
  203. $m->connect('notice/:notice',
  204. array('action' => 'shownotice'),
  205. array('notice' => '[0-9]+'));
  206. $m->connect('notice/:notice/delete',
  207. array('action' => 'deletenotice'),
  208. array('notice' => '[0-9]+'));
  209. // conversation
  210. $m->connect('conversation/:id',
  211. array('action' => 'conversation'),
  212. array('id' => '[0-9]+'));
  213. $m->connect('user/:id',
  214. array('action' => 'userbyid'),
  215. array('id' => '[0-9]+'));
  216. $m->connect('tag/:tag/rss',
  217. array('action' => 'tagrss'),
  218. array('tag' => self::REGEX_TAG));
  219. $m->connect('tag/:tag',
  220. array('action' => 'tag'),
  221. array('tag' => self::REGEX_TAG));
  222. // groups
  223. $m->connect('group/new', array('action' => 'newgroup'));
  224. foreach (array('edit', 'join', 'leave', 'delete', 'cancel', 'approve') as $v) {
  225. $m->connect('group/:nickname/'.$v,
  226. array('action' => $v.'group'),
  227. array('nickname' => Nickname::DISPLAY_FMT));
  228. $m->connect('group/:id/id/'.$v,
  229. array('action' => $v.'group'),
  230. array('id' => '[0-9]+'));
  231. }
  232. foreach (array('members', 'logo', 'rss') as $n) {
  233. $m->connect('group/:nickname/'.$n,
  234. array('action' => 'group'.$n),
  235. array('nickname' => Nickname::DISPLAY_FMT));
  236. }
  237. $m->connect('group/:nickname/foaf',
  238. array('action' => 'foafgroup'),
  239. array('nickname' => Nickname::DISPLAY_FMT));
  240. $m->connect('group/:nickname/blocked',
  241. array('action' => 'blockedfromgroup'),
  242. array('nickname' => Nickname::DISPLAY_FMT));
  243. $m->connect('group/:nickname/makeadmin',
  244. array('action' => 'makeadmin'),
  245. array('nickname' => Nickname::DISPLAY_FMT));
  246. $m->connect('group/:nickname/members/pending',
  247. array('action' => 'groupqueue'),
  248. array('nickname' => Nickname::DISPLAY_FMT));
  249. $m->connect('group/:id/id',
  250. array('action' => 'groupbyid'),
  251. array('id' => '[0-9]+'));
  252. $m->connect('group/:nickname',
  253. array('action' => 'showgroup'),
  254. array('nickname' => Nickname::DISPLAY_FMT));
  255. $m->connect('group/:nickname/',
  256. array('action' => 'showgroup'),
  257. array('nickname' => Nickname::DISPLAY_FMT));
  258. $m->connect('group/', array('action' => 'groups'));
  259. $m->connect('group', array('action' => 'groups'));
  260. $m->connect('groups/', array('action' => 'groups'));
  261. $m->connect('groups', array('action' => 'groups'));
  262. // Twitter-compatible API
  263. // statuses API
  264. $m->connect('api',
  265. array('action' => 'Redirect',
  266. 'nextAction' => 'doc',
  267. 'args' => array('title' => 'api')));
  268. $m->connect('api/statuses/public_timeline.:format',
  269. array('action' => 'ApiTimelinePublic',
  270. 'format' => '(xml|json|rss|atom|as)'));
  271. // this is not part of the Twitter API. Also may require authentication depending on server config!
  272. $m->connect('api/statuses/networkpublic_timeline.:format',
  273. array('action' => 'ApiTimelineNetworkPublic',
  274. 'format' => '(xml|json|rss|atom|as)'));
  275. $m->connect('api/statuses/friends_timeline/:id.:format',
  276. array('action' => 'ApiTimelineFriends',
  277. 'id' => Nickname::INPUT_FMT,
  278. 'format' => '(xml|json|rss|atom|as)'));
  279. $m->connect('api/statuses/friends_timeline.:format',
  280. array('action' => 'ApiTimelineFriends',
  281. 'format' => '(xml|json|rss|atom|as)'));
  282. $m->connect('api/statuses/home_timeline/:id.:format',
  283. array('action' => 'ApiTimelineHome',
  284. 'id' => Nickname::INPUT_FMT,
  285. 'format' => '(xml|json|rss|atom|as)'));
  286. $m->connect('api/statuses/home_timeline.:format',
  287. array('action' => 'ApiTimelineHome',
  288. 'format' => '(xml|json|rss|atom|as)'));
  289. $m->connect('api/statuses/user_timeline/:id.:format',
  290. array('action' => 'ApiTimelineUser',
  291. 'id' => Nickname::INPUT_FMT,
  292. 'format' => '(xml|json|rss|atom|as)'));
  293. $m->connect('api/statuses/user_timeline.:format',
  294. array('action' => 'ApiTimelineUser',
  295. 'format' => '(xml|json|rss|atom|as)'));
  296. $m->connect('api/statuses/mentions/:id.:format',
  297. array('action' => 'ApiTimelineMentions',
  298. 'id' => Nickname::INPUT_FMT,
  299. 'format' => '(xml|json|rss|atom|as)'));
  300. $m->connect('api/statuses/mentions.:format',
  301. array('action' => 'ApiTimelineMentions',
  302. 'format' => '(xml|json|rss|atom|as)'));
  303. $m->connect('api/statuses/replies/:id.:format',
  304. array('action' => 'ApiTimelineMentions',
  305. 'id' => Nickname::INPUT_FMT,
  306. 'format' => '(xml|json|rss|atom|as)'));
  307. $m->connect('api/statuses/replies.:format',
  308. array('action' => 'ApiTimelineMentions',
  309. 'format' => '(xml|json|rss|atom|as)'));
  310. $m->connect('api/statuses/mentions_timeline/:id.:format',
  311. array('action' => 'ApiTimelineMentions',
  312. 'id' => Nickname::INPUT_FMT,
  313. 'format' => '(xml|json|rss|atom|as)'));
  314. $m->connect('api/statuses/mentions_timeline.:format',
  315. array('action' => 'ApiTimelineMentions',
  316. 'format' => '(xml|json|rss|atom|as)'));
  317. $m->connect('api/statuses/friends/:id.:format',
  318. array('action' => 'ApiUserFriends',
  319. 'id' => Nickname::INPUT_FMT,
  320. 'format' => '(xml|json)'));
  321. $m->connect('api/statuses/friends.:format',
  322. array('action' => 'ApiUserFriends',
  323. 'format' => '(xml|json)'));
  324. $m->connect('api/statuses/followers/:id.:format',
  325. array('action' => 'ApiUserFollowers',
  326. 'id' => Nickname::INPUT_FMT,
  327. 'format' => '(xml|json)'));
  328. $m->connect('api/statuses/followers.:format',
  329. array('action' => 'ApiUserFollowers',
  330. 'format' => '(xml|json)'));
  331. $m->connect('api/statuses/show/:id.:format',
  332. array('action' => 'ApiStatusesShow',
  333. 'id' => '[0-9]+',
  334. 'format' => '(xml|json|atom)'));
  335. $m->connect('api/statuses/show.:format',
  336. array('action' => 'ApiStatusesShow',
  337. 'format' => '(xml|json|atom)'));
  338. $m->connect('api/statuses/update.:format',
  339. array('action' => 'ApiStatusesUpdate',
  340. 'format' => '(xml|json|atom)'));
  341. $m->connect('api/statuses/destroy/:id.:format',
  342. array('action' => 'ApiStatusesDestroy',
  343. 'id' => '[0-9]+',
  344. 'format' => '(xml|json)'));
  345. $m->connect('api/statuses/destroy.:format',
  346. array('action' => 'ApiStatusesDestroy',
  347. 'format' => '(xml|json)'));
  348. // START qvitter API additions
  349. $m->connect('api/attachment/:id.:format',
  350. array('action' => 'ApiAttachment',
  351. 'id' => '[0-9]+',
  352. 'format' => '(xml|json)'));
  353. $m->connect('api/checkhub.:format',
  354. array('action' => 'ApiCheckHub',
  355. 'format' => '(xml|json)'));
  356. $m->connect('api/externalprofile/show.:format',
  357. array('action' => 'ApiExternalProfileShow',
  358. 'format' => '(xml|json)'));
  359. $m->connect('api/statusnet/groups/admins/:id.:format',
  360. array('action' => 'ApiGroupAdmins',
  361. 'id' => Nickname::INPUT_FMT,
  362. 'format' => '(xml|json)'));
  363. $m->connect('api/account/update_link_color.:format',
  364. array('action' => 'ApiAccountUpdateLinkColor',
  365. 'format' => '(xml|json)'));
  366. $m->connect('api/account/update_background_color.:format',
  367. array('action' => 'ApiAccountUpdateBackgroundColor',
  368. 'format' => '(xml|json)'));
  369. $m->connect('api/account/register.:format',
  370. array('action' => 'ApiAccountRegister',
  371. 'format' => '(xml|json)'));
  372. $m->connect('api/check_nickname.:format',
  373. array('action' => 'ApiCheckNickname',
  374. 'format' => '(xml|json)'));
  375. // END qvitter API additions
  376. // users
  377. $m->connect('api/users/show/:id.:format',
  378. array('action' => 'ApiUserShow',
  379. 'id' => Nickname::INPUT_FMT,
  380. 'format' => '(xml|json)'));
  381. $m->connect('api/users/show.:format',
  382. array('action' => 'ApiUserShow',
  383. 'format' => '(xml|json)'));
  384. $m->connect('api/users/profile_image/:screen_name.:format',
  385. array('action' => 'ApiUserProfileImage',
  386. 'screen_name' => Nickname::DISPLAY_FMT,
  387. 'format' => '(xml|json)'));
  388. // friendships
  389. $m->connect('api/friendships/show.:format',
  390. array('action' => 'ApiFriendshipsShow',
  391. 'format' => '(xml|json)'));
  392. $m->connect('api/friendships/exists.:format',
  393. array('action' => 'ApiFriendshipsExists',
  394. 'format' => '(xml|json)'));
  395. $m->connect('api/friendships/create/:id.:format',
  396. array('action' => 'ApiFriendshipsCreate',
  397. 'id' => Nickname::INPUT_FMT,
  398. 'format' => '(xml|json)'));
  399. $m->connect('api/friendships/create.:format',
  400. array('action' => 'ApiFriendshipsCreate',
  401. 'format' => '(xml|json)'));
  402. $m->connect('api/friendships/destroy/:id.:format',
  403. array('action' => 'ApiFriendshipsDestroy',
  404. 'id' => Nickname::INPUT_FMT,
  405. 'format' => '(xml|json)'));
  406. $m->connect('api/friendships/destroy.:format',
  407. array('action' => 'ApiFriendshipsDestroy',
  408. 'format' => '(xml|json)'));
  409. // Social graph
  410. $m->connect('api/friends/ids/:id.:format',
  411. array('action' => 'ApiUserFriends',
  412. 'ids_only' => true));
  413. $m->connect('api/followers/ids/:id.:format',
  414. array('action' => 'ApiUserFollowers',
  415. 'ids_only' => true));
  416. $m->connect('api/friends/ids.:format',
  417. array('action' => 'ApiUserFriends',
  418. 'ids_only' => true));
  419. $m->connect('api/followers/ids.:format',
  420. array('action' => 'ApiUserFollowers',
  421. 'ids_only' => true));
  422. // account
  423. $m->connect('api/account/verify_credentials.:format',
  424. array('action' => 'ApiAccountVerifyCredentials'));
  425. $m->connect('api/account/update_profile.:format',
  426. array('action' => 'ApiAccountUpdateProfile'));
  427. $m->connect('api/account/update_profile_image.:format',
  428. array('action' => 'ApiAccountUpdateProfileImage'));
  429. $m->connect('api/account/update_delivery_device.:format',
  430. array('action' => 'ApiAccountUpdateDeliveryDevice'));
  431. // special case where verify_credentials is called w/out a format
  432. $m->connect('api/account/verify_credentials',
  433. array('action' => 'ApiAccountVerifyCredentials'));
  434. $m->connect('api/account/rate_limit_status.:format',
  435. array('action' => 'ApiAccountRateLimitStatus'));
  436. // blocks
  437. $m->connect('api/blocks/create/:id.:format',
  438. array('action' => 'ApiBlockCreate',
  439. 'id' => Nickname::INPUT_FMT,
  440. 'format' => '(xml|json)'));
  441. $m->connect('api/blocks/create.:format',
  442. array('action' => 'ApiBlockCreate',
  443. 'format' => '(xml|json)'));
  444. $m->connect('api/blocks/destroy/:id.:format',
  445. array('action' => 'ApiBlockDestroy',
  446. 'id' => Nickname::INPUT_FMT,
  447. 'format' => '(xml|json)'));
  448. $m->connect('api/blocks/destroy.:format',
  449. array('action' => 'ApiBlockDestroy',
  450. 'format' => '(xml|json)'));
  451. // help
  452. $m->connect('api/help/test.:format',
  453. array('action' => 'ApiHelpTest',
  454. 'format' => '(xml|json)'));
  455. // statusnet
  456. $m->connect('api/statusnet/version.:format',
  457. array('action' => 'ApiGNUsocialVersion',
  458. 'format' => '(xml|json)'));
  459. $m->connect('api/statusnet/config.:format',
  460. array('action' => 'ApiGNUsocialConfig',
  461. 'format' => '(xml|json)'));
  462. // For our current software name, we provide "gnusocial" base action
  463. $m->connect('api/gnusocial/version.:format',
  464. array('action' => 'ApiGNUsocialVersion',
  465. 'format' => '(xml|json)'));
  466. $m->connect('api/gnusocial/config.:format',
  467. array('action' => 'ApiGNUsocialConfig',
  468. 'format' => '(xml|json)'));
  469. // Groups and tags are newer than 0.8.1 so no backward-compatibility
  470. // necessary
  471. // Groups
  472. //'list' has to be handled differently, as php will not allow a method to be named 'list'
  473. $m->connect('api/statusnet/groups/timeline/:id.:format',
  474. array('action' => 'ApiTimelineGroup',
  475. 'id' => Nickname::INPUT_FMT,
  476. 'format' => '(xml|json|rss|atom|as)'));
  477. $m->connect('api/statusnet/groups/show/:id.:format',
  478. array('action' => 'ApiGroupShow',
  479. 'id' => Nickname::INPUT_FMT,
  480. 'format' => '(xml|json)'));
  481. $m->connect('api/statusnet/groups/show.:format',
  482. array('action' => 'ApiGroupShow',
  483. 'format' => '(xml|json)'));
  484. $m->connect('api/statusnet/groups/join/:id.:format',
  485. array('action' => 'ApiGroupJoin',
  486. 'id' => Nickname::INPUT_FMT,
  487. 'format' => '(xml|json)'));
  488. $m->connect('api/statusnet/groups/join.:format',
  489. array('action' => 'ApiGroupJoin',
  490. 'format' => '(xml|json)'));
  491. $m->connect('api/statusnet/groups/leave/:id.:format',
  492. array('action' => 'ApiGroupLeave',
  493. 'format' => '(xml|json)'));
  494. $m->connect('api/statusnet/groups/leave.:format',
  495. array('action' => 'ApiGroupLeave',
  496. 'id' => Nickname::INPUT_FMT,
  497. 'format' => '(xml|json)'));
  498. $m->connect('api/statusnet/groups/is_member.:format',
  499. array('action' => 'ApiGroupIsMember',
  500. 'format' => '(xml|json)'));
  501. $m->connect('api/statusnet/groups/list/:id.:format',
  502. array('action' => 'ApiGroupList',
  503. 'id' => Nickname::INPUT_FMT,
  504. 'format' => '(xml|json|rss|atom)'));
  505. $m->connect('api/statusnet/groups/list.:format',
  506. array('action' => 'ApiGroupList',
  507. 'format' => '(xml|json|rss|atom)'));
  508. $m->connect('api/statusnet/groups/list_all.:format',
  509. array('action' => 'ApiGroupListAll',
  510. 'format' => '(xml|json|rss|atom)'));
  511. $m->connect('api/statusnet/groups/membership/:id.:format',
  512. array('action' => 'ApiGroupMembership',
  513. 'id' => Nickname::INPUT_FMT,
  514. 'format' => '(xml|json)'));
  515. $m->connect('api/statusnet/groups/membership.:format',
  516. array('action' => 'ApiGroupMembership',
  517. 'format' => '(xml|json)'));
  518. $m->connect('api/statusnet/groups/create.:format',
  519. array('action' => 'ApiGroupCreate',
  520. 'format' => '(xml|json)'));
  521. $m->connect('api/statusnet/groups/update/:id.:format',
  522. array('action' => 'ApiGroupProfileUpdate',
  523. 'id' => '[a-zA-Z0-9]+',
  524. 'format' => '(xml|json)'));
  525. $m->connect('api/statusnet/conversation/:id.:format',
  526. array('action' => 'apiconversation',
  527. 'id' => '[0-9]+',
  528. 'format' => '(xml|json|rss|atom|as)'));
  529. // Lists (people tags)
  530. $m->connect('api/lists/list.:format',
  531. array('action' => 'ApiListSubscriptions',
  532. 'format' => '(xml|json)'));
  533. $m->connect('api/lists/memberships.:format',
  534. array('action' => 'ApiListMemberships',
  535. 'format' => '(xml|json)'));
  536. $m->connect('api/:user/lists/memberships.:format',
  537. array('action' => 'ApiListMemberships',
  538. 'user' => '[a-zA-Z0-9]+',
  539. 'format' => '(xml|json)'));
  540. $m->connect('api/lists/subscriptions.:format',
  541. array('action' => 'ApiListSubscriptions',
  542. 'format' => '(xml|json)'));
  543. $m->connect('api/:user/lists/subscriptions.:format',
  544. array('action' => 'ApiListSubscriptions',
  545. 'user' => '[a-zA-Z0-9]+',
  546. 'format' => '(xml|json)'));
  547. $m->connect('api/lists.:format',
  548. array('action' => 'ApiLists',
  549. 'format' => '(xml|json)'));
  550. $m->connect('api/:user/lists/:id.:format',
  551. array('action' => 'ApiList',
  552. 'user' => '[a-zA-Z0-9]+',
  553. 'id' => '[a-zA-Z0-9]+',
  554. 'format' => '(xml|json)'));
  555. $m->connect('api/:user/lists.:format',
  556. array('action' => 'ApiLists',
  557. 'user' => '[a-zA-Z0-9]+',
  558. 'format' => '(xml|json)'));
  559. $m->connect('api/:user/lists/:id/statuses.:format',
  560. array('action' => 'ApiTimelineList',
  561. 'user' => '[a-zA-Z0-9]+',
  562. 'id' => '[a-zA-Z0-9]+',
  563. 'format' => '(xml|json|rss|atom)'));
  564. $m->connect('api/:user/:list_id/members/:id.:format',
  565. array('action' => 'ApiListMember',
  566. 'user' => '[a-zA-Z0-9]+',
  567. 'list_id' => '[a-zA-Z0-9]+',
  568. 'id' => '[a-zA-Z0-9]+',
  569. 'format' => '(xml|json)'));
  570. $m->connect('api/:user/:list_id/members.:format',
  571. array('action' => 'ApiListMembers',
  572. 'user' => '[a-zA-Z0-9]+',
  573. 'list_id' => '[a-zA-Z0-9]+',
  574. 'format' => '(xml|json)'));
  575. $m->connect('api/:user/:list_id/subscribers/:id.:format',
  576. array('action' => 'ApiListSubscriber',
  577. 'user' => '[a-zA-Z0-9]+',
  578. 'list_id' => '[a-zA-Z0-9]+',
  579. 'id' => '[a-zA-Z0-9]+',
  580. 'format' => '(xml|json)'));
  581. $m->connect('api/:user/:list_id/subscribers.:format',
  582. array('action' => 'ApiListSubscribers',
  583. 'user' => '[a-zA-Z0-9]+',
  584. 'list_id' => '[a-zA-Z0-9]+',
  585. 'format' => '(xml|json)'));
  586. // Tags
  587. $m->connect('api/statusnet/tags/timeline/:tag.:format',
  588. array('action' => 'ApiTimelineTag',
  589. 'tag' => self::REGEX_TAG,
  590. 'format' => '(xml|json|rss|atom|as)'));
  591. // media related
  592. $m->connect(
  593. 'api/statusnet/media/upload',
  594. array('action' => 'ApiMediaUpload')
  595. );
  596. $m->connect(
  597. 'api/statuses/update_with_media.json',
  598. array('action' => 'ApiMediaUpload')
  599. );
  600. // Twitter Media upload API v1.1
  601. $m->connect(
  602. 'api/media/upload.:format',
  603. array('action' => 'ApiMediaUpload',
  604. 'format' => '(xml|json)',
  605. )
  606. );
  607. // search
  608. $m->connect('api/search.atom', array('action' => 'ApiSearchAtom'));
  609. $m->connect('api/search.json', array('action' => 'ApiSearchJSON'));
  610. $m->connect('api/trends.json', array('action' => 'ApiTrends'));
  611. $m->connect('api/oauth/request_token',
  612. array('action' => 'ApiOAuthRequestToken'));
  613. $m->connect('api/oauth/access_token',
  614. array('action' => 'ApiOAuthAccessToken'));
  615. $m->connect('api/oauth/authorize',
  616. array('action' => 'ApiOAuthAuthorize'));
  617. // Admin
  618. $m->connect('panel/site', array('action' => 'siteadminpanel'));
  619. $m->connect('panel/user', array('action' => 'useradminpanel'));
  620. $m->connect('panel/access', array('action' => 'accessadminpanel'));
  621. $m->connect('panel/paths', array('action' => 'pathsadminpanel'));
  622. $m->connect('panel/sessions', array('action' => 'sessionsadminpanel'));
  623. $m->connect('panel/sitenotice', array('action' => 'sitenoticeadminpanel'));
  624. $m->connect('panel/license', array('action' => 'licenseadminpanel'));
  625. $m->connect('panel/plugins', array('action' => 'pluginsadminpanel'));
  626. $m->connect('panel/plugins/enable/:plugin',
  627. array('action' => 'pluginenable'),
  628. array('plugin' => '[A-Za-z0-9_]+'));
  629. $m->connect('panel/plugins/disable/:plugin',
  630. array('action' => 'plugindisable'),
  631. array('plugin' => '[A-Za-z0-9_]+'));
  632. // Common people-tag stuff
  633. $m->connect('peopletag/:tag', array('action' => 'peopletag',
  634. 'tag' => self::REGEX_TAG));
  635. $m->connect('selftag/:tag', array('action' => 'selftag',
  636. 'tag' => self::REGEX_TAG));
  637. $m->connect('main/addpeopletag', array('action' => 'addpeopletag'));
  638. $m->connect('main/removepeopletag', array('action' => 'removepeopletag'));
  639. $m->connect('main/profilecompletion', array('action' => 'profilecompletion'));
  640. $m->connect('main/peopletagautocomplete', array('action' => 'peopletagautocomplete'));
  641. // In the "root"
  642. if (common_config('singleuser', 'enabled')) {
  643. $nickname = User::singleUserNickname();
  644. foreach (array('subscriptions', 'subscribers',
  645. 'all', 'foaf', 'replies',
  646. ) as $a) {
  647. $m->connect($a,
  648. array('action' => $a,
  649. 'nickname' => $nickname));
  650. }
  651. foreach (array('subscriptions', 'subscribers') as $a) {
  652. $m->connect($a.'/:tag',
  653. array('action' => $a,
  654. 'nickname' => $nickname),
  655. array('tag' => self::REGEX_TAG));
  656. }
  657. $m->connect('subscribers/pending',
  658. array('action' => 'subqueue',
  659. 'nickname' => $nickname));
  660. foreach (array('rss', 'groups') as $a) {
  661. $m->connect($a,
  662. array('action' => 'user'.$a,
  663. 'nickname' => $nickname));
  664. }
  665. foreach (array('all', 'replies') as $a) {
  666. $m->connect($a.'/rss',
  667. array('action' => $a.'rss',
  668. 'nickname' => $nickname));
  669. }
  670. $m->connect('avatar',
  671. array('action' => 'avatarbynickname',
  672. 'nickname' => $nickname));
  673. $m->connect('avatar/:size',
  674. array('action' => 'avatarbynickname',
  675. 'nickname' => $nickname),
  676. array('size' => '(|original|\d+)'));
  677. $m->connect('tag/:tag/rss',
  678. array('action' => 'userrss',
  679. 'nickname' => $nickname),
  680. array('tag' => self::REGEX_TAG));
  681. $m->connect('tag/:tag',
  682. array('action' => 'showstream',
  683. 'nickname' => $nickname),
  684. array('tag' => self::REGEX_TAG));
  685. $m->connect('rsd.xml',
  686. array('action' => 'rsd',
  687. 'nickname' => $nickname));
  688. // peopletags
  689. $m->connect('peopletags',
  690. array('action' => 'peopletagsbyuser'));
  691. $m->connect('peopletags/private',
  692. array('action' => 'peopletagsbyuser',
  693. 'private' => 1));
  694. $m->connect('peopletags/public',
  695. array('action' => 'peopletagsbyuser',
  696. 'public' => 1));
  697. $m->connect('othertags',
  698. array('action' => 'peopletagsforuser'));
  699. $m->connect('peopletagsubscriptions',
  700. array('action' => 'peopletagsubscriptions'));
  701. $m->connect('all/:tag/subscribers',
  702. array('action' => 'peopletagsubscribers',
  703. 'tag' => self::REGEX_TAG));
  704. $m->connect('all/:tag/tagged',
  705. array('action' => 'peopletagged',
  706. 'tag' => self::REGEX_TAG));
  707. $m->connect('all/:tag/edit',
  708. array('action' => 'editpeopletag',
  709. 'tag' => self::REGEX_TAG));
  710. foreach(array('subscribe', 'unsubscribe') as $v) {
  711. $m->connect('peopletag/:id/'.$v,
  712. array('action' => $v.'peopletag',
  713. 'id' => '[0-9]{1,64}'));
  714. }
  715. $m->connect('user/:tagger_id/profiletag/:id/id',
  716. array('action' => 'profiletagbyid',
  717. 'tagger_id' => '[0-9]+',
  718. 'id' => '[0-9]+'));
  719. $m->connect('all/:tag',
  720. array('action' => 'showprofiletag',
  721. 'tagger' => $nickname,
  722. 'tag' => self::REGEX_TAG));
  723. foreach (array('subscriptions', 'subscribers') as $a) {
  724. $m->connect($a.'/:tag',
  725. array('action' => $a),
  726. array('tag' => self::REGEX_TAG));
  727. }
  728. }
  729. $m->connect('rss', array('action' => 'publicrss'));
  730. $m->connect('featuredrss', array('action' => 'featuredrss'));
  731. $m->connect('featured/', array('action' => 'featured'));
  732. $m->connect('featured', array('action' => 'featured'));
  733. $m->connect('rsd.xml', array('action' => 'rsd'));
  734. foreach (array('subscriptions', 'subscribers',
  735. 'nudge', 'all', 'foaf', 'replies',
  736. 'inbox', 'outbox') as $a) {
  737. $m->connect(':nickname/'.$a,
  738. array('action' => $a),
  739. array('nickname' => Nickname::DISPLAY_FMT));
  740. }
  741. $m->connect(':nickname/subscribers/pending',
  742. array('action' => 'subqueue'),
  743. array('nickname' => Nickname::DISPLAY_FMT));
  744. // some targeted RSS 1.0 actions (extends TargetedRss10Action)
  745. foreach (array('all', 'replies') as $a) {
  746. $m->connect(':nickname/'.$a.'/rss',
  747. array('action' => $a.'rss'),
  748. array('nickname' => Nickname::DISPLAY_FMT));
  749. }
  750. // people tags
  751. $m->connect(':nickname/peopletags',
  752. array('action' => 'peopletagsbyuser',
  753. 'nickname' => Nickname::DISPLAY_FMT));
  754. $m->connect(':nickname/peopletags/private',
  755. array('action' => 'peopletagsbyuser',
  756. 'nickname' => Nickname::DISPLAY_FMT,
  757. 'private' => 1));
  758. $m->connect(':nickname/peopletags/public',
  759. array('action' => 'peopletagsbyuser',
  760. 'nickname' => Nickname::DISPLAY_FMT,
  761. 'public' => 1));
  762. $m->connect(':nickname/othertags',
  763. array('action' => 'peopletagsforuser',
  764. 'nickname' => Nickname::DISPLAY_FMT));
  765. $m->connect(':nickname/peopletagsubscriptions',
  766. array('action' => 'peopletagsubscriptions',
  767. 'nickname' => Nickname::DISPLAY_FMT));
  768. $m->connect(':tagger/all/:tag/subscribers',
  769. array('action' => 'peopletagsubscribers',
  770. 'tagger' => Nickname::DISPLAY_FMT,
  771. 'tag' => self::REGEX_TAG));
  772. $m->connect(':tagger/all/:tag/tagged',
  773. array('action' => 'peopletagged',
  774. 'tagger' => Nickname::DISPLAY_FMT,
  775. 'tag' => self::REGEX_TAG));
  776. $m->connect(':tagger/all/:tag/edit',
  777. array('action' => 'editpeopletag',
  778. 'tagger' => Nickname::DISPLAY_FMT,
  779. 'tag' => self::REGEX_TAG));
  780. foreach(array('subscribe', 'unsubscribe') as $v) {
  781. $m->connect('peopletag/:id/'.$v,
  782. array('action' => $v.'peopletag',
  783. 'id' => '[0-9]{1,64}'));
  784. }
  785. $m->connect('user/:tagger_id/profiletag/:id/id',
  786. array('action' => 'profiletagbyid',
  787. 'tagger_id' => '[0-9]+',
  788. 'id' => '[0-9]+'));
  789. $m->connect(':nickname/all/:tag',
  790. array('action' => 'showprofiletag'),
  791. array('nickname' => Nickname::DISPLAY_FMT,
  792. 'tag' => self::REGEX_TAG));
  793. foreach (array('subscriptions', 'subscribers') as $a) {
  794. $m->connect(':nickname/'.$a.'/:tag',
  795. array('action' => $a),
  796. array('tag' => self::REGEX_TAG,
  797. 'nickname' => Nickname::DISPLAY_FMT));
  798. }
  799. foreach (array('rss', 'groups') as $a) {
  800. $m->connect(':nickname/'.$a,
  801. array('action' => 'user'.$a),
  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. return $this->m->match($path);
  861. } catch (NoRouteMapException $e) {
  862. common_debug($e->getMessage());
  863. // TRANS: Client error on action trying to visit a non-existing page.
  864. throw new ClientException(_('Page not found.'), 404);
  865. }
  866. }
  867. function build($action, $args=null, $params=null, $fragment=null)
  868. {
  869. $action_arg = array('action' => $action);
  870. if ($args) {
  871. $args = array_merge($action_arg, $args);
  872. } else {
  873. $args = $action_arg;
  874. }
  875. $url = $this->m->generate($args, $params, $fragment);
  876. // Due to a bug in the Net_URL_Mapper code, the returned URL may
  877. // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
  878. // repair that here rather than modifying the upstream code...
  879. $qpos = strpos($url, '?');
  880. if ($qpos !== false) {
  881. $url = substr($url, 0, $qpos+1) .
  882. str_replace('?', '&', substr($url, $qpos+1));
  883. // @fixme this is a hacky workaround for http_build_query in the
  884. // lower-level code and bad configs that set the default separator
  885. // to &amp; instead of &. Encoded &s in parameters will not be
  886. // affected.
  887. $url = substr($url, 0, $qpos+1) .
  888. str_replace('&amp;', '&', substr($url, $qpos+1));
  889. }
  890. return $url;
  891. }
  892. }