router.php 47 KB

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