MediaWiki master
SpecialPrefixIndex.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Specials;
22
31
39
44 protected $stripPrefix = false;
45
46 protected $hideRedirects = false;
47
48 // Inherit $maxPerPage
49
50 // phpcs:ignore MediaWiki.Commenting.PropertyDocumentation.WrongStyle
51 private IConnectionProvider $dbProvider;
52 private LinkCache $linkCache;
53
58 public function __construct(
59 IConnectionProvider $dbProvider,
60 LinkCache $linkCache
61 ) {
62 parent::__construct( $dbProvider );
63 $this->mName = 'Prefixindex';
64 $this->dbProvider = $dbProvider;
65 $this->linkCache = $linkCache;
66 }
67
72 public function execute( $par ) {
73 $this->setHeaders();
74 $this->outputHeader();
75
76 $out = $this->getOutput();
77 $out->addModuleStyles( 'mediawiki.special' );
78
79 # GET values
80 $request = $this->getRequest();
81 $from = $request->getVal( 'from', '' );
82 $prefix = $request->getVal( 'prefix', '' );
83 $ns = $request->getIntOrNull( 'namespace' );
84 $namespace = (int)$ns; // if no namespace given, use 0 (NS_MAIN).
85 $this->hideRedirects = $request->getBool( 'hideredirects', $this->hideRedirects );
86 $this->stripPrefix = $request->getBool( 'stripprefix', $this->stripPrefix );
87
88 $namespaces = $this->getContentLanguage()->getNamespaces();
89 $out->setPageTitleMsg(
90 ( $namespace > 0 && array_key_exists( $namespace, $namespaces ) )
91 ? $this->msg( 'prefixindex-namespace' )->plaintextParams(
92 str_replace( '_', ' ', $namespaces[$namespace] )
93 )
94 : $this->msg( 'prefixindex' )
95 );
96
97 $showme = '';
98 if ( $par !== null ) {
99 $showme = $par;
100 } elseif ( $prefix != '' ) {
101 $showme = $prefix;
102 } elseif ( $from != '' && $ns === null ) {
103 // For back-compat with Special:Allpages
104 // Don't do this if namespace is passed, so paging works when doing NS views.
105 $showme = $from;
106 }
107
108 // T29864: if transcluded, show all pages instead of the form.
109 if ( $this->including() || $showme != '' || $ns !== null ) {
110 $this->showPrefixChunk( $namespace, $showme, $from );
111 } else {
112 $out->addHTML( $this->namespacePrefixForm( $namespace, '' )->getHTML( false ) );
113 }
114 }
115
122 protected function namespacePrefixForm( $namespace = NS_MAIN, $from = '' ): HTMLForm {
123 $formDescriptor = [
124 'prefix' => [
125 'label-message' => 'allpagesprefix',
126 'name' => 'prefix',
127 'id' => 'nsfrom',
128 'type' => 'text',
129 'size' => '30',
130 'default' => str_replace( '_', ' ', $from ),
131 ],
132 'namespace' => [
133 'type' => 'namespaceselect',
134 'name' => 'namespace',
135 'id' => 'namespace',
136 'label-message' => 'namespace',
137 'all' => null,
138 'default' => $namespace,
139 ],
140 'hidedirects' => [
141 'class' => HTMLCheckField::class,
142 'name' => 'hideredirects',
143 'label-message' => 'allpages-hide-redirects',
144 ],
145 'stripprefix' => [
146 'class' => HTMLCheckField::class,
147 'name' => 'stripprefix',
148 'label-message' => 'prefixindex-strip',
149 ],
150 ];
151
152 $this->getHookRunner()->onSpecialPrefixIndexGetFormFilters( $this->getContext(), $formDescriptor );
153
154 $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
155 ->setMethod( 'get' )
156 ->setTitle( $this->getPageTitle() ) // Remove subpage
157 ->setWrapperLegendMsg( 'prefixindex' )
158 ->setSubmitTextMsg( 'prefixindex-submit' );
159
160 return $htmlForm->prepareForm();
161 }
162
168 protected function showPrefixChunk( $namespace, $prefix, $from = null ) {
169 if ( $from === null ) {
170 $from = $prefix;
171 }
172
173 $fromList = $this->getNamespaceKeyAndText( $namespace, $from );
174 $prefixList = $this->getNamespaceKeyAndText( $namespace, $prefix );
175 $namespaces = $this->getContentLanguage()->getNamespaces();
176 $res = null;
177 $n = 0;
178 $nextRow = null;
179 $preparedHtmlForm = $this->namespacePrefixForm( $namespace, $prefix );
180
181 if ( !$prefixList || !$fromList ) {
182 $out = $this->msg( 'allpagesbadtitle' )->parseAsBlock();
183 } elseif ( !array_key_exists( $namespace, $namespaces ) ) {
184 // Show errormessage and reset to NS_MAIN
185 $out = $this->msg( 'allpages-bad-ns', $namespace )->parse();
186 $namespace = NS_MAIN;
187 } else {
188 [ $namespace, $prefixKey, $prefix ] = $prefixList;
189 [ /* $fromNS */, $fromKey, ] = $fromList;
190
191 # ## @todo FIXME: Should complain if $fromNs != $namespace
192
193 $dbr = $this->dbProvider->getReplicaDatabase();
194 $queryBuiler = $dbr->newSelectQueryBuilder()
195 ->select( LinkCache::getSelectFields() )
196 ->from( 'page' )
197 ->where( [
198 'page_namespace' => $namespace,
199 $dbr->expr(
200 'page_title',
201 IExpression::LIKE,
202 new LikeValue( $prefixKey, $dbr->anyString() )
203 ),
204 $dbr->expr( 'page_title', '>=', $fromKey ),
205 ] )
206 ->orderBy( 'page_title' )
207 ->limit( $this->maxPerPage + 1 )
208 ->useIndex( 'page_name_title' );
209
210 if ( $this->hideRedirects ) {
211 $queryBuiler->andWhere( [ 'page_is_redirect' => 0 ] );
212 }
213
214 $this->getHookRunner()->onSpecialPrefixIndexQuery( $preparedHtmlForm->mFieldData, $queryBuiler );
215
216 $res = $queryBuiler->caller( __METHOD__ )->fetchResultSet();
217
218 // @todo FIXME: Side link to previous
219
220 if ( $res->numRows() > 0 ) {
221 $out = Html::openElement( 'ul', [ 'class' => 'mw-prefixindex-list' ] );
222
223 $prefixLength = strlen( $prefix );
224 foreach ( $res as $row ) {
225 if ( $n >= $this->maxPerPage ) {
226 $nextRow = $row;
227 break;
228 }
229 $title = Title::newFromRow( $row );
230 // Make sure it gets into LinkCache
231 $this->linkCache->addGoodLinkObjFromRow( $title, $row );
232 $displayed = $title->getText();
233 // Try not to generate unclickable links
234 if ( $this->stripPrefix && $prefixLength !== strlen( $displayed ) ) {
235 $displayed = substr( $displayed, $prefixLength );
236 }
237 $link = ( $title->isRedirect() ? '<div class="allpagesredirect">' : '' ) .
238 $this->getLinkRenderer()->makeKnownLink(
239 $title,
240 $displayed
241 ) .
242 ( $title->isRedirect() ? '</div>' : '' );
243
244 $out .= "<li>$link</li>\n";
245 $n++;
246
247 }
248 $out .= Html::closeElement( 'ul' );
249
250 if ( $res->numRows() > 2 ) {
251 // Only apply CSS column styles if there are more than 2 entries.
252 // Otherwise, rendering is broken as "mw-prefixindex-body"'s CSS column count is 3.
253 $out = Html::rawElement( 'div', [ 'class' => 'mw-prefixindex-body' ], $out );
254 }
255 } else {
256 $out = '';
257 }
258 }
259
260 $output = $this->getOutput();
261
262 if ( $this->including() ) {
263 // We don't show the nav-links and the form when included in other
264 // pages, so let's just finish here.
265 $output->addHTML( $out );
266 return;
267 }
268
269 $topOut = $preparedHtmlForm->getHTML( false );
270
271 if ( $res && ( $n == $this->maxPerPage ) && $nextRow ) {
272 $query = [
273 'from' => $nextRow->page_title,
274 'prefix' => $prefix,
275 'hideredirects' => $this->hideRedirects,
276 'stripprefix' => $this->stripPrefix,
277 ];
278
279 if ( $namespace || $prefix == '' ) {
280 // Keep the namespace even if it's 0 for empty prefixes.
281 // This tells us we're not just a holdover from old links.
282 $query['namespace'] = $namespace;
283 }
284
285 $nextLink = $this->getLinkRenderer()->makeKnownLink(
286 $this->getPageTitle(),
287 $this->msg( 'nextpage', str_replace( '_', ' ', $nextRow->page_title ) )->text(),
288 [],
289 $query
290 );
291
292 // Link shown at the top of the page below the form
293 $topOut .= Html::rawElement( 'div',
294 [ 'class' => 'mw-prefixindex-nav' ],
295 $nextLink
296 );
297
298 // Link shown at the footer
299 $out .= "\n" . Html::element( 'hr' ) .
300 Html::rawElement(
301 'div',
302 [ 'class' => 'mw-prefixindex-nav' ],
303 $nextLink
304 );
305
306 }
307
308 $output->addHTML( $topOut . $out );
309 }
310
311 protected function getGroupName() {
312 return 'pages';
313 }
314}
315
320class_alias( SpecialPrefixIndex::class, 'SpecialPrefixindex' );
const NS_MAIN
Definition Defines.php:65
Cache for article titles (prefixed DB keys) and ids linked from one source.
Definition LinkCache.php:52
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:208
setMethod( $method='post')
Set the method used to submit the form.
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getPageTitle( $subpage=false)
Get a self-referential title 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.
getContentLanguage()
Shortcut to get content language.
including( $x=null)
Whether the special page is being evaluated via transclusion.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages By default the message key is the canonical name of...
Implements Special:Allpages.
Implements Special:Prefixindex.
namespacePrefixForm( $namespace=NS_MAIN, $from='')
Prepared HTMLForm object for the top form.
__construct(IConnectionProvider $dbProvider, LinkCache $linkCache)
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
showPrefixChunk( $namespace, $prefix, $from=null)
execute( $par)
Entry point: initialise variables and call subfunctions.
$stripPrefix
Whether to remove the searched prefix from the displayed link.
Represents a title within MediaWiki.
Definition Title.php:79
Content of like value.
Definition LikeValue.php:14
Provide primary and replica IDatabase connections.
element(SerializerNode $parent, SerializerNode $node, $contents)