SpecialLinkSearch.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. <?php
  2. /**
  3. * @file
  4. * @ingroup SpecialPage
  5. *
  6. * @author Brion Vibber
  7. * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
  8. */
  9. /**
  10. * Special:LinkSearch to search the external-links table.
  11. * @ingroup SpecialPage
  12. */
  13. function wfSpecialLinkSearch( $par ) {
  14. list( $limit, $offset ) = wfCheckLimits();
  15. global $wgOut, $wgRequest, $wgUrlProtocols, $wgMiserMode, $wgLang;
  16. $target = $GLOBALS['wgRequest']->getVal( 'target', $par );
  17. $namespace = $GLOBALS['wgRequest']->getIntorNull( 'namespace', null );
  18. $protocols_list[] = '';
  19. foreach( $wgUrlProtocols as $prot ) {
  20. $protocols_list[] = $prot;
  21. }
  22. $target2 = $target;
  23. $protocol = '';
  24. $pr_sl = strpos($target2, '//' );
  25. $pr_cl = strpos($target2, ':' );
  26. if ( $pr_sl ) {
  27. // For protocols with '//'
  28. $protocol = substr( $target2, 0 , $pr_sl+2 );
  29. $target2 = substr( $target2, $pr_sl+2 );
  30. } elseif ( !$pr_sl && $pr_cl ) {
  31. // For protocols without '//' like 'mailto:'
  32. $protocol = substr( $target2, 0 , $pr_cl+1 );
  33. $target2 = substr( $target2, $pr_cl+1 );
  34. } elseif ( $protocol == '' && $target2 != '' ) {
  35. // default
  36. $protocol = 'http://';
  37. }
  38. if ( !in_array( $protocol, $protocols_list ) ) {
  39. // unsupported protocol, show original search request
  40. $target2 = $target;
  41. $protocol = '';
  42. }
  43. $self = Title::makeTitle( NS_SPECIAL, 'Linksearch' );
  44. $wgOut->addWikiText( wfMsg( 'linksearch-text', '<nowiki>' . $wgLang->commaList( $wgUrlProtocols) . '</nowiki>' ) );
  45. $s = Xml::openElement( 'form', array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $GLOBALS['wgScript'] ) ) .
  46. Xml::hidden( 'title', $self->getPrefixedDbKey() ) .
  47. '<fieldset>' .
  48. Xml::element( 'legend', array(), wfMsg( 'linksearch' ) ) .
  49. Xml::inputLabel( wfMsg( 'linksearch-pat' ), 'target', 'target', 50, $target ) . ' ';
  50. if ( !$wgMiserMode ) {
  51. $s .= Xml::label( wfMsg( 'linksearch-ns' ), 'namespace' ) . ' ' .
  52. XML::namespaceSelector( $namespace, '' );
  53. }
  54. $s .= Xml::submitButton( wfMsg( 'linksearch-ok' ) ) .
  55. '</fieldset>' .
  56. Xml::closeElement( 'form' );
  57. $wgOut->addHTML( $s );
  58. if( $target != '' ) {
  59. $searcher = new LinkSearchPage;
  60. $searcher->setParams( array(
  61. 'query' => $target2,
  62. 'namespace' => $namespace,
  63. 'protocol' => $protocol ) );
  64. $searcher->doQuery( $offset, $limit );
  65. }
  66. }
  67. class LinkSearchPage extends QueryPage {
  68. function setParams( $params ) {
  69. $this->mQuery = $params['query'];
  70. $this->mNs = $params['namespace'];
  71. $this->mProt = $params['protocol'];
  72. }
  73. function getName() {
  74. return 'LinkSearch';
  75. }
  76. /**
  77. * Disable RSS/Atom feeds
  78. */
  79. function isSyndicated() {
  80. return false;
  81. }
  82. /**
  83. * Return an appropriately formatted LIKE query and the clause
  84. */
  85. static function mungeQuery( $query , $prot ) {
  86. $field = 'el_index';
  87. $rv = LinkFilter::makeLike( $query , $prot );
  88. if ($rv === false) {
  89. //makeLike doesn't handle wildcard in IP, so we'll have to munge here.
  90. if (preg_match('/^(:?[0-9]{1,3}\.)+\*\s*$|^(:?[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]*\*\s*$/', $query)) {
  91. $rv = $prot . rtrim($query, " \t*") . '%';
  92. $field = 'el_to';
  93. }
  94. }
  95. return array( $rv, $field );
  96. }
  97. function linkParameters() {
  98. global $wgMiserMode;
  99. $params = array();
  100. $params['target'] = $this->mProt . $this->mQuery;
  101. if( isset( $this->mNs ) && !$wgMiserMode ) {
  102. $params['namespace'] = $this->mNs;
  103. }
  104. return $params;
  105. }
  106. function getSQL() {
  107. global $wgMiserMode;
  108. $dbr = wfGetDB( DB_SLAVE );
  109. $page = $dbr->tableName( 'page' );
  110. $externallinks = $dbr->tableName( 'externallinks' );
  111. /* strip everything past first wildcard, so that index-based-only lookup would be done */
  112. list( $munged, $clause ) = self::mungeQuery( $this->mQuery, $this->mProt );
  113. $stripped = substr($munged,0,strpos($munged,'%')+1);
  114. $encSearch = $dbr->addQuotes( $stripped );
  115. $encSQL = '';
  116. if ( isset ($this->mNs) && !$wgMiserMode )
  117. $encSQL = 'AND page_namespace=' . $dbr->addQuotes( $this->mNs );
  118. $use_index = $dbr->useIndexClause( $clause );
  119. return
  120. "SELECT
  121. page_namespace AS namespace,
  122. page_title AS title,
  123. el_index AS value,
  124. el_to AS url
  125. FROM
  126. $page,
  127. $externallinks $use_index
  128. WHERE
  129. page_id=el_from
  130. AND $clause LIKE $encSearch
  131. $encSQL";
  132. }
  133. function formatResult( $skin, $result ) {
  134. $title = Title::makeTitle( $result->namespace, $result->title );
  135. $url = $result->url;
  136. $pageLink = $skin->makeKnownLinkObj( $title );
  137. $urlLink = $skin->makeExternalLink( $url, $url );
  138. return wfMsgHtml( 'linksearch-line', $urlLink, $pageLink );
  139. }
  140. /**
  141. * Override to check query validity.
  142. */
  143. function doQuery( $offset, $limit, $shownavigation=true ) {
  144. global $wgOut;
  145. list( $this->mMungedQuery, $clause ) = LinkSearchPage::mungeQuery( $this->mQuery, $this->mProt );
  146. if( $this->mMungedQuery === false ) {
  147. $wgOut->addWikiText( wfMsg( 'linksearch-error' ) );
  148. } else {
  149. // For debugging
  150. // Generates invalid xhtml with patterns that contain --
  151. //$wgOut->addHTML( "\n<!-- " . htmlspecialchars( $this->mMungedQuery ) . " -->\n" );
  152. parent::doQuery( $offset, $limit, $shownavigation );
  153. }
  154. }
  155. /**
  156. * Override to squash the ORDER BY.
  157. * We do a truncated index search, so the optimizer won't trust
  158. * it as good enough for optimizing sort. The implicit ordering
  159. * from the scan will usually do well enough for our needs.
  160. */
  161. function getOrder() {
  162. return '';
  163. }
  164. }