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