router.php 49 KB

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