3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
21 namespace MediaWiki\Specials
;
23 use MediaWiki\Cache\LinkBatchFactory
;
24 use MediaWiki\ExternalLinks\LinkFilter
;
25 use MediaWiki\HTMLForm\HTMLForm
;
26 use MediaWiki\MainConfigNames
;
27 use MediaWiki\Parser\Parser
;
28 use MediaWiki\SpecialPage\QueryPage
;
29 use MediaWiki\Title\TitleValue
;
30 use MediaWiki\Utils\UrlUtils
;
33 use Wikimedia\Rdbms\IConnectionProvider
;
34 use Wikimedia\Rdbms\IDatabase
;
35 use Wikimedia\Rdbms\IExpression
;
36 use Wikimedia\Rdbms\IResultWrapper
;
37 use Wikimedia\Rdbms\LikeValue
;
40 * Special:LinkSearch to search the external-links table.
42 * @ingroup SpecialPage
43 * @author Brooke Vibber
45 class SpecialLinkSearch
extends QueryPage
{
46 /** @var array|bool */
47 private $mungedQuery = false;
48 /** @var string|null */
52 /** @var string|null */
55 private UrlUtils
$urlUtils;
57 private function setParams( $params ) {
58 $this->mQuery
= $params['query'];
59 $this->mNs
= $params['namespace'];
60 $this->mProt
= $params['protocol'];
64 * @param IConnectionProvider $dbProvider
65 * @param LinkBatchFactory $linkBatchFactory
66 * @param UrlUtils $urlUtils
68 public function __construct(
69 IConnectionProvider
$dbProvider,
70 LinkBatchFactory
$linkBatchFactory,
73 parent
::__construct( 'LinkSearch' );
74 $this->setDatabaseProvider( $dbProvider );
75 $this->setLinkBatchFactory( $linkBatchFactory );
76 $this->urlUtils
= $urlUtils;
79 public function isCacheable() {
83 public function execute( $par ) {
85 $this->outputHeader();
87 $out = $this->getOutput();
88 $out->getMetadata()->setPreventClickjacking( false );
90 $request = $this->getRequest();
91 $target = $request->getVal( 'target', $par ??
'' );
92 $namespace = $request->getIntOrNull( 'namespace' );
95 foreach ( $this->getConfig()->get( MainConfigNames
::UrlProtocols
) as $prot ) {
96 if ( $prot !== '//' ) {
97 $protocols_list[] = $prot;
101 $target2 = Parser
::normalizeLinkUrl( $target );
103 $bits = $this->urlUtils
->parse( $target );
104 if ( isset( $bits['scheme'] ) && isset( $bits['delimiter'] ) ) {
105 $protocol = $bits['scheme'] . $bits['delimiter'];
106 // Make sure UrlUtils::parse() didn't make some well-intended correction in the protocol
107 if ( str_starts_with( strtolower( $target ), strtolower( $protocol ) ) ) {
108 $target2 = substr( $target, strlen( $protocol ) );
110 // If it did, let LinkFilter::makeLikeArray() handle this
117 '<nowiki>' . $this->getLanguage()->commaList( $protocols_list ) . '</nowiki>',
118 count( $protocols_list )
126 'label-message' => 'linksearch-pat',
127 'default' => $target,
131 if ( !$this->getConfig()->get( MainConfigNames
::MiserMode
) ) {
134 'type' => 'namespaceselect',
135 'name' => 'namespace',
136 'label-message' => 'linksearch-ns',
137 'default' => $namespace,
140 'cssclass' => 'namespaceselector',
144 $htmlForm = HTMLForm
::factory( 'ooui', $fields, $this->getContext() );
145 $htmlForm->setSubmitTextMsg( 'linksearch-ok' );
146 $htmlForm->setWrapperLegendMsg( 'linksearch' );
147 $htmlForm->setTitle( $this->getPageTitle() );
148 $htmlForm->setMethod( 'get' );
149 $htmlForm->prepareForm()->displayForm( false );
150 $this->addHelpLink( 'Help:Linksearch' );
152 if ( $target != '' ) {
155 'namespace' => $namespace,
156 'protocol' => $protocol ] );
157 parent
::execute( $par );
158 if ( $this->mungedQuery
=== false ) {
159 $out->addWikiMsg( 'linksearch-error' );
165 * Disable RSS/Atom feeds
168 public function isSyndicated() {
172 protected function linkParameters() {
174 $params['target'] = $this->mProt
. $this->mQuery
;
175 if ( $this->mNs
!== null && !$this->getConfig()->get( MainConfigNames
::MiserMode
) ) {
176 $params['namespace'] = $this->mNs
;
182 public function getQueryInfo() {
183 $dbr = $this->getDatabaseProvider()->getReplicaDatabase();
185 $field = 'el_to_domain_index';
187 'urldomain' => 'el_to_domain_index',
188 'urlpath' => 'el_to_path'
190 if ( $this->mQuery
=== '*' && $this->mProt
!== '' ) {
191 if ( $this->mProt
!== null ) {
192 $this->mungedQuery
= [
193 $dbr->expr( $field, IExpression
::LIKE
, new LikeValue( $this->mProt
, $dbr->anyString() ) ),
196 $this->mungedQuery
= [
197 $dbr->expr( $field, IExpression
::LIKE
, new LikeValue( 'http://', $dbr->anyString() ) )
198 ->or( $field, IExpression
::LIKE
, new LikeValue( 'https://', $dbr->anyString() ) ),
202 $this->mungedQuery
= LinkFilter
::getQueryConditions( $this->mQuery
, [
203 'protocol' => $this->mProt
,
204 'oneWildcard' => true,
207 if ( $this->mungedQuery
=== false ) {
208 // Invalid query; return no results
209 return [ 'tables' => 'page', 'fields' => 'page_id', 'conds' => '0=1' ];
212 $orderBy = [ 'el_id' ];
215 'tables' => [ 'page', 'externallinks' ],
216 'fields' => array_merge( [
217 'namespace' => 'page_namespace',
218 'title' => 'page_title',
220 'conds' => array_merge(
226 'options' => [ 'ORDER BY' => $orderBy ]
229 if ( $this->mNs
!== null && !$this->getConfig()->get( MainConfigNames
::MiserMode
) ) {
230 $retval['conds']['page_namespace'] = $this->mNs
;
237 * Pre-fill the link cache
239 * @param IDatabase $db
240 * @param IResultWrapper $res
242 public function preprocessResults( $db, $res ) {
243 $this->executeLBFromResultWrapper( $res );
248 * @param stdClass $result Result row
251 public function formatResult( $skin, $result ) {
252 $title = new TitleValue( (int)$result->namespace, $result->title
);
253 $pageLink = $this->getLinkRenderer()->makeLink( $title );
254 $url = LinkFilter
::reverseIndexes( $result->urldomain
) . $result->urlpath
;
256 $urlLink = $this->getLinkRenderer()->makeExternalLink( $url, $url, $this->getFullTitle() );
258 return $this->msg( 'linksearch-line' )->rawParams( $urlLink, $pageLink )->escaped();
262 * Override to squash the ORDER BY.
263 * Not much point in descending order here.
266 protected function getOrderFields() {
270 protected function getGroupName() {
275 * enwiki complained about low limits on this special page
278 * @todo FIXME This special page should not use LIMIT for paging
281 protected function getMaxResults() {
282 return max( parent
::getMaxResults(), 60000 );
286 /** @deprecated class alias since 1.41 */
287 class_alias( SpecialLinkSearch
::class, 'SpecialLinkSearch' );