MediaWiki master
SpecialLinkSearch.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Specials;
8
20use stdClass;
26
35 private $mungedQuery = false;
37 private $mQuery;
39 private $mNs;
41 private $mProt;
42
43 private function setParams( array $params ) {
44 $this->mQuery = $params['query'];
45 $this->mNs = $params['namespace'];
46 $this->mProt = $params['protocol'];
47 }
48
49 public function __construct(
50 IConnectionProvider $dbProvider,
51 LinkBatchFactory $linkBatchFactory,
52 private readonly UrlUtils $urlUtils,
53 ) {
54 parent::__construct( 'LinkSearch' );
55 $this->setDatabaseProvider( $dbProvider );
56 $this->setLinkBatchFactory( $linkBatchFactory );
57 }
58
60 public function isCacheable() {
61 return false;
62 }
63
65 public function execute( $par ) {
66 $this->setHeaders();
67 $this->outputHeader();
68
69 $out = $this->getOutput();
70 $out->getMetadata()->setPreventClickjacking( false );
71
72 $request = $this->getRequest();
73 $target = $request->getVal( 'target', $par ?? '' );
74 $namespace = $request->getIntOrNull( 'namespace' );
75
76 $protocols_list = [];
77 foreach ( $this->getConfig()->get( MainConfigNames::UrlProtocols ) as $prot ) {
78 if ( $prot !== '//' ) {
79 $protocols_list[] = $prot;
80 }
81 }
82
83 $target2 = Parser::normalizeLinkUrl( $target );
84 $protocol = null;
85 $bits = $this->urlUtils->parse( $target );
86 if ( isset( $bits['scheme'] ) && isset( $bits['delimiter'] ) ) {
87 $protocol = $bits['scheme'] . $bits['delimiter'];
88 // Make sure UrlUtils::parse() didn't make some well-intended correction in the protocol
89 if ( str_starts_with( strtolower( $target ), strtolower( $protocol ) ) ) {
90 $target2 = substr( $target, strlen( $protocol ) );
91 } else {
92 // If it did, let LinkFilter::makeLikeArray() handle this
93 $protocol = '';
94 }
95 }
96
97 $out->addWikiMsg(
98 'linksearch-text',
99 '<nowiki>' . $this->getLanguage()->commaList( $protocols_list ) . '</nowiki>',
100 count( $protocols_list )
101 );
102 $fields = [
103 'target' => [
104 'type' => 'text',
105 'name' => 'target',
106 'id' => 'target',
107 'size' => 50,
108 'label-message' => 'linksearch-pat',
109 'default' => $target,
110 'dir' => 'ltr',
111 ]
112 ];
113 if ( !$this->getConfig()->get( MainConfigNames::MiserMode ) ) {
114 $fields += [
115 'namespace' => [
116 'type' => 'namespaceselect',
117 'name' => 'namespace',
118 'label-message' => 'linksearch-ns',
119 'default' => $namespace,
120 'id' => 'namespace',
121 'all' => '',
122 'cssclass' => 'namespaceselector',
123 ],
124 ];
125 }
126 $htmlForm = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
127 $htmlForm->setSubmitTextMsg( 'linksearch-ok' );
128 $htmlForm->setWrapperLegendMsg( 'linksearch' );
129 $htmlForm->setTitle( $this->getPageTitle() );
130 $htmlForm->setMethod( 'get' );
131 $htmlForm->prepareForm()->displayForm( false );
132 $this->addHelpLink( 'Help:Linksearch' );
133
134 if ( $target != '' ) {
135 $this->setParams( [
136 'query' => $target2,
137 'namespace' => $namespace,
138 'protocol' => $protocol ] );
139 parent::execute( $par );
140 if ( $this->mungedQuery === false ) {
141 $out->addWikiMsg( 'linksearch-error' );
142 }
143 }
144 $ignoredDomains = $this->getConfig()->get( MainConfigNames::ExternalLinksIgnoreDomains );
145 if ( $ignoredDomains ) {
146 $out->addWikiMsg(
147 'linksearch-text-ignored-domains',
149 array_map( static fn ( $domain ) => "<code>$domain</code>", $ignoredDomains )
150 ),
151 count( $ignoredDomains )
152 );
153 }
154 }
155
160 public function isSyndicated() {
161 return false;
162 }
163
165 protected function linkParameters() {
166 $params = [];
167 $params['target'] = $this->mProt . $this->mQuery;
168 if ( $this->mNs !== null && !$this->getConfig()->get( MainConfigNames::MiserMode ) ) {
169 $params['namespace'] = $this->mNs;
170 }
171
172 return $params;
173 }
174
176 public function getQueryInfo() {
177 $dbr = $this->getDatabaseProvider()->getReplicaDatabase( ExternalLinksTable::VIRTUAL_DOMAIN );
178
179 $field = 'el_to_domain_index';
180 $extraFields = [
181 'urldomain' => 'el_to_domain_index',
182 'urlpath' => 'el_to_path'
183 ];
184 if ( $this->mQuery === '*' && $this->mProt !== '' ) {
185 if ( $this->mProt !== null ) {
186 $this->mungedQuery = [
187 $dbr->expr( $field, IExpression::LIKE, new LikeValue( $this->mProt, $dbr->anyString() ) ),
188 ];
189 } else {
190 $this->mungedQuery = [
191 $dbr->expr( $field, IExpression::LIKE, new LikeValue( 'http://', $dbr->anyString() ) )
192 ->or( $field, IExpression::LIKE, new LikeValue( 'https://', $dbr->anyString() ) ),
193 ];
194 }
195 } else {
196 $this->mungedQuery = LinkFilter::getQueryConditions( $this->mQuery, [
197 'protocol' => $this->mProt,
198 'oneWildcard' => true,
199 'db' => $dbr
200 ] );
201 if ( $this->mungedQuery === false ) {
202 // Invalid query; return no results
203 return [ 'tables' => 'page', 'fields' => 'page_id', 'conds' => '0=1' ];
204 }
205 }
206 $orderBy = [ 'el_id' ];
207
208 $retval = [
209 'tables' => [ 'page', 'externallinks' ],
210 'fields' => [
211 'namespace' => 'page_namespace',
212 'title' => 'page_title',
213 ...$extraFields,
214 ],
215 'conds' => [
216 'page_id = el_from',
217 ...$this->mungedQuery,
218 ],
219 'options' => [ 'ORDER BY' => $orderBy ]
220 ];
221
222 if ( $this->mNs !== null && !$this->getConfig()->get( MainConfigNames::MiserMode ) ) {
223 $retval['conds']['page_namespace'] = $this->mNs;
224 }
225
226 return $retval;
227 }
228
235 public function preprocessResults( $db, $res ) {
236 $this->executeLBFromResultWrapper( $res );
237 }
238
244 public function formatResult( $skin, $result ) {
245 $title = new TitleValue( (int)$result->namespace, $result->title );
246 $pageLink = $this->getLinkRenderer()->makeLink( $title );
247 $url = LinkFilter::reverseIndexes( $result->urldomain ) . $result->urlpath;
248
249 $urlLink = $this->getLinkRenderer()->makeExternalLink( $url, $url, $this->getFullTitle() );
250
251 return $this->msg( 'linksearch-line' )->rawParams( $urlLink, $pageLink )->escaped();
252 }
253
259 protected function getOrderFields() {
260 return [];
261 }
262
264 protected function getGroupName() {
265 return 'pages';
266 }
267
275 protected function getMaxResults() {
276 return max( parent::getMaxResults(), 60000 );
277 }
278
280 protected function getRecacheDB() {
281 return $this->getDatabaseProvider()->getReplicaDatabase(
282 ExternalLinksTable::VIRTUAL_DOMAIN,
283 'vslow'
284 );
285 }
286}
287
289class_alias( SpecialLinkSearch::class, 'SpecialLinkSearch' );
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:214
A class containing constants representing the names of configuration variables.
const ExternalLinksIgnoreDomains
Name constant for the ExternalLinksIgnoreDomains setting, for use with Config::get()
const UrlProtocols
Name constant for the UrlProtocols setting, for use with Config::get()
const MiserMode
Name constant for the MiserMode setting, for use with Config::get()
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:144
static listParam(array $list, $type=ListType::AND)
Definition Message.php:1355
Factory for LinkBatch objects to batch query page metadata.
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:139
The base class for all skins.
Definition Skin.php:54
This is a class for doing query pages; since they're almost all the same, we factor out some of the f...
Definition QueryPage.php:77
setDatabaseProvider(IConnectionProvider $databaseProvider)
executeLBFromResultWrapper(IResultWrapper $res, $ns=null)
Creates a new LinkBatch object, adds all pages from the passed result wrapper (MUST include title and...
setLinkBatchFactory(LinkBatchFactory $linkBatchFactory)
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getPageTitle( $subpage=false)
Get a self-referential title object.
getConfig()
Shortcut to get main config object.
getContext()
Gets the context this SpecialPage is executed in.
getRequest()
Get the WebRequest being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
getLanguage()
Shortcut to get user's language.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages By default the message key is the canonical name of...
getFullTitle()
Return the full title, including $par.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Special:LinkSearch to search the external-links table.
getRecacheDB()
Get a DB connection to be used for slow recache queries.to override IReadableDatabase
getOrderFields()
Override to squash the ORDER BY.
getQueryInfo()
Subclasses return an SQL query here, formatted as an array with the following keys: tables => Table(s...
execute( $par)
This is the actual workhorse.It does everything needed to make a real, honest-to-gosh query page....
getMaxResults()
enwiki complained about low limits on this special page
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
linkParameters()
If using extra form wheely-dealies, return a set of parameters here as an associative array....
__construct(IConnectionProvider $dbProvider, LinkBatchFactory $linkBatchFactory, private readonly UrlUtils $urlUtils,)
preprocessResults( $db, $res)
Pre-fill the link cache.
isCacheable()
Is the output of this query cacheable? Non-cacheable expensive pages will be disabled in miser mode a...
Represents the target of a wiki link.
A service to expand, parse, and otherwise manipulate URLs.
Definition UrlUtils.php:16
Content of like value.
Definition LikeValue.php:14
Provide primary and replica IDatabase connections.
A database connection without write operations.
Result wrapper for grabbing data queried from an IDatabase object.