SpecialLinkSearch.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. <?php
  2. /**
  3. * Implements Special:LinkSearch
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. * @ingroup SpecialPage
  22. * @author Brion Vibber
  23. */
  24. use Wikimedia\Rdbms\IResultWrapper;
  25. use Wikimedia\Rdbms\IDatabase;
  26. /**
  27. * Special:LinkSearch to search the external-links table.
  28. * @ingroup SpecialPage
  29. */
  30. class SpecialLinkSearch extends QueryPage {
  31. /** @var array|bool */
  32. private $mungedQuery = false;
  33. /** @var string|null */
  34. private $mQuery;
  35. /** @var int|null */
  36. private $mNs;
  37. /** @var string|null */
  38. private $mProt;
  39. function setParams( $params ) {
  40. $this->mQuery = $params['query'];
  41. $this->mNs = $params['namespace'];
  42. $this->mProt = $params['protocol'];
  43. }
  44. function __construct( $name = 'LinkSearch' ) {
  45. parent::__construct( $name );
  46. // Since we don't control the constructor parameters, we can't inject services that way.
  47. // Instead, we initialize services in the execute() method, and allow them to be overridden
  48. // using the setServices() method.
  49. }
  50. function isCacheable() {
  51. return false;
  52. }
  53. public function execute( $par ) {
  54. $this->setHeaders();
  55. $this->outputHeader();
  56. $out = $this->getOutput();
  57. $out->allowClickjacking();
  58. $request = $this->getRequest();
  59. $target = $request->getVal( 'target', $par );
  60. $namespace = $request->getIntOrNull( 'namespace' );
  61. $protocols_list = [];
  62. foreach ( $this->getConfig()->get( 'UrlProtocols' ) as $prot ) {
  63. if ( $prot !== '//' ) {
  64. $protocols_list[] = $prot;
  65. }
  66. }
  67. $target2 = Parser::normalizeLinkUrl( $target );
  68. // Get protocol, default is http://
  69. $protocol = 'http://';
  70. $bits = wfParseUrl( $target );
  71. if ( isset( $bits['scheme'] ) && isset( $bits['delimiter'] ) ) {
  72. $protocol = $bits['scheme'] . $bits['delimiter'];
  73. // Make sure wfParseUrl() didn't make some well-intended correction in the
  74. // protocol
  75. if ( strcasecmp( $protocol, substr( $target, 0, strlen( $protocol ) ) ) === 0 ) {
  76. $target2 = substr( $target, strlen( $protocol ) );
  77. } else {
  78. // If it did, let LinkFilter::makeLikeArray() handle this
  79. $protocol = '';
  80. }
  81. }
  82. $out->addWikiMsg(
  83. 'linksearch-text',
  84. '<nowiki>' . $this->getLanguage()->commaList( $protocols_list ) . '</nowiki>',
  85. count( $protocols_list )
  86. );
  87. $fields = [
  88. 'target' => [
  89. 'type' => 'text',
  90. 'name' => 'target',
  91. 'id' => 'target',
  92. 'size' => 50,
  93. 'label-message' => 'linksearch-pat',
  94. 'default' => $target,
  95. 'dir' => 'ltr',
  96. ]
  97. ];
  98. if ( !$this->getConfig()->get( 'MiserMode' ) ) {
  99. $fields += [
  100. 'namespace' => [
  101. 'type' => 'namespaceselect',
  102. 'name' => 'namespace',
  103. 'label-message' => 'linksearch-ns',
  104. 'default' => $namespace,
  105. 'id' => 'namespace',
  106. 'all' => '',
  107. 'cssclass' => 'namespaceselector',
  108. ],
  109. ];
  110. }
  111. $hiddenFields = [
  112. 'title' => $this->getPageTitle()->getPrefixedDBkey(),
  113. ];
  114. $htmlForm = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
  115. $htmlForm->addHiddenFields( $hiddenFields );
  116. $htmlForm->setSubmitTextMsg( 'linksearch-ok' );
  117. $htmlForm->setWrapperLegendMsg( 'linksearch' );
  118. $htmlForm->setAction( wfScript() );
  119. $htmlForm->setMethod( 'get' );
  120. $htmlForm->prepareForm()->displayForm( false );
  121. $this->addHelpLink( 'Help:Linksearch' );
  122. if ( $target != '' ) {
  123. $this->setParams( [
  124. 'query' => $target2,
  125. 'namespace' => $namespace,
  126. 'protocol' => $protocol ] );
  127. parent::execute( $par );
  128. if ( $this->mungedQuery === false ) {
  129. $out->addWikiMsg( 'linksearch-error' );
  130. }
  131. }
  132. }
  133. /**
  134. * Disable RSS/Atom feeds
  135. * @return bool
  136. */
  137. function isSyndicated() {
  138. return false;
  139. }
  140. function linkParameters() {
  141. $params = [];
  142. $params['target'] = $this->mProt . $this->mQuery;
  143. if ( $this->mNs !== null && !$this->getConfig()->get( 'MiserMode' ) ) {
  144. $params['namespace'] = $this->mNs;
  145. }
  146. return $params;
  147. }
  148. public function getQueryInfo() {
  149. $dbr = wfGetDB( DB_REPLICA );
  150. if ( $this->mQuery === '*' && $this->mProt !== '' ) {
  151. $this->mungedQuery = [
  152. 'el_index_60' . $dbr->buildLike( $this->mProt, $dbr->anyString() ),
  153. ];
  154. } else {
  155. $this->mungedQuery = LinkFilter::getQueryConditions( $this->mQuery, [
  156. 'protocol' => $this->mProt,
  157. 'oneWildcard' => true,
  158. 'db' => $dbr
  159. ] );
  160. }
  161. if ( $this->mungedQuery === false ) {
  162. // Invalid query; return no results
  163. return [ 'tables' => 'page', 'fields' => 'page_id', 'conds' => '0=1' ];
  164. }
  165. $orderBy = [];
  166. if ( !isset( $this->mungedQuery['el_index_60'] ) ) {
  167. $orderBy[] = 'el_index_60';
  168. }
  169. $orderBy[] = 'el_id';
  170. $retval = [
  171. 'tables' => [ 'page', 'externallinks' ],
  172. 'fields' => [
  173. 'namespace' => 'page_namespace',
  174. 'title' => 'page_title',
  175. 'value' => 'el_index',
  176. 'url' => 'el_to'
  177. ],
  178. 'conds' => array_merge(
  179. [
  180. 'page_id = el_from',
  181. ],
  182. $this->mungedQuery
  183. ),
  184. 'options' => [ 'ORDER BY' => $orderBy ]
  185. ];
  186. if ( $this->mNs !== null && !$this->getConfig()->get( 'MiserMode' ) ) {
  187. $retval['conds']['page_namespace'] = $this->mNs;
  188. }
  189. return $retval;
  190. }
  191. /**
  192. * Pre-fill the link cache
  193. *
  194. * @param IDatabase $db
  195. * @param IResultWrapper $res
  196. */
  197. function preprocessResults( $db, $res ) {
  198. $this->executeLBFromResultWrapper( $res );
  199. }
  200. /**
  201. * @param Skin $skin
  202. * @param object $result Result row
  203. * @return string
  204. */
  205. function formatResult( $skin, $result ) {
  206. $title = new TitleValue( (int)$result->namespace, $result->title );
  207. $pageLink = $this->getLinkRenderer()->makeLink( $title );
  208. $url = $result->url;
  209. $urlLink = Linker::makeExternalLink( $url, $url );
  210. return $this->msg( 'linksearch-line' )->rawParams( $urlLink, $pageLink )->escaped();
  211. }
  212. /**
  213. * Override to squash the ORDER BY.
  214. * Not much point in descending order here.
  215. * @return array
  216. */
  217. function getOrderFields() {
  218. return [];
  219. }
  220. protected function getGroupName() {
  221. return 'redirects';
  222. }
  223. /**
  224. * enwiki complained about low limits on this special page
  225. *
  226. * @see T130058
  227. * @todo FIXME This special page should not use LIMIT for paging
  228. * @return int
  229. */
  230. protected function getMaxResults() {
  231. return max( parent::getMaxResults(), 60000 );
  232. }
  233. }