Completed
Branch master (eb7c38)
by
unknown
25:25
created

LinkSearchPage::getMaxResults()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
/**
3
 * Implements Special:LinkSearch
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 * @ingroup SpecialPage
22
 * @author Brion Vibber
23
 */
24
25
/**
26
 * Special:LinkSearch to search the external-links table.
27
 * @ingroup SpecialPage
28
 */
29
class LinkSearchPage extends QueryPage {
30
	/** @var array|bool */
31
	private $mungedQuery = false;
32
33
	/**
34
	 * @var PageLinkRenderer
35
	 */
36
	protected $linkRenderer = null;
37
38
	function setParams( $params ) {
39
		$this->mQuery = $params['query'];
0 ignored issues
show
Bug introduced by
The property mQuery does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
40
		$this->mNs = $params['namespace'];
0 ignored issues
show
Bug introduced by
The property mNs does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
41
		$this->mProt = $params['protocol'];
0 ignored issues
show
Bug introduced by
The property mProt does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
42
	}
43
44
	function __construct( $name = 'LinkSearch' ) {
45
		parent::__construct( $name );
46
47
		// Since we don't control the constructor parameters, we can't inject services that way.
48
		// Instead, we initialize services in the execute() method, and allow them to be overridden
49
		// using the setServices() method.
50
	}
51
52
	/**
53
	 * Initialize or override the PageLinkRenderer LinkSearchPage collaborates with.
54
	 * Useful mainly for testing.
55
	 *
56
	 * @todo query logic and rendering logic should be split and also injected
57
	 *
58
	 * @param PageLinkRenderer $linkRenderer
59
	 */
60
	public function setPageLinkRenderer(
61
		PageLinkRenderer $linkRenderer
62
	) {
63
		$this->linkRenderer = $linkRenderer;
64
	}
65
66
	/**
67
	 * Initialize any services we'll need (unless it has already been provided via a setter).
68
	 * This allows for dependency injection even though we don't control object creation.
69
	 */
70 View Code Duplication
	private function initServices() {
71
		global $wgContLang;
72
		if ( !$this->linkRenderer ) {
73
			$titleFormatter = new MediaWikiTitleCodec( $wgContLang, GenderCache::singleton() );
0 ignored issues
show
Bug introduced by
It seems like \GenderCache::singleton() can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
74
			$this->linkRenderer = new MediaWikiPageLinkRenderer( $titleFormatter );
75
		}
76
	}
77
78
	function isCacheable() {
79
		return false;
80
	}
81
82
	public function execute( $par ) {
83
		$this->initServices();
84
85
		$this->setHeaders();
86
		$this->outputHeader();
87
88
		$out = $this->getOutput();
89
		$out->allowClickjacking();
90
91
		$request = $this->getRequest();
92
		$target = $request->getVal( 'target', $par );
93
		$namespace = $request->getIntOrNull( 'namespace' );
94
95
		$protocols_list = [];
96
		foreach ( $this->getConfig()->get( 'UrlProtocols' ) as $prot ) {
97
			if ( $prot !== '//' ) {
98
				$protocols_list[] = $prot;
99
			}
100
		}
101
102
		$target2 = $target;
103
		// Get protocol, default is http://
104
		$protocol = 'http://';
105
		$bits = wfParseUrl( $target );
106
		if ( isset( $bits['scheme'] ) && isset( $bits['delimiter'] ) ) {
107
			$protocol = $bits['scheme'] . $bits['delimiter'];
108
			// Make sure wfParseUrl() didn't make some well-intended correction in the
109
			// protocol
110
			if ( strcasecmp( $protocol, substr( $target, 0, strlen( $protocol ) ) ) === 0 ) {
111
				$target2 = substr( $target, strlen( $protocol ) );
112
			} else {
113
				// If it did, let LinkFilter::makeLikeArray() handle this
114
				$protocol = '';
115
			}
116
		}
117
118
		$out->addWikiMsg(
119
			'linksearch-text',
120
			'<nowiki>' . $this->getLanguage()->commaList( $protocols_list ) . '</nowiki>',
121
			count( $protocols_list )
122
		);
123
		$fields = [
124
			'target' => [
125
				'type' => 'text',
126
				'name' => 'target',
127
				'id' => 'target',
128
				'size' => 50,
129
				'label-message' => 'linksearch-pat',
130
				'default' => $target,
131
				'dir' => 'ltr',
132
			]
133
		];
134 View Code Duplication
		if ( !$this->getConfig()->get( 'MiserMode' ) ) {
135
			$fields += [
136
				'namespace' => [
137
					'type' => 'namespaceselect',
138
					'name' => 'namespace',
139
					'label-message' => 'linksearch-ns',
140
					'default' => $namespace,
141
					'id' => 'namespace',
142
					'all' => '',
143
					'cssclass' => 'namespaceselector',
144
				],
145
			];
146
		}
147
		$hiddenFields = [
148
			'title' => $this->getPageTitle()->getPrefixedDBkey(),
149
		];
150
		$htmlForm = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
151
		$htmlForm->addHiddenFields( $hiddenFields );
152
		$htmlForm->setSubmitTextMsg( 'linksearch-ok' );
153
		$htmlForm->setWrapperLegendMsg( 'linksearch' );
154
		$htmlForm->setAction( wfScript() );
155
		$htmlForm->setMethod( 'get' );
156
		$htmlForm->prepareForm()->displayForm( false );
157
		$this->addHelpLink( 'Help:Linksearch' );
158
159
		if ( $target != '' ) {
160
			$this->setParams( [
161
				'query' => Parser::normalizeLinkUrl( $target2 ),
162
				'namespace' => $namespace,
163
				'protocol' => $protocol ] );
164
			parent::execute( $par );
165
			if ( $this->mungedQuery === false ) {
166
				$out->addWikiMsg( 'linksearch-error' );
167
			}
168
		}
169
	}
170
171
	/**
172
	 * Disable RSS/Atom feeds
173
	 * @return bool
174
	 */
175
	function isSyndicated() {
176
		return false;
177
	}
178
179
	/**
180
	 * Return an appropriately formatted LIKE query and the clause
181
	 *
182
	 * @param string $query Search pattern to search for
183
	 * @param string $prot Protocol, e.g. 'http://'
184
	 *
185
	 * @return array
186
	 */
187
	static function mungeQuery( $query, $prot ) {
188
		$field = 'el_index';
189
		$dbr = wfGetDB( DB_SLAVE );
190
191
		if ( $query === '*' && $prot !== '' ) {
192
			// Allow queries like 'ftp://*' to find all ftp links
193
			$rv = [ $prot, $dbr->anyString() ];
194
		} else {
195
			$rv = LinkFilter::makeLikeArray( $query, $prot );
196
		}
197
198
		if ( $rv === false ) {
199
			// LinkFilter doesn't handle wildcard in IP, so we'll have to munge here.
200
			$pattern = '/^(:?[0-9]{1,3}\.)+\*\s*$|^(:?[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]*\*\s*$/';
201
			if ( preg_match( $pattern, $query ) ) {
202
				$rv = [ $prot . rtrim( $query, " \t*" ), $dbr->anyString() ];
203
				$field = 'el_to';
204
			}
205
		}
206
207
		return [ $rv, $field ];
208
	}
209
210
	function linkParameters() {
211
		$params = [];
212
		$params['target'] = $this->mProt . $this->mQuery;
213
		if ( $this->mNs !== null && !$this->getConfig()->get( 'MiserMode' ) ) {
214
			$params['namespace'] = $this->mNs;
215
		}
216
217
		return $params;
218
	}
219
220
	public function getQueryInfo() {
221
		$dbr = wfGetDB( DB_SLAVE );
222
		// strip everything past first wildcard, so that
223
		// index-based-only lookup would be done
224
		list( $this->mungedQuery, $clause ) = self::mungeQuery( $this->mQuery, $this->mProt );
225
		if ( $this->mungedQuery === false ) {
226
			// Invalid query; return no results
227
			return [ 'tables' => 'page', 'fields' => 'page_id', 'conds' => '0=1' ];
228
		}
229
230
		$stripped = LinkFilter::keepOneWildcard( $this->mungedQuery );
231
		$like = $dbr->buildLike( $stripped );
232
		$retval = [
233
			'tables' => [ 'page', 'externallinks' ],
234
			'fields' => [
235
				'namespace' => 'page_namespace',
236
				'title' => 'page_title',
237
				'value' => 'el_index',
238
				'url' => 'el_to'
239
			],
240
			'conds' => [
241
				'page_id = el_from',
242
				"$clause $like"
243
			],
244
			'options' => [ 'USE INDEX' => $clause ]
245
		];
246
247
		if ( $this->mNs !== null && !$this->getConfig()->get( 'MiserMode' ) ) {
248
			$retval['conds']['page_namespace'] = $this->mNs;
249
		}
250
251
		return $retval;
252
	}
253
254
	/**
255
	 * Pre-fill the link cache
256
	 *
257
	 * @param IDatabase $db
258
	 * @param ResultWrapper $res
259
	 */
260
	function preprocessResults( $db, $res ) {
261
		if ( $res->numRows() > 0 ) {
262
			$linkBatch = new LinkBatch();
263
264
			foreach ( $res as $row ) {
265
				$linkBatch->add( $row->namespace, $row->title );
266
			}
267
268
			$res->seek( 0 );
269
			$linkBatch->execute();
270
		}
271
	}
272
273
	/**
274
	 * @param Skin $skin
275
	 * @param object $result Result row
276
	 * @return string
277
	 */
278
	function formatResult( $skin, $result ) {
279
		$title = new TitleValue( (int)$result->namespace, $result->title );
280
		$pageLink = $this->linkRenderer->renderHtmlLink( $title );
281
282
		$url = $result->url;
283
		$urlLink = Linker::makeExternalLink( $url, $url );
284
285
		return $this->msg( 'linksearch-line' )->rawParams( $urlLink, $pageLink )->escaped();
286
	}
287
288
	/**
289
	 * Override to squash the ORDER BY.
290
	 * We do a truncated index search, so the optimizer won't trust
291
	 * it as good enough for optimizing sort. The implicit ordering
292
	 * from the scan will usually do well enough for our needs.
293
	 * @return array
294
	 */
295
	function getOrderFields() {
296
		return [];
297
	}
298
299
	protected function getGroupName() {
300
		return 'redirects';
301
	}
302
303
	/**
304
	 * enwiki complained about low limits on this special page
305
	 *
306
	 * @see T130058
307
	 * @todo FIXME This special page should not use LIMIT for paging
308
	 */
309
	protected function getMaxResults() {
310
		return max( parent::getMaxResults(), 60000 );
311
	}
312
}
313