LinkerTest.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. <?php
  2. /**
  3. * @group Database
  4. */
  5. class LinkerTest extends MediaWikiLangTestCase {
  6. /**
  7. * @dataProvider provideCasesForUserLink
  8. * @covers Linker::userLink
  9. */
  10. public function testUserLink( $expected, $userId, $userName, $altUserName = false, $msg = '' ) {
  11. $this->setMwGlobals( [
  12. 'wgArticlePath' => '/wiki/$1',
  13. ] );
  14. $this->assertEquals(
  15. $expected,
  16. Linker::userLink( $userId, $userName, $altUserName ),
  17. $msg
  18. );
  19. }
  20. public static function provideCasesForUserLink() {
  21. # Format:
  22. # - expected
  23. # - userid
  24. # - username
  25. # - optional altUserName
  26. # - optional message
  27. return [
  28. # ## ANONYMOUS USER ########################################
  29. [
  30. '<a href="/wiki/Special:Contributions/JohnDoe" '
  31. . 'class="mw-userlink mw-anonuserlink" '
  32. . 'title="Special:Contributions/JohnDoe"><bdi>JohnDoe</bdi></a>',
  33. 0, 'JohnDoe', false,
  34. ],
  35. [
  36. '<a href="/wiki/Special:Contributions/::1" '
  37. . 'class="mw-userlink mw-anonuserlink" '
  38. . 'title="Special:Contributions/::1"><bdi>::1</bdi></a>',
  39. 0, '::1', false,
  40. 'Anonymous with pretty IPv6'
  41. ],
  42. [
  43. '<a href="/wiki/Special:Contributions/0:0:0:0:0:0:0:1" '
  44. . 'class="mw-userlink mw-anonuserlink" '
  45. . 'title="Special:Contributions/0:0:0:0:0:0:0:1"><bdi>::1</bdi></a>',
  46. 0, '0:0:0:0:0:0:0:1', false,
  47. 'Anonymous with almost pretty IPv6'
  48. ],
  49. [
  50. '<a href="/wiki/Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001" '
  51. . 'class="mw-userlink mw-anonuserlink" '
  52. . 'title="Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001"><bdi>::1</bdi></a>',
  53. 0, '0000:0000:0000:0000:0000:0000:0000:0001', false,
  54. 'Anonymous with full IPv6'
  55. ],
  56. [
  57. '<a href="/wiki/Special:Contributions/::1" '
  58. . 'class="mw-userlink mw-anonuserlink" '
  59. . 'title="Special:Contributions/::1"><bdi>AlternativeUsername</bdi></a>',
  60. 0, '::1', 'AlternativeUsername',
  61. 'Anonymous with pretty IPv6 and an alternative username'
  62. ],
  63. # IPV4
  64. [
  65. '<a href="/wiki/Special:Contributions/127.0.0.1" '
  66. . 'class="mw-userlink mw-anonuserlink" '
  67. . 'title="Special:Contributions/127.0.0.1"><bdi>127.0.0.1</bdi></a>',
  68. 0, '127.0.0.1', false,
  69. 'Anonymous with IPv4'
  70. ],
  71. [
  72. '<a href="/wiki/Special:Contributions/127.0.0.1" '
  73. . 'class="mw-userlink mw-anonuserlink" '
  74. . 'title="Special:Contributions/127.0.0.1"><bdi>AlternativeUsername</bdi></a>',
  75. 0, '127.0.0.1', 'AlternativeUsername',
  76. 'Anonymous with IPv4 and an alternative username'
  77. ],
  78. # ## Regular user ##########################################
  79. # TODO!
  80. ];
  81. }
  82. /**
  83. * @dataProvider provideCasesForFormatComment
  84. * @covers Linker::formatComment
  85. * @covers Linker::formatAutocomments
  86. * @covers Linker::formatLinksInComment
  87. */
  88. public function testFormatComment(
  89. $expected, $comment, $title = false, $local = false, $wikiId = null
  90. ) {
  91. $conf = new SiteConfiguration();
  92. $conf->settings = [
  93. 'wgServer' => [
  94. 'enwiki' => '//en.example.org',
  95. 'dewiki' => '//de.example.org',
  96. ],
  97. 'wgArticlePath' => [
  98. 'enwiki' => '/w/$1',
  99. 'dewiki' => '/w/$1',
  100. ],
  101. ];
  102. $conf->suffixes = [ 'wiki' ];
  103. $this->setMwGlobals( [
  104. 'wgScript' => '/wiki/index.php',
  105. 'wgArticlePath' => '/wiki/$1',
  106. 'wgCapitalLinks' => true,
  107. 'wgConf' => $conf,
  108. ] );
  109. if ( $title === false ) {
  110. // We need a page title that exists
  111. $title = Title::newFromText( 'Special:BlankPage' );
  112. }
  113. $this->assertEquals(
  114. $expected,
  115. Linker::formatComment( $comment, $title, $local, $wikiId )
  116. );
  117. }
  118. public function provideCasesForFormatComment() {
  119. $wikiId = 'enwiki'; // $wgConf has a fake entry for this
  120. // phpcs:disable Generic.Files.LineLength
  121. return [
  122. // Linker::formatComment
  123. [
  124. 'a&lt;script&gt;b',
  125. 'a<script>b',
  126. ],
  127. [
  128. 'a—b',
  129. 'a&mdash;b',
  130. ],
  131. [
  132. "&#039;&#039;&#039;not bolded&#039;&#039;&#039;",
  133. "'''not bolded'''",
  134. ],
  135. [
  136. "try &lt;script&gt;evil&lt;/scipt&gt; things",
  137. "try <script>evil</scipt> things",
  138. ],
  139. // Linker::formatAutocomments
  140. [
  141. '<a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→</a>‎<span dir="auto"><span class="autocomment">autocomment</span></span>',
  142. "/* autocomment */",
  143. ],
  144. [
  145. '<a href="/wiki/Special:BlankPage#linkie.3F" title="Special:BlankPage">→</a>‎<span dir="auto"><span class="autocomment"><a href="/wiki/index.php?title=Linkie%3F&amp;action=edit&amp;redlink=1" class="new" title="Linkie? (page does not exist)">linkie?</a></span></span>',
  146. "/* [[linkie?]] */",
  147. ],
  148. [
  149. '<a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→</a>‎<span dir="auto"><span class="autocomment">autocomment: </span> post</span>',
  150. "/* autocomment */ post",
  151. ],
  152. [
  153. 'pre <a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→</a>‎<span dir="auto"><span class="autocomment">autocomment</span></span>',
  154. "pre /* autocomment */",
  155. ],
  156. [
  157. 'pre <a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→</a>‎<span dir="auto"><span class="autocomment">autocomment: </span> post</span>',
  158. "pre /* autocomment */ post",
  159. ],
  160. [
  161. '<a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→</a>‎<span dir="auto"><span class="autocomment">autocomment: </span> multiple? <a href="/wiki/Special:BlankPage#autocomment2" title="Special:BlankPage">→</a>‎<span dir="auto"><span class="autocomment">autocomment2: </span> </span></span>',
  162. "/* autocomment */ multiple? /* autocomment2 */ ",
  163. ],
  164. [
  165. '<a href="/wiki/Special:BlankPage#autocomment_containing_.2F.2A" title="Special:BlankPage">→</a>‎<span dir="auto"><span class="autocomment">autocomment containing /*: </span> T70361</span>',
  166. "/* autocomment containing /* */ T70361"
  167. ],
  168. [
  169. '<a href="/wiki/Special:BlankPage#autocomment_containing_.22quotes.22" title="Special:BlankPage">→</a>‎<span dir="auto"><span class="autocomment">autocomment containing &quot;quotes&quot;</span></span>',
  170. "/* autocomment containing \"quotes\" */"
  171. ],
  172. [
  173. '<a href="/wiki/Special:BlankPage#autocomment_containing_.3Cscript.3Etags.3C.2Fscript.3E" title="Special:BlankPage">→</a>‎<span dir="auto"><span class="autocomment">autocomment containing &lt;script&gt;tags&lt;/script&gt;</span></span>',
  174. "/* autocomment containing <script>tags</script> */"
  175. ],
  176. [
  177. '<a href="#autocomment">→</a>‎<span dir="auto"><span class="autocomment">autocomment</span></span>',
  178. "/* autocomment */",
  179. false, true
  180. ],
  181. [
  182. '‎<span dir="auto"><span class="autocomment">autocomment</span></span>',
  183. "/* autocomment */",
  184. null
  185. ],
  186. [
  187. '<a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→</a>‎<span dir="auto"><span class="autocomment">autocomment</span></span>',
  188. "/* autocomment */",
  189. false, false
  190. ],
  191. [
  192. '<a class="external" rel="nofollow" href="//en.example.org/w/Special:BlankPage#autocomment">→</a>‎<span dir="auto"><span class="autocomment">autocomment</span></span>',
  193. "/* autocomment */",
  194. false, false, $wikiId
  195. ],
  196. // Linker::formatLinksInComment
  197. [
  198. 'abc <a href="/wiki/index.php?title=Link&amp;action=edit&amp;redlink=1" class="new" title="Link (page does not exist)">link</a> def',
  199. "abc [[link]] def",
  200. ],
  201. [
  202. 'abc <a href="/wiki/index.php?title=Link&amp;action=edit&amp;redlink=1" class="new" title="Link (page does not exist)">text</a> def',
  203. "abc [[link|text]] def",
  204. ],
  205. [
  206. 'abc <a href="/wiki/Special:BlankPage" title="Special:BlankPage">Special:BlankPage</a> def',
  207. "abc [[Special:BlankPage|]] def",
  208. ],
  209. [
  210. 'abc <a href="/wiki/index.php?title=%C4%84%C5%9B%C5%BC&amp;action=edit&amp;redlink=1" class="new" title="Ąśż (page does not exist)">ąśż</a> def',
  211. "abc [[%C4%85%C5%9B%C5%BC]] def",
  212. ],
  213. [
  214. 'abc <a href="/wiki/Special:BlankPage#section" title="Special:BlankPage">#section</a> def',
  215. "abc [[#section]] def",
  216. ],
  217. [
  218. 'abc <a href="/wiki/index.php?title=/subpage&amp;action=edit&amp;redlink=1" class="new" title="/subpage (page does not exist)">/subpage</a> def',
  219. "abc [[/subpage]] def",
  220. ],
  221. [
  222. 'abc <a href="/wiki/index.php?title=%22evil!%22&amp;action=edit&amp;redlink=1" class="new" title="&quot;evil!&quot; (page does not exist)">&quot;evil!&quot;</a> def',
  223. "abc [[\"evil!\"]] def",
  224. ],
  225. [
  226. 'abc [[&lt;script&gt;very evil&lt;/script&gt;]] def',
  227. "abc [[<script>very evil</script>]] def",
  228. ],
  229. [
  230. 'abc [[|]] def',
  231. "abc [[|]] def",
  232. ],
  233. [
  234. 'abc <a href="/wiki/index.php?title=Link&amp;action=edit&amp;redlink=1" class="new" title="Link (page does not exist)">link</a> def',
  235. "abc [[link]] def",
  236. false, false
  237. ],
  238. [
  239. 'abc <a class="external" rel="nofollow" href="//en.example.org/w/Link">link</a> def',
  240. "abc [[link]] def",
  241. false, false, $wikiId
  242. ],
  243. ];
  244. // phpcs:enable
  245. }
  246. /**
  247. * @covers Linker::formatLinksInComment
  248. * @dataProvider provideCasesForFormatLinksInComment
  249. */
  250. public function testFormatLinksInComment( $expected, $input, $wiki ) {
  251. $conf = new SiteConfiguration();
  252. $conf->settings = [
  253. 'wgServer' => [
  254. 'enwiki' => '//en.example.org'
  255. ],
  256. 'wgArticlePath' => [
  257. 'enwiki' => '/w/$1',
  258. ],
  259. ];
  260. $conf->suffixes = [ 'wiki' ];
  261. $this->setMwGlobals( [
  262. 'wgScript' => '/wiki/index.php',
  263. 'wgArticlePath' => '/wiki/$1',
  264. 'wgCapitalLinks' => true,
  265. 'wgConf' => $conf,
  266. ] );
  267. $this->assertEquals(
  268. $expected,
  269. Linker::formatLinksInComment( $input, Title::newFromText( 'Special:BlankPage' ), false, $wiki )
  270. );
  271. }
  272. public static function provideCasesForFormatLinksInComment() {
  273. // phpcs:disable Generic.Files.LineLength
  274. return [
  275. [
  276. 'foo bar <a href="/wiki/Special:BlankPage" title="Special:BlankPage">Special:BlankPage</a>',
  277. 'foo bar [[Special:BlankPage]]',
  278. null,
  279. ],
  280. [
  281. '<a href="/wiki/Special:BlankPage" title="Special:BlankPage">Special:BlankPage</a>',
  282. '[[ :Special:BlankPage]]',
  283. null,
  284. ],
  285. [
  286. '<a class="external" rel="nofollow" href="//en.example.org/w/Foo%27bar">Foo\'bar</a>',
  287. "[[Foo'bar]]",
  288. 'enwiki',
  289. ],
  290. [
  291. 'foo bar <a class="external" rel="nofollow" href="//en.example.org/w/Special:BlankPage">Special:BlankPage</a>',
  292. 'foo bar [[Special:BlankPage]]',
  293. 'enwiki',
  294. ],
  295. [
  296. 'foo bar <a class="external" rel="nofollow" href="//en.example.org/w/File:Example">Image:Example</a>',
  297. 'foo bar [[Image:Example]]',
  298. 'enwiki',
  299. ],
  300. ];
  301. // phpcs:enable
  302. }
  303. public static function provideLinkBeginHook() {
  304. // phpcs:disable Generic.Files.LineLength
  305. return [
  306. // Modify $html
  307. [
  308. function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
  309. $html = 'foobar';
  310. },
  311. '<a href="/wiki/Special:BlankPage" title="Special:BlankPage">foobar</a>'
  312. ],
  313. // Modify $attribs
  314. [
  315. function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
  316. $attribs['bar'] = 'baz';
  317. },
  318. '<a href="/wiki/Special:BlankPage" title="Special:BlankPage" bar="baz">Special:BlankPage</a>'
  319. ],
  320. // Modify $query
  321. [
  322. function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
  323. $query['bar'] = 'baz';
  324. },
  325. '<a href="/w/index.php?title=Special:BlankPage&amp;bar=baz" title="Special:BlankPage">Special:BlankPage</a>'
  326. ],
  327. // Force HTTP $options
  328. [
  329. function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
  330. $options = [ 'http' ];
  331. },
  332. '<a href="http://example.org/wiki/Special:BlankPage" title="Special:BlankPage">Special:BlankPage</a>'
  333. ],
  334. // Force 'forcearticlepath' in $options
  335. [
  336. function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
  337. $options = [ 'forcearticlepath' ];
  338. $query['foo'] = 'bar';
  339. },
  340. '<a href="/wiki/Special:BlankPage?foo=bar" title="Special:BlankPage">Special:BlankPage</a>'
  341. ],
  342. // Abort early
  343. [
  344. function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
  345. $ret = 'foobar';
  346. return false;
  347. },
  348. 'foobar'
  349. ],
  350. ];
  351. // phpcs:enable
  352. }
  353. /**
  354. * @covers MediaWiki\Linker\LinkRenderer::runLegacyBeginHook
  355. * @dataProvider provideLinkBeginHook
  356. */
  357. public function testLinkBeginHook( $callback, $expected ) {
  358. $this->hideDeprecated( 'LinkBegin hook (used in hook-LinkBegin-closure)' );
  359. $this->setMwGlobals( [
  360. 'wgArticlePath' => '/wiki/$1',
  361. 'wgServer' => '//example.org',
  362. 'wgCanonicalServer' => 'http://example.org',
  363. 'wgScriptPath' => '/w',
  364. 'wgScript' => '/w/index.php',
  365. ] );
  366. $this->setMwGlobals( 'wgHooks', [ 'LinkBegin' => [ $callback ] ] );
  367. $title = SpecialPage::getTitleFor( 'Blankpage' );
  368. $out = Linker::link( $title );
  369. $this->assertEquals( $expected, $out );
  370. }
  371. public static function provideLinkEndHook() {
  372. return [
  373. // Override $html
  374. [
  375. function ( $dummy, $title, $options, &$html, &$attribs, &$ret ) {
  376. $html = 'foobar';
  377. },
  378. '<a href="/wiki/Special:BlankPage" title="Special:BlankPage">foobar</a>'
  379. ],
  380. // Modify $attribs
  381. [
  382. function ( $dummy, $title, $options, &$html, &$attribs, &$ret ) {
  383. $attribs['bar'] = 'baz';
  384. },
  385. '<a href="/wiki/Special:BlankPage" title="Special:BlankPage" bar="baz">Special:BlankPage</a>'
  386. ],
  387. // Fully override return value and abort hook
  388. [
  389. function ( $dummy, $title, $options, &$html, &$attribs, &$ret ) {
  390. $ret = 'blahblahblah';
  391. return false;
  392. },
  393. 'blahblahblah'
  394. ],
  395. ];
  396. }
  397. /**
  398. * @covers MediaWiki\Linker\LinkRenderer::buildAElement
  399. * @dataProvider provideLinkEndHook
  400. */
  401. public function testLinkEndHook( $callback, $expected ) {
  402. $this->hideDeprecated( 'LinkEnd hook (used in hook-LinkEnd-closure)' );
  403. $this->setMwGlobals( [
  404. 'wgArticlePath' => '/wiki/$1',
  405. ] );
  406. $this->setMwGlobals( 'wgHooks', [ 'LinkEnd' => [ $callback ] ] );
  407. $title = SpecialPage::getTitleFor( 'Blankpage' );
  408. $out = Linker::link( $title );
  409. $this->assertEquals( $expected, $out );
  410. }
  411. }