MediaWiki master
SpecialLinkSearch.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Specials;
8
19use stdClass;
25
34 private $mungedQuery = false;
36 private $mQuery;
38 private $mNs;
40 private $mProt;
41
42 private function setParams( array $params ) {
43 $this->mQuery = $params['query'];
44 $this->mNs = $params['namespace'];
45 $this->mProt = $params['protocol'];
46 }
47
48 public function __construct(
49 IConnectionProvider $dbProvider,
50 LinkBatchFactory $linkBatchFactory,
51 private readonly UrlUtils $urlUtils,
52 ) {
53 parent::__construct( 'LinkSearch' );
54 $this->setDatabaseProvider( $dbProvider );
55 $this->setLinkBatchFactory( $linkBatchFactory );
56 }
57
59 public function isCacheable() {
60 return false;
61 }
62
64 public function execute( $par ) {
65 $this->setHeaders();
66 $this->outputHeader();
67
68 $out = $this->getOutput();
69 $out->getMetadata()->setPreventClickjacking( false );
70
71 $request = $this->getRequest();
72 $target = $request->getVal( 'target', $par ?? '' );
73 $namespace = $request->getIntOrNull( 'namespace' );
74
75 $protocols_list = [];
76 foreach ( $this->getConfig()->get( MainConfigNames::UrlProtocols ) as $prot ) {
77 if ( $prot !== '//' ) {
78 $protocols_list[] = $prot;
79 }
80 }
81
82 $target2 = Parser::normalizeLinkUrl( $target );
83 $protocol = null;
84 $bits = $this->urlUtils->parse( $target );
85 if ( isset( $bits['scheme'] ) && isset( $bits['delimiter'] ) ) {
86 $protocol = $bits['scheme'] . $bits['delimiter'];
87 // Make sure UrlUtils::parse() didn't make some well-intended correction in the protocol
88 if ( str_starts_with( strtolower( $target ), strtolower( $protocol ) ) ) {
89 $target2 = substr( $target, strlen( $protocol ) );
90 } else {
91 // If it did, let LinkFilter::makeLikeArray() handle this
92 $protocol = '';
93 }
94 }
95
96 $out->addWikiMsg(
97 'linksearch-text',
98 '<nowiki>' . $this->getLanguage()->commaList( $protocols_list ) . '</nowiki>',
99 count( $protocols_list )
100 );
101 $fields = [
102 'target' => [
103 'type' => 'text',
104 'name' => 'target',
105 'id' => 'target',
106 'size' => 50,
107 'label-message' => 'linksearch-pat',
108 'default' => $target,
109 'dir' => 'ltr',
110 ]
111 ];
112 if ( !$this->getConfig()->get( MainConfigNames::MiserMode ) ) {
113 $fields += [
114 'namespace' => [
115 'type' => 'namespaceselect',
116 'name' => 'namespace',
117 'label-message' => 'linksearch-ns',
118 'default' => $namespace,
119 'id' => 'namespace',
120 'all' => '',
121 'cssclass' => 'namespaceselector',
122 ],
123 ];
124 }
125 $htmlForm = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
126 $htmlForm->setSubmitTextMsg( 'linksearch-ok' );
127 $htmlForm->setWrapperLegendMsg( 'linksearch' );
128 $htmlForm->setTitle( $this->getPageTitle() );
129 $htmlForm->setMethod( 'get' );
130 $htmlForm->prepareForm()->displayForm( false );
131 $this->addHelpLink( 'Help:Linksearch' );
132
133 if ( $target != '' ) {
134 $this->setParams( [
135 'query' => $target2,
136 'namespace' => $namespace,
137 'protocol' => $protocol ] );
138 parent::execute( $par );
139 if ( $this->mungedQuery === false ) {
140 $out->addWikiMsg( 'linksearch-error' );
141 }
142 }
143 $ignoredDomains = $this->getConfig()->get( MainConfigNames::ExternalLinksIgnoreDomains );
144 if ( $ignoredDomains ) {
145 $out->addWikiMsg(
146 'linksearch-text-ignored-domains',
147 $this->getLanguage()->listToText(
148 array_map( static fn ( $domain ) => "<code>$domain</code>", $ignoredDomains )
149 ),
150 count( $ignoredDomains )
151 );
152 }
153 }
154
159 public function isSyndicated() {
160 return false;
161 }
162
164 protected function linkParameters() {
165 $params = [];
166 $params['target'] = $this->mProt . $this->mQuery;
167 if ( $this->mNs !== null && !$this->getConfig()->get( MainConfigNames::MiserMode ) ) {
168 $params['namespace'] = $this->mNs;
169 }
170
171 return $params;
172 }
173
175 public function getQueryInfo() {
176 $dbr = $this->getDatabaseProvider()->getReplicaDatabase( ExternalLinksTable::VIRTUAL_DOMAIN );
177
178 $field = 'el_to_domain_index';
179 $extraFields = [
180 'urldomain' => 'el_to_domain_index',
181 'urlpath' => 'el_to_path'
182 ];
183 if ( $this->mQuery === '*' && $this->mProt !== '' ) {
184 if ( $this->mProt !== null ) {
185 $this->mungedQuery = [
186 $dbr->expr( $field, IExpression::LIKE, new LikeValue( $this->mProt, $dbr->anyString() ) ),
187 ];
188 } else {
189 $this->mungedQuery = [
190 $dbr->expr( $field, IExpression::LIKE, new LikeValue( 'http://', $dbr->anyString() ) )
191 ->or( $field, IExpression::LIKE, new LikeValue( 'https://', $dbr->anyString() ) ),
192 ];
193 }
194 } else {
195 $this->mungedQuery = LinkFilter::getQueryConditions( $this->mQuery, [
196 'protocol' => $this->mProt,
197 'oneWildcard' => true,
198 'db' => $dbr
199 ] );
200 if ( $this->mungedQuery === false ) {
201 // Invalid query; return no results
202 return [ 'tables' => 'page', 'fields' => 'page_id', 'conds' => '0=1' ];
203 }
204 }
205 $orderBy = [ 'el_id' ];
206
207 $retval = [
208 'tables' => [ 'page', 'externallinks' ],
209 'fields' => [
210 'namespace' => 'page_namespace',
211 'title' => 'page_title',
212 ...$extraFields,
213 ],
214 'conds' => [
215 'page_id = el_from',
216 ...$this->mungedQuery,
217 ],
218 'options' => [ 'ORDER BY' => $orderBy ]
219 ];
220
221 if ( $this->mNs !== null && !$this->getConfig()->get( MainConfigNames::MiserMode ) ) {
222 $retval['conds']['page_namespace'] = $this->mNs;
223 }
224
225 return $retval;
226 }
227
234 public function preprocessResults( $db, $res ) {
235 $this->executeLBFromResultWrapper( $res );
236 }
237
243 public function formatResult( $skin, $result ) {
244 $title = new TitleValue( (int)$result->namespace, $result->title );
245 $pageLink = $this->getLinkRenderer()->makeLink( $title );
246 $url = LinkFilter::reverseIndexes( $result->urldomain ) . $result->urlpath;
247
248 $urlLink = $this->getLinkRenderer()->makeExternalLink( $url, $url, $this->getFullTitle() );
249
250 return $this->msg( 'linksearch-line' )->rawParams( $urlLink, $pageLink )->escaped();
251 }
252
258 protected function getOrderFields() {
259 return [];
260 }
261
263 protected function getGroupName() {
264 return 'pages';
265 }
266
274 protected function getMaxResults() {
275 return max( parent::getMaxResults(), 60000 );
276 }
277
279 protected function getRecacheDB() {
280 return $this->getDatabaseProvider()->getReplicaDatabase(
281 ExternalLinksTable::VIRTUAL_DOMAIN,
282 'vslow'
283 );
284 }
285}
286
288class_alias( SpecialLinkSearch::class, 'SpecialLinkSearch' );
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:207
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()
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:134
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.